diff options
Diffstat (limited to 'sw/source/core/text')
67 files changed, 9970 insertions, 5010 deletions
diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 088f21c00b10..62052080d479 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -17,22 +17,25 @@ * 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> #include <vcl/outdev.hxx> #include <vcl/pdfextoutdevdata.hxx> +#include <vcl/pdf/PDFNote.hxx> #include <tools/multisel.hxx> #include <editeng/adjustitem.hxx> #include <editeng/lrspitem.hxx> #include <editeng/langitem.hxx> #include <tools/urlobj.hxx> #include <svl/languageoptions.hxx> +#include <svl/numformat.hxx> #include <svl/zforlist.hxx> #include <swatrset.hxx> #include <frmatr.hxx> @@ -65,36 +68,38 @@ #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 <PostItMgr.hxx> +#include <AnnotationWin.hxx> +#include <names.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 +130,65 @@ 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::vector< IdMapEntry > NoteIdMap; +typedef std::map< const SwTable*, TableColumnsMapEntry > TableColumnsMap; +typedef std::map< const SwTable*, sal_Int32 > TableCaptionsMap; +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; + TableCaptionsMap m_TableCaptionsMap; + LinkIdMap m_LinkIdMap; + NoteIdMap m_NoteIdMap; + 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; +constexpr OUString constTitleStyleName = u"Title"_ustr; +constexpr OUString constEmphasisStyleName = u"Emphasis"_ustr; +constexpr OUString constStrongEmphasisStyleName = u"Strong Emphasis"_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 +197,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,12 +216,13 @@ 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"; +constexpr OUStringLiteral aAnnotString = u"Annot"; // returns true if first paragraph in cell frame has 'table heading' style bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) @@ -178,14 +235,101 @@ bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps(); const SwFormat* pTextFormat = pTextNode->GetFormatColl(); - OUString sStyleName; + ProgName sStyleName; SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); - bRet = sStyleName == aTableHeadingName; + bRet = sStyleName.toString() == 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); + TableStyleName const& rStyleName(pTable->GetTableStyleName()); + if (!rStyleName.isEmpty()) + { + if (SwTableAutoFormat const*const pTableAF = + pTable->GetFrameFormat()->GetDoc().GetTableStyles().FindAutoFormat(rStyleName)) + { + bRet |= pTableAF->HasHeaderRow(); + } + } } return bRet; } +// returns true if the frame is a caption +bool lcl_IsCaptionFrame(const SwFrame& rFrame) +{ + if (!rFrame.IsTextFrame()) + return false; + + SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(&rFrame)); + const SwTextNode* const pTextNd(rTextFrame.GetTextNodeForParaProps()); + if (!pTextNd) + return false; + + const SwFormat* pTextFormat = pTextNd->GetFormatColl(); + const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr; + + ProgName sParentStyleName; + if (pParentTextFormat) + SwStyleNameMapper::FillProgName(pParentTextFormat->GetName(), sParentStyleName, + SwGetPoolIdFromName::TxtColl); + + return sParentStyleName == aCaption; +} + +const SwTabFrame* lcl_FindTableForCaption(const SwFrame& rFrame) +{ + const SwTabFrame* pTabFrame = nullptr; + bool bPrevFrame = false; + + // It is possible to add multiple captions to a table, + // both above and below, either simultaneously or separately. + // Start by checking the next frame, and if we don't find a table frame + // or if the next frame is not a caption, we return to the current caption + // and perform the same operation backwards using the previous frames. + const SwFrame* pRetFrame = rFrame.GetNext(); + if (!pRetFrame) + { + bPrevFrame = true; + pRetFrame = rFrame.GetPrev(); + } + + while (pRetFrame) + { + if (pRetFrame->IsTabFrame()) + { + pTabFrame = static_cast<const SwTabFrame*>(pRetFrame); + break; + } + + // Check if the next or the previous frame is a caption frame + bool bIsCaption = lcl_IsCaptionFrame(*pRetFrame); + if (bIsCaption && pRetFrame->GetNext()) + { + pRetFrame = !bPrevFrame ? pRetFrame->GetNext() : pRetFrame->GetPrev(); + } + else if (!bPrevFrame && rFrame.GetPrev()) + { + // If no table was found while checking the GetNext() frames, + // jump back to the current caption and + // start checking the GetPrev() frames. + bPrevFrame = true; + pRetFrame = rFrame.GetPrev(); + } + else + // This part handles the case + // when the table has been deleted, + // but the caption has not. + break; + } + + return pTabFrame; +} + // List all frames for which the NonStructElement tag is set: bool lcl_IsInNonStructEnv( const SwFrame& rFrame ) { @@ -208,25 +352,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 +420,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 +428,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, @@ -309,7 +491,7 @@ SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo, else if ( mpPorInfo ) BeginInlineStructureElements(); else - BeginTag( vcl::PDFWriter::NonStructElement, OUString() ); + BeginTag( vcl::pdf::StructElement::NonStructElement, OUString() ); #if OSL_DEBUG_LEVEL > 1 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); @@ -336,10 +518,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 +544,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 +565,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.contains(pKey) + || 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 +597,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 +605,63 @@ 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::pdf::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::pdf::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() ) || + rFrame.IsTabFrame() ) + { + pKey = lcl_GetKeyFromFrame(rFrame); + + if (pKey) + { + FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet); + assert(!rFrameTagSet.contains(pKey)); + 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 +672,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 ) + if (vcl::pdf::StructElement::List == eType) { - NumListIdMap& rNumListIdMap = SwEnhancedPDFExportHelper::GetNumListIdMap(); + NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap); rNumListIdMap[ pNodeNum ] = nId; } - else if ( vcl::PDFWriter::LIBody == eType ) + else if (vcl::pdf::StructElement::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,8 +700,25 @@ 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 ) +void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) { sal_Int32 nVal; @@ -510,16 +743,21 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bool bHeight = false; bool bBox = false; bool bRowSpan = false; + bool bAltText = false; // Check which attributes to set: - switch ( eType ) + switch (eType) { - case vcl::PDFWriter::Document : + case vcl::pdf::StructElement::Document: bWritingMode = true; break; - case vcl::PDFWriter::Table : + case vcl::pdf::StructElement::Note: + bPlacement = true; + break; + + case vcl::pdf::StructElement::Table: bPlacement = bWritingMode = bSpaceBefore = @@ -531,13 +769,15 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bBox = true; break; - case vcl::PDFWriter::TableRow : + case vcl::pdf::StructElement::TableRow: bPlacement = bWritingMode = true; break; - case vcl::PDFWriter::TableHeader : - case vcl::PDFWriter::TableData : + case vcl::pdf::StructElement::TableHeader: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column); + [[fallthrough]]; + case vcl::pdf::StructElement::TableData: bPlacement = bWritingMode = bWidth = @@ -545,17 +785,22 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bRowSpan = true; break; - case vcl::PDFWriter::H1 : - case vcl::PDFWriter::H2 : - case vcl::PDFWriter::H3 : - case vcl::PDFWriter::H4 : - case vcl::PDFWriter::H5 : - case vcl::PDFWriter::H6 : - case vcl::PDFWriter::Paragraph : - case vcl::PDFWriter::Heading : - case vcl::PDFWriter::Caption : - case vcl::PDFWriter::BlockQuote : - + case vcl::pdf::StructElement::Caption: + if (pFrame->IsSctFrame()) + { + break; + } + [[fallthrough]]; + case vcl::pdf::StructElement::H1: + case vcl::pdf::StructElement::H2: + case vcl::pdf::StructElement::H3: + case vcl::pdf::StructElement::H4: + case vcl::pdf::StructElement::H5: + case vcl::pdf::StructElement::H6: + case vcl::pdf::StructElement::Paragraph: + case vcl::pdf::StructElement::Heading: + case vcl::pdf::StructElement::BlockQuote: + case vcl::pdf::StructElement::Title: bPlacement = bWritingMode = bSpaceBefore = @@ -566,13 +811,35 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bTextAlign = true; break; - case vcl::PDFWriter::Formula : - case vcl::PDFWriter::Figure : + case vcl::pdf::StructElement::Formula: + case vcl::pdf::StructElement::Figure: + bAltText = bPlacement = bWidth = bHeight = bBox = true; break; + + case vcl::pdf::StructElement::Division: + if (pFrame->IsFlyFrame()) // this can be something else too + { + bAltText = true; + bBox = true; + } + break; + + case vcl::pdf::StructElement::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; } @@ -581,10 +848,20 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) if ( bPlacement ) { - eVal = vcl::PDFWriter::TableHeader == eType || - vcl::PDFWriter::TableData == eType ? - vcl::PDFWriter::Inline : - vcl::PDFWriter::Block; + bool bIsFigureInline = false; + if (vcl::pdf::StructElement::Figure == eType) + { + const SwFrame* pKeyFrame = static_cast<const SwFlyFrame&>(*pFrame).GetAnchorFrame(); + if (const SwLayoutFrame* pUpperFrame = pKeyFrame->GetUpper()) + if (pUpperFrame->GetType() == SwFrameType::Body) + bIsFigureInline = true; + } + + eVal = vcl::pdf::StructElement::TableHeader == eType + || vcl::pdf::StructElement::TableData == eType + || bIsFigureInline + ? vcl::PDFWriter::Inline + : vcl::PDFWriter::Block; mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal ); } @@ -632,9 +909,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.ResolveTextFirstLineOffset({}); if ( 0 != nVal ) mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal ); } @@ -658,9 +935,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() : u" - "_ustr); + OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription()); + if (!altText.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(altText); + } + } if ( bWidth ) { @@ -677,10 +970,10 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) if ( bBox ) { // BBox only for non-split tables: - if ( vcl::PDFWriter::Table != eType || + if (vcl::pdf::StructElement::Table != eType || ( pFrame->IsTabFrame() && !static_cast<const SwTabFrame*>(pFrame)->IsFollow() && - !static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) ) + !static_cast<const SwTabFrame*>(pFrame)->HasFollow() )) { mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect()); } @@ -688,9 +981,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 +994,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 +1010,12 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) } } } + + if (mpFrameInfo->m_isLink) + { + SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea()); + LinkLinkLink(*mpPDFExtOutDevData, aRect); + } } /* @@ -731,15 +1030,16 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bool bBaselineShift = false; bool bTextDecorationType = false; bool bLinkAttribute = false; + bool bAnnotAttribute = false; bool bLanguage = false; // Check which attributes to set: - switch ( eType ) + switch (eType) { - case vcl::PDFWriter::Span : - case vcl::PDFWriter::Quote : - case vcl::PDFWriter::Code : + case vcl::pdf::StructElement::Span: + case vcl::pdf::StructElement::Quote: + case vcl::pdf::StructElement::Code: if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() || PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() ) bActualText = true; @@ -751,13 +1051,64 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) } break; - case vcl::PDFWriter::Link : + case vcl::pdf::StructElement::Link: + case vcl::pdf::StructElement::BibEntry: bTextDecorationType = bBaselineShift = bLinkAttribute = bLanguage = true; break; + case vcl::pdf::StructElement::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; + + case vcl::pdf::StructElement::Annot: + bAnnotAttribute = + bLanguage = true; + break; + default: break; } @@ -802,7 +1153,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,19 +1161,73 @@ 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); + } + + if (bAnnotAttribute) + { + SwRect aPorRect; + rInf.CalcRect(*pPor, &aPorRect); + const NoteIdMap& rNoteIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap); + const Point aCenter = aPorRect.Center(); + auto aIter = std::find_if(rNoteIdMap.begin(), rNoteIdMap.end(), + [&aCenter](const IdMapEntry& rEntry) + { return rEntry.first.Contains(aCenter); }); + if (aIter != rNoteIdMap.end()) { - sal_Int32 nLinkId = (*aIter).second; - mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::LinkAnnotation, nLinkId ); + sal_Int32 nNoteId = (*aIter).second; + mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::NoteAnnotation, + nNoteId); } } } + else if (mpNumInfo && eType == vcl::pdf::StructElement::List) + { + SwTextFrame const& rFrame(mpNumInfo->mrFrame); + SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps()); + SwNumRule const*const pNumRule = rNode.GetNumRule(); + assert(pNumRule); // was required for List + + auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) { + switch (rFormat.GetNumberingType()) + { + case css::style::NumberingType::CHARS_UPPER_LETTER: + return vcl::PDFWriter::UpperAlpha; + case css::style::NumberingType::CHARS_LOWER_LETTER: + return vcl::PDFWriter::LowerAlpha; + case css::style::NumberingType::ROMAN_UPPER: + return vcl::PDFWriter::UpperRoman; + case css::style::NumberingType::ROMAN_LOWER: + return vcl::PDFWriter::LowerRoman; + case css::style::NumberingType::ARABIC: + return vcl::PDFWriter::Decimal; + case css::style::NumberingType::CHAR_SPECIAL: + switch (rFormat.GetBulletChar()) + { + case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437': + return vcl::PDFWriter::Disc; + case u'\u2218': case u'\u25CB': case u'\u25E6': + return vcl::PDFWriter::Circle; + case u'\u25A0': case u'\u25AA': case u'\uE00A': + return vcl::PDFWriter::Square; + default: + return vcl::PDFWriter::NONE; + } + default: // the other 50 types + return vcl::PDFWriter::NONE; + } + }; + + // Note: for every level, BeginNumberedListStructureElements() produces + // a separate List element, so even though in PDF this is limited to + // the whole List we can just export the current level here. + vcl::PDFWriter::StructAttributeValue const value( + ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel()))); + // ISO 14289-1:2014, Clause: 7.6 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value); + } } void SwTaggedPDFHelper::BeginNumberedListStructureElements() @@ -832,14 +1237,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 +1285,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 +1325,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 +1356,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(); } @@ -953,12 +1367,17 @@ void SwTaggedPDFHelper::BeginNumberedListStructureElements() const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item: if ( bNewListTag ) - BeginTag( vcl::PDFWriter::List, aListString ); + BeginTag(vcl::pdf::StructElement::List, aListString); if ( bNewItemTag ) { - BeginTag( vcl::PDFWriter::ListItem, aListItemString ); - BeginTag( vcl::PDFWriter::LIBody, aListBodyString ); + BeginTag(vcl::pdf::StructElement::ListItem, aListItemString); + assert(rTextFrame.GetPara()); + // check whether to open LBody now or delay until after Lbl + if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering)) + { + BeginTag(vcl::pdf::StructElement::LIBody, aListBodyString); + } } } @@ -968,7 +1387,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. @@ -989,7 +1408,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // Document: Document - nPDFType = vcl::PDFWriter::Document; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Document); aPDFType = aDocumentString; break; @@ -998,24 +1417,24 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // Header, Footer: NonStructElement - nPDFType = vcl::PDFWriter::NonStructElement; + nPDFType = sal_uInt16(vcl::pdf::StructElement::NonStructElement); break; - case SwFrameType::FtnCont : + case SwFrameType::FootnoteContainer: // Footnote container: Division - nPDFType = vcl::PDFWriter::Division; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Division); aPDFType = aDivString; break; - case SwFrameType::Ftn : + case SwFrameType::Footnote: // Footnote frame: Note // Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless // we treat it like a grouping element! - nPDFType = vcl::PDFWriter::Note; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Note); aPDFType = aNoteString; break; @@ -1026,26 +1445,54 @@ 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.contains(pSection)) + { + // 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 = sal_uInt16(vcl::pdf::StructElement::Caption); + aPDFType = aCaptionString; + } + else if (SectionType::ToxContent == pSection->GetType()) { const SwTOXBase* pTOXBase = pSection->GetTOXBase(); if ( pTOXBase ) { if ( TOX_INDEX == pTOXBase->GetType() ) { - nPDFType = vcl::PDFWriter::Index; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Index); aPDFType = aIndexString; } else { - nPDFType = vcl::PDFWriter::TOC; + nPDFType = sal_uInt16(vcl::pdf::StructElement::TOC); aPDFType = aTOCString; } } } else if ( SectionType::Content == pSection->GetType() ) { - nPDFType = vcl::PDFWriter::Section; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Section); aPDFType = aSectString; } } @@ -1057,14 +1504,24 @@ 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::pdf::StructElement::LIBody, aListBodyString); + SwNodeNum const*const pNodeNum(pTextNd->GetNum(rTextFrame.getRootFrame())); + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); + rNumListBodyIdMap[ pNodeNum ] = nId; + } const SwFormat* pTextFormat = pTextNd->GetFormatColl(); const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr; - OUString sStyleName; - OUString sParentStyleName; + ProgName sStyleName; + ProgName sParentStyleName; if ( pTextFormat) SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); @@ -1074,14 +1531,22 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // This is the default. If the paragraph could not be mapped to // any of the standard pdf tags, we write a user defined tag // <stylename> with role = P - nPDFType = vcl::PDFWriter::Paragraph; - aPDFType = sStyleName; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Paragraph); + aPDFType = sStyleName.toString(); + + // Title + + if (sStyleName == constTitleStyleName) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Title); + aPDFType = constTitleStyleName; + } // Quotations: BlockQuote if (sStyleName == aQuotations) { - nPDFType = vcl::PDFWriter::BlockQuote; + nPDFType = sal_uInt16(vcl::pdf::StructElement::BlockQuote); aPDFType = aBlockQuoteString; } @@ -1089,7 +1554,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() else if (sStyleName == aCaption) { - nPDFType = vcl::PDFWriter::Caption; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Caption); aPDFType = aCaptionString; } @@ -1097,27 +1562,63 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() else if (sParentStyleName == aCaption) { - nPDFType = vcl::PDFWriter::Caption; - aPDFType = sStyleName + aCaptionString; + OUString sTableCaption = sStyleName.toString() + aCaptionString; + + if (!pFrame->IsInFly()) // Table caption + { + TableCaptionsMap& rTableCaptionsMap( + mpPDFExtOutDevData->GetSwPDFState()->m_TableCaptionsMap); + + const SwTabFrame* pTabFrame = lcl_FindTableForCaption(*pFrame); + if (pTabFrame) + { + const SwTable* pTable = pTabFrame->GetTable(); + if (rTableCaptionsMap.contains(pTable)) + { + // Reopen Caption tag: + // - if the table has an above and below caption + // - if the table has multiple above or below captions + m_nRestoreCurrentTag + = mpPDFExtOutDevData->GetCurrentStructureElement(); + + sal_Int32 const nCaptionId = rTableCaptionsMap[pTable]; + mpPDFExtOutDevData->SetCurrentStructureElement(nCaptionId); + } + else + { + OpenTagImpl(pTable); + + // Open Caption tag + sal_Int32 const nId = BeginTagImpl( + nullptr, vcl::pdf::StructElement::Caption, sTableCaption); + + rTableCaptionsMap[pTable] = nId; + } + } + aPDFType = "Standard"; + } + else // Figure caption + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Caption); + aPDFType = sTableCaption; + } } // Heading: H else if (sStyleName == aHeading) { - nPDFType = vcl::PDFWriter::Heading; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Heading); aPDFType = aHString; } // 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 +1636,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>(sal_uInt16(vcl::pdf::StructElement::H1) + nRealLevel); } // Section: TOCI @@ -1154,7 +1676,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() ) { // Special case: Open additional TOCI tag: - BeginTag( vcl::PDFWriter::TOCI, aTOCIString ); + BeginTagImpl(nullptr, vcl::pdf::StructElement::TOCI, aTOCIString); } } } @@ -1165,7 +1687,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // TabFrame: Table - nPDFType = vcl::PDFWriter::Table; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Table); aPDFType = aTableString; { @@ -1173,7 +1695,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() ) @@ -1219,12 +1741,12 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() ) { - nPDFType = vcl::PDFWriter::TableRow; + nPDFType = sal_uInt16(vcl::pdf::StructElement::TableRow); aPDFType = aTRString; } else { - nPDFType = vcl::PDFWriter::NonStructElement; + nPDFType = sal_uInt16(vcl::pdf::StructElement::NonStructElement); } break; @@ -1236,12 +1758,12 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame(); if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) ) { - nPDFType = vcl::PDFWriter::TableHeader; + nPDFType = sal_uInt16(vcl::pdf::StructElement::TableHeader); aPDFType = aTHString; } else { - nPDFType = vcl::PDFWriter::TableData; + nPDFType = sal_uInt16(vcl::pdf::StructElement::TableData); aPDFType = aTDString; } } @@ -1255,9 +1777,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 = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::NonStructElement); + } + else if (pFly->Lower() && pFly->Lower()->IsNoTextFrame()) { bool bFormula = false; @@ -1274,18 +1807,18 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() } if ( bFormula ) { - nPDFType = vcl::PDFWriter::Formula; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Formula); aPDFType = aFormulaString; } else { - nPDFType = vcl::PDFWriter::Figure; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Figure); aPDFType = aFigureString; } } else { - nPDFType = vcl::PDFWriter::Division; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Division); aPDFType = aDivString; } } @@ -1296,12 +1829,26 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() if ( USHRT_MAX != nPDFType ) { - BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType ); + BeginTag(vcl::pdf::StructElement(nPDFType), aPDFType ); } } void SwTaggedPDFHelper::EndStructureElements() { + if (mpFrameInfo != nullptr) + { + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) + { // close span at end of paragraph + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); + ++m_nEndStructureElement; + } + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + { // close link at end of paragraph + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); + ++m_nEndStructureElement; + } + } + while ( m_nEndStructureElement > 0 ) { EndTag(); @@ -1311,6 +1858,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,63 +1966,120 @@ 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); + + ProgName 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.toString(), pInetFormatAttr)); + sal_uInt16 nPDFType = USHRT_MAX; OUString aPDFType; switch ( pPor->GetWhichPor() ) { + case PortionType::PostIts: + if (!mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.empty()) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Annot); + aPDFType = aAnnotString; + } + break; + case PortionType::Hyphen : case PortionType::SoftHyphen : // Check for alternative spelling: case PortionType::HyphenStr : case PortionType::SoftHyphenStr : - nPDFType = vcl::PDFWriter::Span; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Span); aPDFType = aSpanString; break; + case PortionType::Fly: + // if a link is split by a fly overlap, then there will be multiple + // annotations for the link, and hence there must be multiple SEs, + // so every annotation has its own SE. + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + { + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); + EndTag(); + } + break; + case PortionType::Lay : case PortionType::Text : case PortionType::Para : { - 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 = sal_uInt16(vcl::pdf::StructElement::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; + } + } + // Emphasis + else if (sStyleName == constEmphasisStyleName) + { + if (!isContinueSpan) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Emphasis); + aPDFType = constEmphasisStyleName; + CreateCurrentSpan(rInf, sStyleName.toString()); + } + } + // Strong + else if (sStyleName == constStrongEmphasisStyleName) + { + if (!isContinueSpan) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Strong); + aPDFType = constStrongEmphasisStyleName; + CreateCurrentSpan(rInf, sStyleName.toString()); + } } // Check for Quote/Code character style: else if (sStyleName == aQuotation) { - nPDFType = vcl::PDFWriter::Quote; - aPDFType = aQuoteString; + if (!isContinueSpan) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Quote); + aPDFType = aQuoteString; + CreateCurrentSpan(rInf, sStyleName.toString()); + } } else if (sStyleName == aSourceText) { - nPDFType = vcl::PDFWriter::Code; - aPDFType = aCodeString; + if (!isContinueSpan) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Code); + aPDFType = aCodeString; + CreateCurrentSpan(rInf, sStyleName.toString()); + } } - 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() || @@ -1389,18 +2090,19 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() nCurrentLanguage != nDefaultLang || !sStyleName.isEmpty()) { - nPDFType = vcl::PDFWriter::Span; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Span); if (!sStyleName.isEmpty()) - aPDFType = sStyleName; + aPDFType = sStyleName.toString(); else aPDFType = aSpanString; + CreateCurrentSpan(rInf, sStyleName.toString()); } } } break; case PortionType::Footnote : - nPDFType = vcl::PDFWriter::Link; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Link); aPDFType = aLinkString; break; @@ -1416,30 +2118,96 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() const SwField* pField = pHint->GetFormatField().GetField(); if ( SwFieldIds::GetRef == pField->Which() ) { - nPDFType = vcl::PDFWriter::Link; + nPDFType = sal_uInt16(vcl::pdf::StructElement::Link); aPDFType = aLinkString; } else if ( SwFieldIds::TableOfAuthorities == pField->Which() ) { - nPDFType = vcl::PDFWriter::BibEntry; + nPDFType = sal_uInt16(vcl::pdf::StructElement::BibEntry); aPDFType = aBibEntryString; } } } 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 = sal_uInt16(vcl::pdf::StructElement::Ruby); + aPDFType = "Ruby"; + break; + case 1: + nPDFType = sal_uInt16(vcl::pdf::StructElement::RT); + aPDFType = "RT"; + break; + case 2: + nPDFType = sal_uInt16(vcl::pdf::StructElement::RB); + aPDFType = "RB"; + break; + } + } + else if (pMulti->IsDouble()) + { + EndCurrentAll(); + switch (mpPorInfo->m_Mode) + { + case 0: + nPDFType = sal_uInt16(vcl::pdf::StructElement::Warichu); + aPDFType = "Warichu"; + break; + case 1: + nPDFType = sal_uInt16(vcl::pdf::StructElement::WP); + aPDFType = "WP"; + break; + case 2: + nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::LILabel); + aPDFType = aListLabelString; + } + break; + + case PortionType::Tab : case PortionType::TabRight : case PortionType::TabCenter : case PortionType::TabDecimal : - nPDFType = vcl::PDFWriter::NonStructElement; + nPDFType = sal_uInt16(vcl::pdf::StructElement::NonStructElement); break; default: break; } if ( USHRT_MAX != nPDFType ) { - BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType ); + BeginTag( static_cast<vcl::pdf::StructElement>(nPDFType), aPDFType ); } } @@ -1481,27 +2249,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,14 +2274,26 @@ 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; + } + return MapSwRectToPDFRect(pCurrPage, rRectangle); +} + +double SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale() +{ + return 0.75; +} + +tools::Rectangle SwEnhancedPDFExportHelper::MapSwRectToPDFRect(const SwPageFrame* pCurrPage, + const tools::Rectangle& rRectangle) +{ //the page has been scaled by 75% and vertically centered, so adjust these //rectangles equivalently tools::Rectangle aRect(rRectangle); Size aRectSize(aRect.GetSize()); - double fScale = 0.75; + double fScale = GetSwRectToPDFRectScale(); aRectSize.setWidth( aRectSize.Width() * fScale ); aRectSize.setHeight( aRectSize.Height() * fScale ); tools::Long nOrigHeight = pCurrPage->getFrameArea().Height(); @@ -1531,7 +2306,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 +2316,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 +2336,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() if ( !mbEditEngineOnly ) { + assert(pPDFExtOutDevData->GetSwPDFState() == nullptr); + pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault)); // POSTITS @@ -1592,15 +2369,38 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() const Color* pColor; pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor); - vcl::PDFNote aNote; + vcl::pdf::PDFNote aNote; // The title should consist of the author and the date: - aNote.Title = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : ""); + aNote.maTitle = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : u""_ustr); // Guess what the contents contains... - aNote.Contents = pField->GetText(); + aNote.maContents = pField->GetText(); + + tools::Rectangle aPopupRect(0, 0); + SwPostItMgr* pPostItMgr = pDoc->GetEditShell()->GetPostItMgr(); + for (auto it = pPostItMgr->begin(); it != pPostItMgr->end(); ++it) + { + sw::annotation::SwAnnotationWin* pWin = it->get()->mpPostIt; + if (pWin) + { + const SwRect& aAnnotRect = pWin->GetAnchorRect(); + if (aAnnotRect.Contains(rNoteRect)) + { + Point aPt(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetPosPixel())); + Size aSize(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetSizePixel())); + aPopupRect = tools::Rectangle(aPt, aSize); + } + } + } // Link Export tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect())); - pPDFExtOutDevData->CreateNote(aRect, aNote, aNotePageNum); + sal_Int32 nNoteId = pPDFExtOutDevData->CreateNote(aRect, aNote, aPopupRect, aNotePageNum); + + if (mrPrintData.GetPrintPostIts() != SwPostItMode::InMargins) + { + const IdMapEntry aNoteEntry(aRect, nNoteId); + pPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.push_back(aNoteEntry); + } } mrSh.SwCursorShell::ClearMark(); } @@ -1623,10 +2423,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,28 +2435,28 @@ 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 altText(p->rINetAttr.GetINetFormat().GetName()); 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(); - if (! JumpToSwMark( &mrSh, aURL )) + if (! JumpToSwMark( &mrSh, SwMarkName(aURL) )) { continue; // target deleted } @@ -1678,11 +2478,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 +2498,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,29 +2523,28 @@ 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(); - if (! JumpToSwMark( &mrSh, aURL )) + if (! JumpToSwMark( &mrSh, SwMarkName(aURL) )) { continue; // target deleted } @@ -1767,11 +2565,11 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() } } - if ( !bIntern || -1 != nDestId ) + if ( !bInternal || -1 != nDestId ) { Point aNullPt; const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt ); - + UIName const linkName(pItem->GetName()); // Link PageNums std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect ); @@ -1780,10 +2578,14 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() { tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); const sal_Int32 nLinkId = - pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + pPDFExtOutDevData->CreateLink(aRect, linkName.toString(), 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 +2594,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, linkName.toString()); } } } @@ -1808,7 +2610,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,20 +2619,31 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape") { uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY); + OUString title; + xShapePropSet->getPropertyValue(u"Title"_ustr) >>= title; + OUString description; + xShapePropSet->getPropertyValue(u"Description"_ustr) >>= 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; + xShapePropSet->getPropertyValue(u"MediaURL"_ustr) >>= aMediaURL; if (!aMediaURL.isEmpty()) { + OUString const mimeType(xShapePropSet->getPropertyValue(u"MediaMimeType"_ustr).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. OUString aTempFileURL; - xShapePropSet->getPropertyValue("PrivateTempFileURL") >>= aTempFileURL; + xShapePropSet->getPropertyValue(u"PrivateTempFileURL"_ustr) >>= aTempFileURL; pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL); } else @@ -1858,19 +2671,18 @@ 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(); // Destination Rectangle const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField()); - const OUString& rRefName = pField->GetSetRefName(); - mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo() ); + const SwMarkName& rRefName = pField->GetSetRefName(); + mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() ); const SwRect& rDestRect = mrSh.GetCharRect(); const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); @@ -1885,8 +2697,7 @@ 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 ); // Create links for all selected rectangles: const size_t nNumOfRects = aTmp.size(); @@ -1903,11 +2714,11 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Link Export aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()); const sal_Int32 nLinkId = - pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + pPDFExtOutDevData->CreateLink(aRect, rRefName.toString(), 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 +2726,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // #i44368# Links in Header/Footer if ( bHeaderFooter ) { - MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, "", true ); + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, u""_ustr, true, rRefName.toString()); } } } @@ -1935,12 +2746,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 +2761,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 +2779,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, numStrRef, 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, numStrSymbol, 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 ); } } } @@ -2013,13 +2845,21 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() std::stack< StackEntry > aOutlineStack; aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value + // outlines inside flys (text frames) collected before the normal + // outlines by GetOutLineNds(), so store them with page/position data + // to insert later on the right page and position: + // tuple< nDestPageNum, rDestRect, nLevel, rEntry, nDestId > + typedef std::tuple< sal_Int32, SwRect, sal_Int32, const OUString, sal_Int32 > FlyEntry; + std::vector< FlyEntry > aFlyVector; + sal_Int32 nStartFly = 0; // first not processed item in aFlyVector + const SwOutlineNodes::size_type nOutlineCount = mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i ) { // Check if outline is hidden const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode(); - OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + assert(pTNd && "Enhanced pdf export - text node is missing"); if ( pTNd->IsHidden() || !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) || @@ -2027,6 +2867,14 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() pTNd->GetText().isEmpty()) continue; + // Check if outline is inside a text frame + bool bFlyOutline = pTNd->GetFlyFormat(); + + // save outline stack to use for postponed fly outlines + std::stack< StackEntry > aSavedOutlineStack; + if ( !aFlyVector.empty() && !bFlyOutline ) + aSavedOutlineStack = aOutlineStack; + // Get parent id from stack: const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i )); sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first; @@ -2056,17 +2904,72 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); // Outline entry text - const OUString& rEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText( + const OUString aEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText( i, mrSh.GetLayout(), true, false, false ); + // postpone fly outlines + if ( bFlyOutline ) + { + aFlyVector.push_back( + FlyEntry(nDestPageNum, rDestRect, nLevel, aEntry, nDestId) ); + continue; + } + + // create new outline items from postponed fly outlines, if they are before + // the recent not fly outline (and after the already created fly outlines) + for (size_t j = nStartFly; j < aFlyVector.size(); ++j) + { + if ( std::get<0>(aFlyVector[j]) < nDestPageNum || + ( std::get<0>(aFlyVector[j]) == nDestPageNum && + std::get<1>(aFlyVector[j]).Pos().Y() < rDestRect.Pos().Y() ) ) + { + sal_Int32 nFlyLevel = std::get<2>(aFlyVector[j]); + sal_Int8 nLevelOnTopOfSavedStack = aSavedOutlineStack.top().first; + while ( nLevelOnTopOfSavedStack >= nFlyLevel && + nLevelOnTopOfSavedStack != -1 ) + { + aSavedOutlineStack.pop(); + nLevelOnTopOfSavedStack = aSavedOutlineStack.top().first; + } + const sal_Int32 nFlyParent = aSavedOutlineStack.top().second; + const sal_Int32 nId = pPDFExtOutDevData->CreateOutlineItem( nFlyParent, + std::get<3>(aFlyVector[j]), + std::get<4>(aFlyVector[j]) ); + // Push current level and outline id on saved stack: + aSavedOutlineStack.push( StackEntry( nFlyLevel, nId ) ); + ++nStartFly; + } + else + break; + } + // Create a new outline item: const sal_Int32 nOutlineId = - pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, nDestId ); + pPDFExtOutDevData->CreateOutlineItem( nParent, aEntry, nDestId ); // Push current level and nOutlineId on stack: aOutlineStack.push( StackEntry( nLevel, nOutlineId ) ); } } + + // create remaining fly outlines + for (size_t j = nStartFly; j < aFlyVector.size(); ++j) + { + sal_Int32 nLevel = std::get<2>(aFlyVector[j]); + sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first; + while ( nLevelOnTopOfStack >= nLevel && + nLevelOnTopOfStack != -1 ) + { + aOutlineStack.pop(); + nLevelOnTopOfStack = aOutlineStack.top().first; + } + const sal_Int32 nParent = aOutlineStack.top().second; + + const sal_Int32 nOutlineId = pPDFExtOutDevData->CreateOutlineItem( nParent, + std::get<3>(aFlyVector[j]), + std::get<4>(aFlyVector[j]) ); + aOutlineStack.push( StackEntry( std::get<2>(aFlyVector[j]), nOutlineId ) ); + } } if( pPDFExtOutDevData->GetIsExportNamedDestinations() ) @@ -2077,14 +2980,14 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // 1. a name for the destination, formed from the standard OOo bookmark name // 2. the destination, obtained from where the bookmark destination lies IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess(); - for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin(); + for(auto ppMark = pMarkAccess->getBookmarksBegin(); ppMark != pMarkAccess->getBookmarksEnd(); ++ppMark) { //get the name - const ::sw::mark::IMark* pBkmk = *ppMark; + const ::sw::mark::MarkBase* pBkmk = *ppMark; mrSh.SwCursorShell::ClearMark(); - const OUString& sBkName = pBkmk->GetName(); + const SwMarkName& sBkName = pBkmk->GetName(); //jump to it if (! JumpToSwMark( &mrSh, sBkName )) @@ -2105,7 +3008,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() if ( -1 != nDestPageNum ) { tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); - pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum); + pPDFExtOutDevData->CreateNamedDest(sBkName.toString(), aRect, nDestPageNum); } } mrSh.SwCursorShell::ClearMark(); @@ -2121,11 +3024,11 @@ 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 ); + JumpToSwMark( &mrSh, SwMarkName(aBookmarkName) ); // Destination Rectangle const SwRect& rDestRect = mrSh.GetCharRect(); @@ -2157,6 +3060,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 +3079,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 +3144,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 aURL = rAuthorityField.GetAbsoluteURL(); + if (aURL.getLength() == 0) + { + continue; + } + + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } - // Select the field. - mrSh.SwCursorShell::SetMark(); - mrSh.SwCursorShell::Right(1, CRSR_SKIP_CHARS); + OUString const content(rAuthorityField.GetAuthority(mrSh.GetLayout())); - // Create the links. - for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_()) - { - for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + // 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, aURL); + } } + 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; - mrSh.SwCursorShell::ClearMark(); + std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings; + for (const auto& rDestinationTuple : vDestinations) + { + if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple)) + == vFormattedFieldStrings.end()) + vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple), + rAuthorityField.GetAuthority(mrSh.GetLayout(), + &std::get<0>(rDestinationTuple)->GetTOXForm())); + + if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple)) + { + nDestId = std::get<2>(rDestinationTuple); + break; + } + } + + if (nDestId == -1) + continue; + + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } + + OUString const content(rAuthorityField.GetAuthority(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 +3310,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 +3319,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 ); + // 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); - - // 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..6330857deb18 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 46 -#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..1af5e50d8550 100644 --- a/sw/source/core/text/atrstck.cxx +++ b/sw/source/core/text/atrstck.cxx @@ -42,6 +42,7 @@ #include <editeng/twolinesitem.hxx> #include <editeng/charhiddenitem.hxx> #include <editeng/boxitem.hxx> +#include <editeng/nhypitem.hxx> #include <editeng/shaditem.hxx> #include <viewopt.hxx> #include <charfmt.hxx> @@ -60,9 +61,10 @@ * stack, the top most attribute on the stack is valid. Because some * kinds of attributes have to be pushed to the same stacks we map their * ids to stack ids - * Attention: The first NUM_DEFAULT_VALUES ( defined in swfntcch.hxx ) + * Attention: Stacks for character attributes (RES_CHRATR_*) * are stored in the defaultitem-cache, if you add one, you have to increase - * NUM_DEFAULT_VALUES. + * NUM_DEFAULT_VALUES (defined in swfntcch.hxx), and ensure your new stack id + * is below this number. * Also adjust NUM_ATTRIBUTE_STACKS in atrhndl.hxx. */ const sal_uInt8 StackPos[ RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN + 1 ] = @@ -112,17 +114,19 @@ const sal_uInt8 StackPos[ RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN + 1 ] = 38, // RES_CHRATR_HIGHLIGHT, // 42 0, // RES_CHRATR_GRABBAG, // 43 0, // RES_CHRATR_BIDIRTL, // 44 - 0, // RES_CHRATR_IDCTHINT, // 45 - 39, // RES_TXTATR_REFMARK, // 46 - 40, // RES_TXTATR_TOXMARK, // 47 - 41, // RES_TXTATR_META, // 48 - 41, // RES_TXTATR_METAFIELD, // 49 - 0, // RES_TXTATR_AUTOFMT, // 50 - 0, // RES_TXTATR_INETFMT // 51 - 0, // RES_TXTATR_CHARFMT, // 52 - 42, // RES_TXTATR_CJK_RUBY, // 53 - 0, // RES_TXTATR_UNKNOWN_CONTAINER, // 54 - 43, // RES_TXTATR_INPUTFIELD // 55 + 0, // RES_CHRATR_UNUSED3, // 45 + 39, // RES_CHRATR_SCRIPT_HINT, // 46 + 40, // RES_TXTATR_REFMARK, // 47 + 41, // RES_TXTATR_TOXMARK, // 48 + 42, // RES_TXTATR_META, // 49 + 42, // RES_TXTATR_METAFIELD, // 50 + 0, // RES_TXTATR_AUTOFMT, // 51 + 0, // RES_TXTATR_INETFMT // 52 + 0, // RES_TXTATR_CHARFMT, // 53 + 43, // RES_TXTATR_CJK_RUBY, // 54 + 0, // RES_TXTATR_UNKNOWN_CONTAINER, // 55 + 44, // RES_TXTATR_INPUTFIELD // 56 + 45, // RES_TXTATR_CONTENTCONTROL // 57 }; namespace CharFormat @@ -220,9 +224,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 +240,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 +250,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 +325,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( ) @@ -344,15 +343,19 @@ void SwAttrHandler::PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) // they have to be pushed to each stack they belong to if ( RES_TXTATR_INETFMT == rAttr.Which() || RES_TXTATR_CHARFMT == rAttr.Which() || + RES_PARATR_LIST_AUTOFMT == rAttr.Which() || RES_TXTATR_AUTOFMT == rAttr.Which() ) { - const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + const SfxItemSet* pSet = rAttr.Which() == RES_PARATR_LIST_AUTOFMT + ? rAttr.GetAttr().StaticWhichCast(RES_PARATR_LIST_AUTOFMT).GetStyleHandle().get() + : CharFormat::GetItemSet( rAttr.GetAttr() ); if ( !pSet ) return; + bool const inParent{rAttr.Which() != RES_TXTATR_AUTOFMT && rAttr.Which() != RES_PARATR_LIST_AUTOFMT}; for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++) { const SfxPoolItem* pItem; - bool bRet = SfxItemState::SET == pSet->GetItemState( i, rAttr.Which() != RES_TXTATR_AUTOFMT, &pItem ); + bool bRet = SfxItemState::SET == pSet->GetItemState(i, inParent, &pItem); if ( bRet ) { @@ -371,6 +374,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 +447,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 +494,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 +538,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 +552,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 +567,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 @@ -615,8 +636,16 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) CharFormat::GetItem( *pTopAt, RES_CHRATR_HIDDEN ) : m_pDefaultArray[ nStackPos ]; + const sal_uInt16 nStackPos2 = StackPos[ RES_CHRATR_NOHYPHEN ]; + const SwTextAttr* pTopAt2 = GetTop(nStackPos2); + + const SfxPoolItem* pTmpItem2 = pTopAt2 ? + CharFormat::GetItem( *pTopAt2, RES_CHRATR_NOHYPHEN ) : + m_pDefaultArray[ nStackPos2 ]; + if ((m_pShell && !m_pShell->GetWin()) || - (pTmpItem && !pTmpItem->StaticWhichCast(RES_CHRATR_HIDDEN).GetValue()) ) + (pTmpItem && !pTmpItem->StaticWhichCast(RES_CHRATR_HIDDEN).GetValue()) || + (pTmpItem2 && !pTmpItem2->StaticWhichCast(RES_CHRATR_NOHYPHEN).GetValue()) ) { rFnt.SetUnderline( rItem.StaticWhichCast(RES_CHRATR_UNDERLINE).GetLineStyle() ); rFnt.SetUnderColor( rItem.StaticWhichCast(RES_CHRATR_UNDERLINE).GetColor() ); @@ -671,6 +700,19 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) case RES_CHRATR_HIGHLIGHT : rFnt.SetHighlightColor( rItem.StaticWhichCast(RES_CHRATR_HIGHLIGHT).GetColor() ); break; + case RES_CHRATR_NOHYPHEN : + if ( m_pShell && m_pShell->GetWin() && + m_pShell->GetViewOptions()->IsViewMetaChars() ) + { + if ( rItem.StaticWhichCast(RES_CHRATR_NOHYPHEN).GetValue() ) + { + rFnt.SetUnderline( LINESTYLE_DOTTED ); + rFnt.SetUnderColor( COL_LIGHTGRAY ); + } + else + ActivateTop( rFnt, RES_CHRATR_UNDERLINE ); + } + break; case RES_CHRATR_CJK_FONT : { auto& rFontItem = rItem.StaticWhichCast(RES_CHRATR_CJK_FONT); @@ -751,7 +793,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 +828,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 +857,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 +880,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..a2545c300abf 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: @@ -216,7 +216,7 @@ bool SwTextFrame::GetCharRect( SwRect& rOrig, const SwPosition &rPos, Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); SwTextNode const*const pTextNd(GetTextNodeForParaProps()); short nFirstOffset; - pTextNd->GetFirstLineOfsWithNum( nFirstOffset ); + pTextNd->GetFirstLineOfsWithNum(nFirstOffset, {}); Point aPnt2; if ( aRectFnSet.IsVert() ) @@ -339,7 +339,7 @@ bool SwTextFrame::GetCharRect( SwRect& rOrig, const SwPosition &rPos, if( bRet ) { SwPageFrame *pPage = pFrame->FindPageFrame(); - OSL_ENSURE( pPage, "Text escaped from page?" ); + assert(pPage && "Text escaped from page?"); const SwTwips nOrigTop = aRectFnSet.GetTop(rOrig); const SwTwips nPageTop = aRectFnSet.GetTop(pPage->getFrameArea()); const SwTwips nPageBott = aRectFnSet.GetBottom(pPage->getFrameArea()); @@ -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() ); @@ -697,6 +697,48 @@ bool SwTextFrame::LeftMargin(SwPaM *pPam) const return true; } +bool SwTextFrame::IsInHyphenatedWord(SwPaM *pPam, bool bSelection) const +{ + assert(GetMergedPara() || &pPam->GetPointNode() == static_cast<SwContentNode const*>(GetDep())); + + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), + SwTextCursor::IsRightMargin() ); + pFrame->GetFormatted(); + if (!IsEmpty()) + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + TextFrameIndex const nCursorPos(MapModelToViewPos(*pPam->GetPoint())); + aLine.CharCursorToLine(nCursorPos); + if ( aLine.GetCurr()->IsEndHyph() ) + { + TextFrameIndex nPos(aLine.GetStart() + aLine.GetCurr()->GetLen()); + while( nPos > nCursorPos && ' ' != aInf.GetText()[sal_Int32(nPos) - 1] ) + --nPos; + if ( nPos == nCursorPos && ( bSelection || + // without selection, the cursor must be inside the word, not before that + // to apply the character formatting, as usual + ( nPos > aLine.GetStart() && ' ' != aInf.GetText()[sal_Int32(nPos) - 1] ) ) ) + return true; + } + // the hyphenated word starts in the previous line + if ( aLine.GetStart() > TextFrameIndex(0) ) + { + TextFrameIndex nPos(aLine.GetStart()); + aLine.CharCursorToLine(nPos - TextFrameIndex(1)); + if ( aLine.GetCurr()->IsEndHyph() ) + { + while( nPos < nCursorPos && ' ' != aInf.GetText()[sal_Int32(nPos)] ) + ++nPos; + if ( nPos == nCursorPos && + ( bSelection || ' ' != aInf.GetText()[sal_Int32(nPos)] ) ) + return true; + } + } + } + return false; +} + /* * To the line end: That's the position before the last char of the line. * Exception: In the last line, it should be able to place the cursor after @@ -705,7 +747,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 +806,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 +873,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 +1193,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 +1234,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 +1242,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 +1361,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 +1460,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 +1473,19 @@ 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() + - GetTextNodeForParaProps()->GetLeftMarginWithNum(); - SwTwips nRight = rFill.Right() - rLRSpace.GetRight(); + + SwTwips nLeft = rFill.Left() + rTextLeftMargin.ResolveLeft(rFirstLine, /*metrics*/ {}) + + GetTextNodeForParaProps()->GetLeftMarginWithNum(); + SwTwips nRight = rFill.Right() - rRightMargin.ResolveRight({}); SwTwips nCenter = ( nLeft + nRight ) / 2; rRect.Left( nLeft ); if( SwFillMode::Margin == rFill.Mode() ) @@ -1475,7 +1520,7 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const SwTwips nSpace = 0; if( SwFillMode::Tab != rFill.Mode() ) { - SwDrawTextInfo aDrawInf( pSh, *pOut, " ", 0, 2 ); + SwDrawTextInfo aDrawInf( pSh, *pOut, u" "_ustr, 0, 2 ); nSpace = pFnt->GetTextSize_( aDrawInf ).Width()/2; } if( rFill.X() >= nRight ) @@ -1510,15 +1555,15 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const } else if( rFill.X() > nLeft ) { - SwTwips nTextLeft = rFill.Left() + rLRSpace.GetTextLeft() + - GetTextNodeForParaProps()->GetLeftMarginWithNum(true); + SwTwips nTextLeft = rFill.Left() + rTextLeftMargin.ResolveTextLeft({}) + + GetTextNodeForParaProps()->GetLeftMarginWithNum(true); rFill.nLineWidth += rFill.bFirstLine ? nLeft : nTextLeft; SwTwips nLeftTab; SwTwips nRightTab = nLeft; sal_uInt16 nSpaceCnt = 0; sal_uInt16 nSpaceOnlyCnt = 0; - sal_uInt16 nTabCnt = 0; sal_uInt16 nIdx = 0; + int nTabCnt = 0; do { nLeftTab = nRightTab; @@ -1536,7 +1581,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..17e55bf5ba48 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,10 @@ #include <editeng/tstpitem.hxx> #include <redline.hxx> #include <comphelper/lok.hxx> +#include <flyfrms.hxx> +#include <frmtool.hxx> +#include <layouter.hxx> +#include <fmtsrnd.hxx> // Tolerance in formatting and text output #define SLOPPY_TWIPS 5 @@ -139,9 +145,15 @@ void SwTextFrame::ValidateBodyFrame() SwSwapIfSwapped swap( this ); // See comment in ValidateFrame() - if ( !IsInFly() && !IsInTab() && - !( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() ) ) - ValidateBodyFrame_( GetUpper() ); + if ( !IsInFly() && !IsInTab()) + { + if (SwSectionFrame* pSctFrame = FindSctFrame()) + { + SwFrame* pLower = pSctFrame->Lower(); + if (pLower && !pLower->IsColumnFrame()) + ValidateBodyFrame_( GetUpper() ); + } + } } bool SwTextFrame::GetDropRect_( SwRect &rRect ) const @@ -212,7 +224,7 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) if( pPara ) { pPara->GetReformat() = SwCharRange(); - pPara->GetDelta() = 0; + pPara->SetDelta(0); } } @@ -310,7 +322,7 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) if( pPara ) { pPara->GetReformat() = SwCharRange(); - pPara->GetDelta() = 0; + pPara->SetDelta(0); } } @@ -323,7 +335,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 +349,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 +399,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 +508,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() @@ -466,14 +518,16 @@ void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit ) // We can get a bit of space in table cells, because there could be some // left through a vertical alignment to the top. // Assure that first lower in upper is the current one or is valid. - if ( IsInTab() && - ( GetUpper()->Lower() == this || - GetUpper()->Lower()->isFrameAreaDefinitionValid() ) ) + if (IsInTab()) { - tools::Long nAdd = aRectFnSet.YDiff( aRectFnSet.GetTop(GetUpper()->Lower()->getFrameArea()), - aRectFnSet.GetPrtTop(*GetUpper()) ); - OSL_ENSURE( nAdd >= 0, "Ey" ); - nRstHeight += nAdd; + SwFrame* pLower = GetUpper()->Lower(); + if ( pLower == this || (pLower && pLower->isFrameAreaDefinitionValid()) ) + { + tools::Long nAdd = aRectFnSet.YDiff( aRectFnSet.GetTop(pLower->getFrameArea()), + aRectFnSet.GetPrtTop(*GetUpper()) ); + OSL_ENSURE( nAdd >= 0, "Ey" ); + nRstHeight += nAdd; + } } // nRstHeight < 0 means that the TextFrame is located completely outside of its Upper. @@ -517,9 +571,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 +584,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 +602,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: @@ -561,24 +612,32 @@ css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips C // If it's 0, the FollowFrame is deleted. void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine, const TextFrameIndex nOffset, const TextFrameIndex nEnd, - const sal_uInt8 nMode ) + const bool bDontJoin) { SwFrameSwapper aSwapper( this, false ); // We got the rest of the text mass: Delete all Follows // DummyPortions() are a special case. - // Special cases are controlled by parameter <nMode>. - if( HasFollow() && !(nMode & 1) && nOffset == nEnd ) + 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,21 +650,47 @@ 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 ( nMode ) + 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 (bDontJoin) GetFollow()->ManipOfst(TextFrameIndex(0)); if ( CalcFollow( nNewOfst ) ) // CalcFollow only at the end, we do a SetOffset there @@ -666,16 +751,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 +789,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 +850,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 +860,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(); } @@ -816,15 +908,18 @@ bool SwTextFrame::CalcPreps() } else if ( aRectFnSet.IsVert() ) { + // Replicate the same overflow behavior that is used for horizontal portions. + SwTwips const nTmp = sw::WIDOW_MAGIC - (getFrameArea().Left() + 10000); + SwTwips nDiff = nTmp - getFrameArea().Width(); + { SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); - aFrm.Width( aFrm.Width() + aFrm.Left() ); - aFrm.Left( 0 ); + aFrm.Width(nTmp); } { SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); - aPrt.Width( aPrt.Width() + getFrameArea().Left() ); + aPrt.Width(aPrt.Width() + nDiff); } SetWidow( true ); @@ -987,7 +1082,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 +1091,107 @@ void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew ) MoveFlyInCnt( pFrame, nNew, TextFrameIndex(COMPLETE_STRING) ); } +static bool isFirstVisibleFrameInPageBody(const SwTextFrame* pFrame) +{ + const SwFrame* pBodyFrame = pFrame->FindBodyFrame(); + while (pBodyFrame && !pBodyFrame->IsPageBodyFrame()) + pBodyFrame = pBodyFrame->GetUpper()->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 == pBodyFrame) + 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()); +} + +namespace +{ +/// Determines if pFrame has at least one anchored object which is positioned against the page frame +/// and uses all space available for body text. +bool HasFullPageFly(const SwTextFrame* pFrame) +{ + const SwFrame* pBodyFrame = pFrame->FindBodyFrame(); + if (!pBodyFrame) + { + // Inside a fly frame, not interesting. + return false; + } + + const SwRect& rBodyFrameArea = pBodyFrame->getFrameArea(); + const SwSortedObjs* pDrawObjs = pFrame->GetDrawObjs(); + if (!pDrawObjs) + { + return false; + } + + for (SwAnchoredObject* pDrawObj : *pDrawObjs) + { + SwFrameFormat* pFrameFormat = pDrawObj->GetFrameFormat(); + if (pFrameFormat->GetHoriOrient().GetRelationOrient() != text::RelOrientation::PAGE_FRAME) + { + continue; + } + + if (pFrameFormat->GetVertOrient().GetRelationOrient() != text::RelOrientation::PAGE_FRAME) + { + continue; + } + + if (pFrameFormat->GetSurround().GetValue() != text::WrapTextMode::WrapTextMode_NONE) + { + // Not a case where the request is to wrap the content around the object, ignore. + continue; + } + + if (pDrawObj->GetObjRectWithSpaces().Contains(rBodyFrameArea)) + { + // Wrap is requested, but the object uses all available space: this is a full page + // object. + return true; + } + } + + return false; +} +} + void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, WidowsAndOrphans &rFrameBreak, TextFrameIndex const nStrLen, @@ -1013,36 +1209,81 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, // Call base class method <SwTextFrameBreak::IsBreakNow(..)> // instead of method <WidowsAndOrphans::IsBreakNow(..)> to get a break, // even if due to widow rule no enough lines exists. - sal_uInt8 nNew = ( !GetFollow() && + bool createNew = ( !GetFollow() && nEnd < nStrLen && ( rLine.IsStop() || ( bHasToFit ? ( rLine.GetLineNr() > 1 && !rFrameBreak.IsInside( rLine ) ) - : rFrameBreak.IsBreakNow( rLine ) ) ) ) - ? 1 : 0; + : rFrameBreak.IsBreakNow( rLine ) ) ) ); + + SwTextFormatInfo& rInf = rLine.GetInfo(); + bool bEmptyWithSplitFly = false; + if (!createNew && !nStrLen && !rInf.GetTextFly().IsOn() && IsEmptyWithSplitFly()) + { + // Empty paragraph, so IsBreakNow() is not called, but we should split the fly portion and + // the paragraph marker. + createNew = true; + 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; + (*GetDrawObjs())[0]->GetFrameFormat()->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR; - // Still try split text frame if we have columns. - if (FindColFrame()) - bOnlyContainsAsCharAnchoredObj = false; + if (bLoneAsCharAnchoredObj) + { + // 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 (!isFirstVisibleFrameInPageBody(this)) + bLoneAsCharAnchoredObj = false; + else + createNew = false; + } + else if (createNew) + { + 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)) + createNew = false; + } + 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); tdf#136040: still try split text + // frame if we have columns. + if (pBodyFrame && !FindColFrame() && isFirstVisibleFrameInPageBody(this) + && !hasFly(this) && !hasAtPageFly(pBodyFrame)) + createNew = false; + } + } - if ( nNew && bOnlyContainsAsCharAnchoredObj ) + if (createNew && nEnd == TextFrameIndex(0) && !bEmptyWithSplitFly && HasFullPageFly(this)) { - nNew = 0; + // We intended to split at start, due to an anchored object which would use all space on the + // current page. It makes no sense to split & move all text of the frame forward: the + // current page would be empty and we would move back later anyway. + createNew = false; } - if ( nNew ) + if (createNew) { SplitFrame( nEnd ); } - - const SwFrame *pBodyFrame = FindBodyFrame(); + bool dontJoin = createNew; const tools::Long nBodyHeight = pBodyFrame ? ( IsVertical() ? pBodyFrame->getFrameArea().Width() : @@ -1052,12 +1293,12 @@ 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() ) { rLine.TruncLines( true ); - nNew = 1; + dontJoin = true; } // FindBreak truncates the last line @@ -1067,10 +1308,14 @@ 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 ) + if (dontJoin && nOld < nEnd) RemoveFootnote( nOld, nEnd - nOld ); ChangeOffset( GetFollow(), nEnd ); if( !bDelta ) @@ -1082,6 +1327,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() ) { @@ -1096,7 +1342,7 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, GetFollow()->IsFieldFollow() || (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule())) { - nNew |= 3; + dontJoin = true; } else if (FindTabFrame() && nEnd > TextFrameIndex(0) && rLine.GetInfo().GetChar(nEnd - TextFrameIndex(1)) == CH_BREAK) @@ -1105,24 +1351,49 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, // ends with a hard line break. Don't join the follow just // because the follow would have no content, we may still need it // for the paragraph mark. - nNew |= 1; + dontJoin = true; + } + // 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 (dontJoin && 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 ); - nNew |= 3; + dontJoin = true; } } // If the remaining height changed e.g by RemoveFootnote() we need to @@ -1142,7 +1413,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; } @@ -1163,13 +1434,13 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, AdjustFrame( nChg, bHasToFit ); if( HasFollow() || IsInFootnote() ) - AdjustFollow_( rLine, nEnd, nStrLen, nNew ); + AdjustFollow_(rLine, nEnd, nStrLen, dontJoin); pPara->SetPrepMustFit( false ); } // 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 +1448,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; @@ -1267,17 +1538,25 @@ bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev ) rRepaint.SetRightOfst( nRght ); // Finally we enlarge the repaint rectangle if we found an underscore - // within our line. 40 Twips should be enough - const bool bHasUnderscore = - ( rLine.GetInfo().GetUnderScorePos() < nNewStart ); - if ( bHasUnderscore || rLine.GetCurr()->HasUnderscore() ) - rRepaint.Bottom( rRepaint.Bottom() + 40 ); - - const_cast<SwLineLayout*>(rLine.GetCurr())->SetUnderscore( bHasUnderscore ); + // or another glyph extending beyond the line height within the line. + auto nBaseAscent = pNew->GetAscent(); + auto nMaxExtraAscent + = std::max({ SwTwips{ 0 }, rLine.GetInfo().GetExtraAscent() - nBaseAscent, + rLine.GetCurr()->GetExtraAscent() }); + rRepaint.Top(rRepaint.Top() - nMaxExtraAscent); + const_cast<SwLineLayout*>(rLine.GetCurr())->SetExtraAscent(nMaxExtraAscent); + + auto nBaseDescent = pNew->Height() - pNew->GetAscent(); + auto nMaxExtraDescent + = std::max({ SwTwips{ 0 }, rLine.GetInfo().GetExtraDescent() - nBaseDescent, + rLine.GetCurr()->GetExtraDescent() }); + rRepaint.Bottom(rRepaint.Bottom() + nMaxExtraDescent); + const_cast<SwLineLayout*>(rLine.GetCurr())->SetExtraDescent(nMaxExtraDescent); } // 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 +1875,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 +1984,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 +2020,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(); @@ -1744,10 +2054,58 @@ void SwTextFrame::Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pP } } +namespace +{ +class SwTextFrameFormatScopeGuard +{ +private: + VclPtr<OutputDevice> m_pOut = nullptr; + VclPtr<OutputDevice> m_pRef = nullptr; + +public: + SwTextFrameFormatScopeGuard(OutputDevice* pOut, SwTextFrame* pFrame) + : m_pOut(pOut) + { + auto pVsh = pFrame->getRootFrame()->GetCurrShell(); + if (pVsh) + { + m_pRef = &pVsh->GetRefDev(); + } + + if (m_pOut) + { + m_pOut->Push(vcl::PushFlags::ALL); + } + + if (m_pRef) + { + m_pRef->Push(vcl::PushFlags::ALL); + } + } + + ~SwTextFrameFormatScopeGuard() + { + if (m_pRef) + { + m_pRef->Pop(); + } + + if (m_pOut) + { + m_pOut->Pop(); + } + } +}; +} + // We calculate the text frame's size and send a notification. // Shrink() or Grow() to adjust the frame's size to the changed required space. void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs * ) { + // tdf#92091: SwTextFrame::Format is re-entrant, but may change VCL global state. + // The previous state is saved here and restored after returning. + SwTextFrameFormatScopeGuard stSg{ pRenderContext, this }; + SwRectFnSet aRectFnSet(this); CalcAdditionalFirstLineOffset(); @@ -1757,9 +2115,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 +2124,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 +2141,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 +2261,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 +2275,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 +2365,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 +2494,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..386f40942f5f 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) @@ -174,14 +172,15 @@ void SwTextFrameInfo::GetSpaces( // in the selection if( aLine.GetNext() ) { - nPos = aLine.GetTextEnd(); + TextFrameIndex const nEndPos{aLine.GetTextEnd()}; - if( nPos < aLine.GetEnd() ) + // if only whitespace in line, nEndPos < nPos ! + if (nPos < nEndPos && nEndPos < aLine.GetEnd()) { TextFrameIndex const nOff( !bWithLineBreak && CH_BREAK == aLine.GetInfo().GetChar(aLine.GetEnd() - TextFrameIndex(1)) ? 1 : 0 ); - AddRange( rRanges, nPos, aLine.GetEnd() - nPos - nOff ); + AddRange(rRanges, nEndPos, aLine.GetEnd() - nEndPos - nOff); } } } diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx index 6f1d1fc12ad1..22f00a9b6391 100644 --- a/sw/source/core/text/frmpaint.cxx +++ b/sw/source/core/text/frmpaint.cxx @@ -43,6 +43,8 @@ #include <tabfrm.hxx> #include <numrule.hxx> #include <wrong.hxx> +#include <vcl/lineinfo.hxx> +#include <officecfg/Office/Writer.hxx> #include <EnhancedPDFExportHelper.hxx> @@ -68,7 +70,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,14 +84,21 @@ 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()); } void PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, bool bRed, const OUString* pRedlineText = nullptr ); - void PaintRedline( SwTwips nY, tools::Long nMax ); + void PaintRedline( SwTwips nY, tools::Long nMax, sal_Int16 nWordSpacing = 0 ); }; } @@ -128,7 +137,7 @@ SwExtraPainter::SwExtraPainter( const SwTextFrame *pFrame, SwViewShell *pVwSh, m_nDivider = !m_rLineInf.GetDivider().isEmpty() ? m_rLineInf.GetDividerCountBy() : 0; m_nX = pFrame->getFrameArea().Left(); SwCharFormat* pFormat = m_rLineInf.GetCharFormat( const_cast<IDocumentStylePoolAccess&>(pFrame->GetDoc().getIDocumentStylePoolAccess()) ); - OSL_ENSURE( pFormat, "PaintExtraData without CharFormat" ); + assert(pFormat && "PaintExtraData without CharFormat"); m_pFnt.reset( new SwFont(&pFormat->GetAttrSet(), &pFrame->GetDoc().getIDocumentSettingAccess()) ); m_pFnt->Invalidate(); m_pFnt->ChgPhysFnt( m_pSh, *m_pSh->GetOut() ); @@ -170,7 +179,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; @@ -199,7 +208,7 @@ void SwExtraPainter::PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, if ( pRedlineText ) { - m_pFnt->SetColor(NON_PRINTING_CHARACTER_COLOR); + m_pFnt->SetColor(m_pSh->GetViewOptions()->GetNonPrintingCharacterColor()); // don't strike out text in Insertions In Margin mode if ( !m_pSh->GetViewOptions()->IsShowChangesInMargin2() ) m_pFnt->SetStrikeout( STRIKEOUT_SINGLE ); @@ -248,7 +257,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; @@ -272,7 +281,8 @@ void SwExtraPainter::PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, } } -void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax ) +// paint redline or word spacing indicator +void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax, sal_Int16 nWordSpacing ) { Point aStart( m_nRedX, nY ); Point aEnd( m_nRedX, nY + nMax ); @@ -280,24 +290,27 @@ 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; m_aClip.ChgClip( m_aRect, m_pTextFrame ); } } - const Color aOldCol( m_pSh->GetOut()->GetLineColor() ); - m_pSh->GetOut()->SetLineColor( SW_MOD()->GetRedlineMarkColor() ); + m_pSh->GetOut()->Push(vcl::PushFlags::LINECOLOR); + m_pSh->GetOut()->SetLineColor(nWordSpacing ? COL_LIGHTRED : SwModule::get()->GetRedlineMarkColor()); - if ( m_pTextFrame->IsVertical() ) + if ( nWordSpacing ) { - m_pTextFrame->SwitchHorizontalToVertical( aStart ); - m_pTextFrame->SwitchHorizontalToVertical( aEnd ); - } + LineInfo aLineInfo; + aLineInfo.SetStyle(LineStyle::Solid); + aLineInfo.SetWidth( nWordSpacing * 2540/1440 ); - m_pSh->GetOut()->DrawLine( aStart, aEnd ); - m_pSh->GetOut()->SetLineColor( aOldCol ); + m_pSh->GetOut()->DrawLine( aStart, aEnd, aLineInfo ); + } + else + m_pSh->GetOut()->DrawLine( aStart, aEnd ); + m_pSh->GetOut()->Pop(); } void SwTextFrame::PaintExtraData( const SwRect &rRect ) const @@ -305,26 +318,31 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) const if( getFrameArea().Top() > rRect.Bottom() || getFrameArea().Bottom() < rRect.Top() ) return; + PaintOutlineContentVisibilityButton(); + SwDoc const& rDoc(GetDoc()); const IDocumentRedlineAccess& rIDRA = rDoc.getIDocumentRedlineAccess(); const SwLineNumberInfo &rLineInf = rDoc.GetLineNumberInfo(); const SwFormatLineNumber &rLineNum = GetAttrSet()->GetLineNumber(); bool bLineNum = !IsInTab() && rLineInf.IsPaintLineNumbers() && ( !IsInFly() || rLineInf.IsCountInFlys() ) && rLineNum.IsCount(); - sal_Int16 eHor = static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos()); + sal_Int16 eHor = static_cast<sal_Int16>(SwModule::get()->GetRedlineMarkPos()); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + bool bWordSpacingIndicator = officecfg::Office::Writer::Content::Display::ShowWordSpacingIndicator::get() + && pSh->GetViewOptions()->IsViewMetaChars(); if (eHor != text::HoriOrientation::NONE + && !bWordSpacingIndicator && (!IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags()) || getRootFrame()->IsHideRedlines())) { eHor = text::HoriOrientation::NONE; } bool bRedLine = eHor != text::HoriOrientation::NONE; - if ( !bLineNum && !bRedLine ) + if ( !bLineNum && !bRedLine && !bWordSpacingIndicator ) return; if( IsLocked() || IsHiddenNow() || !getFramePrintArea().Height() ) return; - SwViewShell *pSh = getRootFrame()->GetCurrShell(); SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); SwRect rOldRect( rRect ); @@ -369,59 +387,47 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) const tools::Long nBottom = rRect.Bottom(); - bool bNoPrtLine = 0 == GetMinPrtLine(); - if( !bNoPrtLine ) - { - while ( aLine.Y() < GetMinPrtLine() ) - { - if( ( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() ) - && !aLine.GetCurr()->IsDummy() ) - aExtra.IncLineNr(); - if( !aLine.Next() ) - break; - } - bNoPrtLine = aLine.Y() >= GetMinPrtLine(); - } const bool bIsShowChangesInMargin = pSh->GetViewOptions()->IsShowChangesInMargin(); - if( bNoPrtLine ) + do { - do + if( bNoDummy || !aLine.GetCurr()->IsDummy() ) { - if( bNoDummy || !aLine.GetCurr()->IsDummy() ) + SwTwips nExtraSpaceSize = aLine.GetCurr()->GetFirstPortion()->ExtraSpaceSize(); + if ( nExtraSpaceSize && bWordSpacingIndicator ) + aExtra.PaintRedline( aLine.Y(), aLine.GetLineHeight(), nExtraSpaceSize ); + + bool bRed = bRedLine && aLine.GetCurr()->HasRedline(); + if( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() ) { - bool bRed = bRedLine && aLine.GetCurr()->HasRedline(); - if( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() ) + bool bRedInMargin = bIsShowChangesInMargin && bRed; + bool bNum = bLineNum && ( aExtra.HasNumber() || aExtra.HasDivider() ); + if( bRedInMargin || bNum ) { - bool bRedInMargin = bIsShowChangesInMargin && bRed; - bool bNum = bLineNum && ( aExtra.HasNumber() || aExtra.HasDivider() ); - if( bRedInMargin || bNum ) + SwTwips nTmpHeight, nTmpAscent; + aLine.CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + if ( bRedInMargin ) { - sal_uInt16 nTmpHeight, nTmpAscent; - aLine.CalcAscentAndHeight( nTmpAscent, nTmpHeight ); - if ( bRedInMargin ) + const OUString* pRedlineText = aLine.GetCurr()->GetRedlineText(); + if( !pRedlineText->isEmpty() ) { - const OUString* pRedlineText = aLine.GetCurr()->GetRedlineText(); - if( !pRedlineText->isEmpty() ) - { - aExtra.PaintExtra( aLine.Y(), nTmpAscent, - nTmpHeight, bRed, pRedlineText ); - bRed = false; - bNum = false; - } - } - if ( bNum ) - { - aExtra.PaintExtra( aLine.Y(), nTmpAscent, nTmpHeight, bRed ); + aExtra.PaintExtra( aLine.Y(), nTmpAscent, + nTmpHeight, bRed, pRedlineText ); bRed = false; + bNum = false; } } - aExtra.IncLineNr(); + if ( bNum ) + { + aExtra.PaintExtra( aLine.Y(), nTmpAscent, nTmpHeight, bRed ); + bRed = false; + } } - if( bRed ) - aExtra.PaintRedline( aLine.Y(), aLine.GetLineHeight() ); + aExtra.IncLineNr(); } - } while( aLine.Next() && aLine.Y() <= nBottom ); - } + if( bRed ) + aExtra.PaintRedline( aLine.Y(), aLine.GetLineHeight() ); + } + } while( aLine.Next() && aLine.Y() <= nBottom ); } else { @@ -492,6 +498,8 @@ SwRect SwTextFrame::GetPaintSwRect() bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const { + PaintParagraphStylesHighlighting(); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); if( pSh && ( pSh->GetViewOptions()->IsParagraph() || bInitFont ) ) { @@ -530,13 +538,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(); } } @@ -561,11 +569,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.0 < rFirstLine.GetTextFirstLineOffset().m_dValue) + { + aPos.AdjustX(rFirstLine.ResolveTextFirstLineOffset({})); + } std::unique_ptr<SwSaveClip, o3tl::default_delete<SwSaveClip>> xClip; if( IsUndersized() ) @@ -592,7 +602,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 ); @@ -616,7 +627,7 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const pFnt->SetUnderline( LINESTYLE_NONE ); } - pFnt->SetColor(NON_PRINTING_CHARACTER_COLOR); + pFnt->SetColor(pSh->GetViewOptions()->GetNonPrintingCharacterColor()); pFnt->DrawText_( aDrawInf ); } } @@ -628,19 +639,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, PaintFrameMode) 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; @@ -667,6 +672,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)); @@ -740,21 +770,11 @@ void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& aLine.TwipsToLine( rRect.Top() + 1 ); tools::Long nBottom = rRect.Bottom(); - bool bNoPrtLine = 0 == GetMinPrtLine(); - if( !bNoPrtLine ) + do { - while ( aLine.Y() < GetMinPrtLine() && aLine.Next() ) - ; - bNoPrtLine = aLine.Y() >= GetMinPrtLine(); - } - if( bNoPrtLine ) - { - do - { - aLine.DrawTextLine( rRect, aClip, IsUndersized() ); + aLine.DrawTextLine(rRect, aClip, IsUndersized(), oTaggedLabel, oTaggedParagraph, isPDFTaggingEnabled); - } while( aLine.Next() && aLine.Y() <= nBottom ); - } + } while( aLine.Next() && aLine.Y() <= nBottom ); // Once is enough: if( aLine.IsPaintDrop() ) @@ -764,10 +784,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..694a74361553 100644 --- a/sw/source/core/text/guess.cxx +++ b/sw/source/core/text/guess.cxx @@ -27,10 +27,13 @@ #include <com/sun/star/i18n/BreakType.hpp> #include <com/sun/star/i18n/WordType.hpp> #include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/text/ParagraphHyphenationKeepType.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 +45,120 @@ 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 SwTextGuess::maybeAdjustPositionsForBlockAdjust(tools::Long& rMaxSizeDiff, + SwTwips& rExtraAscent, SwTwips& rExtraDescent, + const SwTextFormatInfo& rInf, const SwScriptInfo& rSI, + sal_uInt16 maxComp, + std::optional<SwLinePortionLayoutContext> nLayoutContext) +{ + 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(m_nCutPos); !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(m_nCutPos, 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 + m_nBreakStart = m_nCutPos = newCutPos; + m_nBreakPos = breakPos; + // throw away old m_xHyphWord because the current break pos is now between words + m_xHyphWord = nullptr; + + rInf.GetTextSize(&rSI, rInf.GetIdx(), breakPos - rInf.GetIdx(), nLayoutContext, maxComp, + m_nBreakWidth, rMaxSizeDiff, rExtraAscent, rExtraDescent, + rInf.GetCachedVclData().get()); + rInf.GetTextSize(&rSI, breakPos, m_nBreakStart - breakPos, nLayoutContext, maxComp, + m_nExtraBlankWidth, rMaxSizeDiff, rExtraAscent, rExtraDescent, + 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, + sal_uInt16 nPropWordSpacing, sal_Int16 nSpaceWidth ) { m_nCutPos = rInf.GetIdx(); @@ -61,50 +171,36 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, OSL_ENSURE( nPorHeight, "+SwTextGuess::Guess: no height" ); - sal_uInt16 nMaxSizeDiff; + tools::Long nMaxSizeDiff; + SwTwips nExtraAscent = 0; + SwTwips nExtraDescent = 0; const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); - sal_uInt16 nMaxComp = ( SwFontScript::CJK == rInf.GetFont()->GetActual() ) && - rSI.CountCompChg() && - ! rInf.IsMulti() && - ! rPor.InFieldGrp() && - ! rPor.IsDropPortion() ? - 10000 : - 0 ; + const sal_uInt16 nMaxComp = rPor.GetMaxComp(rInf); SwTwips nLineWidth = rInf.GetLineWidth(); TextFrameIndex nMaxLen = TextFrameIndex(rInf.GetText().getLength()) - rInf.GetIdx(); - const SvxAdjust& rAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust(); + SvxAdjustItem aAdjustItem = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust(); + const SvxAdjust aAdjust = aAdjustItem.GetAdjust(); + // Maximum word spacing allows bigger spaces to limit hyphenation, + // implement it based on the hyphenation zone: calculate a hyphenation zone + // from maximum word spacing and space count of the line + SwTwips nWordSpacingMaximumZone = 0; - // tdf#104668 space chars at the end should be cut if the compatibility option is enabled - // for LTR mode only - if ( !rInf.GetTextFrame()->IsRightToLeft() ) + if ( nSpacesInLine ) { - if (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( - DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS)) + SwTwips nExtraSpace = nSpacesInLine * nSpaceWidth/10.0 * (1.0 - nPropWordSpacing / 100.0); + nLineWidth += nExtraSpace; + // convert maximum word spacing to hyphenation zone, if defined + if ( nPropWordSpacing == aAdjustItem.GetPropWordSpacing() ) { - 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; - } - } + SwTwips nMaxDif = aAdjustItem.GetPropWordSpacingMaximum() - nPropWordSpacing; + nWordSpacingMaximumZone = nSpacesInLine * nSpaceWidth/10.0 * nMaxDif / 100.0; } + + rInf.SetExtraSpace(nExtraSpace); } if ( rInf.GetLen() < nMaxLen ) @@ -123,7 +219,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, { SwTextGridItem const*const pGrid( GetGridItem(rInf.GetTextFrame()->FindPageFrame())); - bAddItalic = !pGrid || GRID_LINES_CHARS != pGrid->GetGridType(); + bAddItalic = !pGrid || SwTextGrid::LinesAndChars != pGrid->GetGridType(); } // do not add extra italic value for an isolated blank: @@ -163,31 +259,57 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, ( bUnbreakableNumberings && rPor.IsNumberPortion() ) ) { // call GetTextSize with maximum compression (for kanas) - rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen, - nMaxComp, m_nBreakWidth, nMaxSizeDiff ); + rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rInf.GetLayoutContext(), nMaxComp, + m_nBreakWidth, nMaxSizeDiff, nExtraAscent, nExtraDescent); if ( ( m_nBreakWidth <= nLineWidth ) || ( bUnbreakableNumberings && rPor.IsNumberPortion() ) ) { // portion fits to line m_nCutPos = rInf.GetIdx() + nMaxLen; + bool bRet = rPor.InFieldGrp() + || maybeAdjustPositionsForBlockAdjust( + nMaxSizeDiff, nExtraAscent, nExtraDescent, rInf, + rSI, nMaxComp, rInf.GetLayoutContext()); 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 ) rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff ); + rInf.SetExtraAscent(nExtraAscent); + rInf.SetExtraDescent(nExtraDescent); + m_nBreakWidth += nLeftRightBorderSpace; - return true; + return bRet; } } - bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud(); + bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud() && + // disable hyphenation at minimum word spacing + // (and at weighted word spacing between minimum and desired word spacing) + !( nPropWordSpacing < aAdjustItem.GetPropWordSpacing() ); + + // disable hyphenation according to hyphenation-keep and hyphenation-keep-type, + // or modify hyphenation according to hyphenation-zone-column/page/spread (see widorp.cxx) + sal_Int16 nEndZone = 0; + if ( bHyph && + rInf.GetTextFrame()->GetNoHyphOffset() != TextFrameIndex(COMPLETE_STRING) && // ) // && + // when there is a portion in the last line, rInf.GetIdx() > GetNoHyphOffset() + rInf.GetTextFrame()->GetNoHyphOffset() <= rInf.GetIdx() ) + { + nEndZone = rInf.GetTextFrame()->GetNoHyphEndZone(); + // disable hyphenation in the last line, when hyphenation-keep-line is enabled + // and hyphenation-keep, too (i.e. when end zone is not defined), + // also when the end zone is bigger, than the line width + if ( nEndZone < 0 || nEndZone >= nLineWidth ) + bHyph = false; + } TextFrameIndex nHyphPos(0); // nCutPos is the first character not fitting to the current line @@ -195,8 +317,181 @@ 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; + sal_Int32 nParaZone = -1; + const css::beans::PropertyValues & rHyphValues = rInf.GetHyphValues(); + assert( rHyphValues.getLength() > 10 && rHyphValues[5].Name == UPN_HYPH_ZONE && rHyphValues[10].Name == UPN_HYPH_ZONE_ALWAYS ); + // hyphenation zone (distance from the line end in twips) + sal_Int16 nTextHyphenZone = 0; + sal_Int16 nTextHyphenZoneAlways = 0; + rHyphValues[5].Value >>= nTextHyphenZone; + + // maximum word spacing can result bigger hyphenation zone, + // if there are enough spaces in the line: apply that + if ( nWordSpacingMaximumZone > nTextHyphenZone ) + nTextHyphenZone = nWordSpacingMaximumZone; + + rHyphValues[10].Value >>= nTextHyphenZoneAlways; + if ( nTextHyphenZone || nTextHyphenZoneAlways || nEndZone ) + { + nHyphZone = nTextHyphenZone >= nLineWidth + ? 0 + : sal_Int32(rInf.GetTextBreak( nLineWidth - (nEndZone ? nEndZone : nTextHyphenZone), + nMaxLen, nMaxComp, rInf.GetCachedVclData().get() )); + } m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, nHyphPos, rInf.GetCachedVclData().get() ); + if ( !nEndZone && nTextHyphenZoneAlways && + // if paragraph end zone is not different from the hyphenation zone, skip its handling + nTextHyphenZoneAlways != nTextHyphenZone && + // end of the paragraph + rInf.GetText().getLength() - sal_Int32(nHyphZone) < + sal_Int32(m_nCutPos) - sal_Int32(rInf.GetLineStart() ) ) + { + nParaZone = nTextHyphenZoneAlways >= nLineWidth + ? 0 + : sal_Int32(rInf.GetTextBreak( nLineWidth - nTextHyphenZoneAlways, + nMaxLen, nMaxComp, rInf.GetCachedVclData().get() )); + } + + // don't try to hyphenate in the hyphenation zone or in the paragraph end zone + // maximum end zone is the lower non-negative text break position + // (-1 means that no hyphenation zone is defined) + sal_Int32 nMaxZone = nParaZone > -1 && nParaZone < nHyphZone + ? nParaZone + : nHyphZone; + if ( nMaxZone != -1 && TextFrameIndex(COMPLETE_STRING) != m_nCutPos ) + { + sal_Int32 nZonePos = sal_Int32(m_nCutPos); + // disable hyphenation, if there is a space within the hyphenation or end zones + // 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.GetLineStart()) <= nZonePos && nMaxZone <= nZonePos; --nZonePos ) + { + sal_Unicode cChar = rInf.GetText()[nZonePos]; + if ( cChar == CH_BLANK || cChar == CH_FULL_BLANK || cChar == CH_SIX_PER_EM ) + { + // no column/page/spread/end zone (!nEndZone), + // but is it good for a paragraph end zone? + if ( nParaZone != -1 && nParaZone <= nZonePos && + // still end of the paragraph, i.e. still more characters in the original + // last full line, then in the planned last paragraph line + // FIXME: guarantee, that last not full line won't become a full line + rInf.GetText().getLength() - sal_Int32(nZonePos) < + sal_Int32(m_nCutPos) - sal_Int32(rInf.GetLineStart() ) ) + { + bHyph = false; + } + // otherwise disable hyphenation, if there is a space in the hyphenation zone + else if ( nHyphZone != 1 && nHyphZone <= nZonePos ) + { + bHyph = false; + } + // set applied end zone + if ( !bHyph && nEndZone ) + rInf.GetTextFrame()->SetNoHyphOffset(TextFrameIndex(COMPLETE_STRING)); + } + } + } + + 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 of the line + sal_Int32 nLastWord = rInf.GetText().getLength() - 1; + bool bDoNotHyphenateLastLine = false; // don't hyphenate last full line of the paragraph + bool bHyphenationNoLastWord = false; // do not hyphenate the last word of the paragraph + assert( rHyphValues.getLength() > 3 && rHyphValues[3].Name == UPN_HYPH_NO_LAST_WORD ); + assert( rHyphValues.getLength() > 6 && rHyphValues[6].Name == UPN_HYPH_KEEP_TYPE ); + assert( rHyphValues.getLength() > 8 && rHyphValues[8].Name == UPN_HYPH_KEEP ); + rHyphValues[3].Value >>= bHyphenationNoLastWord; + rHyphValues[8].Value >>= bDoNotHyphenateLastLine; + if ( bDoNotHyphenateLastLine ) + { + sal_Int16 nKeepType = css::text::ParagraphHyphenationKeepType::COLUMN; + rHyphValues[6].Value >>= nKeepType; + if ( nKeepType == css::text::ParagraphHyphenationKeepType::ALWAYS ) + { + if ( TextFrameIndex(COMPLETE_STRING) != m_nCutPos ) + nLastWord = sal_Int32(m_nCutPos); + } + else + bDoNotHyphenateLastLine = false; + } + + if ( bHyphenationNoLastWord || bDoNotHyphenateLastLine ) + { + // 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 line + if ( ( bHyphenationNoLastWord || bDoNotHyphenateLastLine ) && + 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.GetLineStart()) < nLastWord && + // if the case of bDoNotHyphenateLastLine == true, skip hyphenation + // only if the character length of the very last line of the paragraph + // would be still less, than the length of the recent last but one line + // with hyphenation, i.e. don't skip hyphenation, if the last paragraph + // line is already near full. + ( !bDoNotHyphenateLastLine || + // FIXME: character count is not fail-safe: remaining characters + // can exceed the line, resulting two last full paragraph lines + // with disabled hyphenation. + rInf.GetText().getLength() - sal_Int32(nLastWord) < + sal_Int32(m_nCutPos) - sal_Int32(rInf.GetLineStart() ) ) ) + { + m_nCutPos = TextFrameIndex(nLastWord); + } + } + if ( !nHyphPos && rInf.GetIdx() ) nHyphPos = rInf.GetIdx() - TextFrameIndex(1); } @@ -207,9 +502,9 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, #if OSL_DEBUG_LEVEL > 1 if ( TextFrameIndex(COMPLETE_STRING) != m_nCutPos ) { - sal_uInt16 nMinSize; - rInf.GetTextSize( &rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(), - nMaxComp, nMinSize, nMaxSizeDiff ); + SwTwips nMinSize; + rInf.GetTextSize(&rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(), std::nullopt, nMaxComp, + nMinSize, nMaxSizeDiff, nExtraAscent, nExtraDescent); OSL_ENSURE( nMinSize <= nLineWidth, "What a Guess!!!" ); } #endif @@ -219,23 +514,31 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, { // second check if everything fits to line m_nCutPos = m_nBreakPos = rInf.GetIdx() + nMaxLen - TextFrameIndex(1); - rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen, nMaxComp, - m_nBreakWidth, nMaxSizeDiff ); + rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rInf.GetLayoutContext(), nMaxComp, + m_nBreakWidth, nMaxSizeDiff, nExtraAscent, nExtraDescent); // The following comparison should always give true, otherwise // there likely has been a pixel rounding error in GetTextBreak if ( m_nBreakWidth <= nLineWidth ) { + bool bRet = rPor.InFieldGrp() + || maybeAdjustPositionsForBlockAdjust( + nMaxSizeDiff, nExtraAscent, nExtraDescent, rInf, + rSI, nMaxComp, rInf.GetLayoutContext()); + 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 ) rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff ); + rInf.SetExtraAscent(nExtraAscent); + rInf.SetExtraDescent(nExtraDescent); + m_nBreakWidth += nLeftRightBorderSpace; - return true; + return bRet; } } @@ -250,36 +553,13 @@ 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(); + // throw away old m_xHyphWord when m_nBreakStart changes + m_xHyphWord = nullptr; } else { @@ -339,12 +619,12 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, // We have to switch the current language if we have a script // change at nCutPos. Otherwise LATIN punctuation would never // be allowed to be hanging punctuation. - // NEVER call GetLang if the string has been modified!!! + // NEVER call GetLangOfChar if the string has been modified!!! LanguageType aLang = rInf.GetFont()->GetLanguage(); // If we are inside a field portion, we use a temporary string which // differs from the string at the textnode. Therefore we are not allowed - // to call the GetLang function. + // to call the GetLangOfChar function. if ( m_nCutPos && ! rPor.InFieldGrp() ) { const CharClass& rCC = GetAppCharClass(); @@ -423,6 +703,27 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, m_nBreakStart = m_nBreakPos; bHyph = BreakType::HYPHENATION == aResult.breakType; + if (bHyph) + { + LanguageType aNoHyphLang; + if (rPor.InFieldGrp()) + { + // If we are inside a field portion, we use a temporary string which + // differs from the string at the textnode. Therefore we are not allowed + // to call the GetLangOfChar function. + aNoHyphLang = LANGUAGE_DONTKNOW; + } + else + { + // allow hyphenation of the word only if it's not disabled by character formatting + aNoHyphLang = rInf.GetTextFrame()->GetLangOfChar( + TextFrameIndex( sal_Int32(m_nBreakPos) + + aResult.rHyphenatedWord->getHyphenationPos() ), + 1, true, /*bNoneIfNoHyphenation=*/true ); + } + // allow hyphenation of the word only if it's not disabled by character formatting + bHyph = aNoHyphLang != LANGUAGE_NONE; + } if (bHyph && m_nBreakPos != TextFrameIndex(COMPLETE_STRING)) { @@ -465,7 +766,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, m_nBreakPos = rInf.GetIdx() - TextFrameIndex(1); } - if( rAdjust != SvxAdjust::Left ) + if( aAdjust != SvxAdjust::Left ) { // Delete any blanks at the end of a line, but be careful: // If a field has been expanded, we do not want to delete any @@ -494,10 +795,10 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, if (m_nBreakPos > m_nCutPos && m_nBreakPos != TextFrameIndex(COMPLETE_STRING)) { const TextFrameIndex nHangingLen = m_nBreakPos - m_nCutPos; - SwPosSize aTmpSize = rInf.GetTextSize( &rSI, m_nCutPos, nHangingLen ); + SwPositiveSize 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(); } @@ -535,19 +836,30 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, if( nPorLen ) { - rInf.GetTextSize( &rSI, rInf.GetIdx(), nPorLen, - nMaxComp, m_nBreakWidth, nMaxSizeDiff, - rInf.GetCachedVclData().get() ); + rInf.GetTextSize(&rSI, rInf.GetIdx(), nPorLen, std::nullopt, nMaxComp, m_nBreakWidth, + nMaxSizeDiff, nExtraAscent, nExtraDescent, rInf.GetCachedVclData().get()); + rInf.SetBreakWidth(m_nBreakWidth); // save maximum width for later use if ( nMaxSizeDiff ) rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff ); + rInf.SetExtraAscent(nExtraAscent); + rInf.SetExtraDescent(nExtraDescent); + m_nBreakWidth += nItalic + nLeftRightBorderSpace; } else m_nBreakWidth = 0; + if (m_nBreakStart > rInf.GetIdx() + nPorLen + m_nFieldDiff) + { + rInf.GetTextSize(&rSI, rInf.GetIdx() + nPorLen, + m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff, std::nullopt, + nMaxComp, m_nExtraBlankWidth, nMaxSizeDiff, nExtraAscent, nExtraDescent, + 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..5c1d0d387b16 100644 --- a/sw/source/core/text/guess.hxx +++ b/sw/source/core/text/guess.hxx @@ -32,32 +32,38 @@ class SwTextGuess { css::uno::Reference< css::linguistic2::XHyphenatedWord > m_xHyphWord; std::unique_ptr<SwHangingPortion> m_pHanging; // for hanging punctuation - TextFrameIndex m_nCutPos; // this character doesn't fit - TextFrameIndex m_nBreakStart; // start index of word containing line break - TextFrameIndex m_nBreakPos; // start index of break position - TextFrameIndex m_nFieldDiff; // absolute positions can be wrong if we + TextFrameIndex m_nCutPos{ 0 }; // this character doesn't fit + TextFrameIndex m_nBreakStart{ 0 }; // start index of word containing line break + TextFrameIndex m_nBreakPos{ 0 }; // start index of break position + TextFrameIndex m_nFieldDiff{ 0 }; // absolute positions can be wrong if we // a field in the text has been expanded - sal_uInt16 m_nBreakWidth; // width of the broken portion + SwTwips m_nBreakWidth{ 0 }; // width of the broken portion + SwTwips m_nExtraBlankWidth{ 0 }; // width of spaces after the break public: - SwTextGuess(): m_nCutPos(0), m_nBreakStart(0), - m_nBreakPos(0), m_nFieldDiff(0), m_nBreakWidth(0) - { } + SwTextGuess() = default; // 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, + sal_uInt16 nPropWordSpacing = 100, sal_Int16 nSpaceWidth = 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; } + SwTwips BreakWidth() const { return m_nBreakWidth; } + SwTwips ExtraBlankWidth() const { return m_nExtraBlankWidth; } TextFrameIndex CutPos() const { return m_nCutPos; } TextFrameIndex BreakStart() const { return m_nBreakStart; } TextFrameIndex BreakPos() const {return m_nBreakPos; } TextFrameIndex FieldDiff() const {return m_nFieldDiff; } const css::uno::Reference< css::linguistic2::XHyphenatedWord >& HyphWord() const { return m_xHyphWord; } +private: + bool maybeAdjustPositionsForBlockAdjust(tools::Long& rMaxSizeDiff, + SwTwips& rExtraAscent, SwTwips& rExtraDescent, + const SwTextFormatInfo& rInf, const SwScriptInfo& rSI, + sal_uInt16 maxComp, + std::optional<SwLinePortionLayoutContext> nLayoutContext); }; - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index bb63a36ae157..7c85cc95a8a8 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -21,15 +21,17 @@ #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> +#include <sfx2/StylePreviewRenderer.hxx> #include <sal/log.hxx> #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,19 +40,22 @@ #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 <fmteiro.hxx> #include <IDocumentSettingAccess.hxx> #include <IDocumentDeviceAccess.hxx> #include <IDocumentMarkAccess.hxx> #include <paratr.hxx> +#include <sectfrm.hxx> #include <rootfrm.hxx> #include "inftxt.hxx" #include <noteurl.hxx> +#include "porfly.hxx" #include "porftn.hxx" #include "porrst.hxx" #include "itratr.hxx" @@ -66,13 +71,25 @@ #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 <names.hxx> +#include <com/sun/star/awt/FontSlant.hpp> using namespace ::com::sun::star; using namespace ::com::sun::star::linguistic2; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::beans; -#define CHAR_UNDERSCORE u'_' #define CHAR_LEFT_ARROW u'\x25C0' #define CHAR_RIGHT_ARROW u'\x25B6' #define CHAR_TAB u'\x2192' @@ -99,7 +116,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 +124,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 +141,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; } } @@ -137,7 +154,7 @@ void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, m_pSpace = &rAttrSet.GetLineSpacing(); m_nVertAlign = rAttrSet.GetParaVertAlign().GetValue(); - m_nDefTabStop = USHRT_MAX; + m_nDefTabStop = std::numeric_limits<SwTwips>::max(); } void SwTextInfo::CtorInitTextInfo( SwTextFrame *pFrame ) @@ -191,6 +208,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) @@ -206,6 +224,8 @@ SwTextSizeInfo::SwTextSizeInfo() , m_bForbiddenChars(false) , m_bSnapToGrid(false) , m_nDirection(0) +, m_nExtraSpace(0) +, m_nBreakWidth(0) {} SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew ) @@ -221,6 +241,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() ), @@ -235,7 +256,9 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew ) m_bScriptSpace( rNew.HasScriptSpace() ), m_bForbiddenChars( rNew.HasForbiddenChars() ), m_bSnapToGrid( rNew.SnapToGrid() ), - m_nDirection( rNew.GetDirection() ) + m_nDirection( rNew.GetDirection() ), + m_nExtraSpace( rNew.GetExtraSpace() ), + m_nBreakWidth( rNew.GetBreakWidth() ) { #if OSL_DEBUG_LEVEL > 0 ChkOutDev( *this ); @@ -247,6 +270,8 @@ void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextF { m_pKanaComp = nullptr; m_nKanaIdx = 0; + m_nExtraSpace = 0; + m_nBreakWidth = 0; m_pFrame = pFrame; CtorInitTextInfo( m_pFrame ); SwDoc const& rDoc(m_pFrame->GetDoc()); @@ -280,14 +305,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; } @@ -295,7 +320,7 @@ void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextF m_pOpt = m_pVsh ? m_pVsh->GetViewOptions() : - SW_MOD()->GetViewOption(rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)); // Options from Module, due to StarONE + SwModule::get()->GetViewOption(rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)); // Options from Module, due to StarONE // bURLNotify is set if MakeGraphic prepares it // TODO: Unwind @@ -309,7 +334,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 +357,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() ), @@ -346,7 +372,9 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pTex m_bScriptSpace( rNew.HasScriptSpace() ), m_bForbiddenChars( rNew.HasForbiddenChars() ), m_bSnapToGrid( rNew.SnapToGrid() ), - m_nDirection( rNew.GetDirection() ) + m_nDirection( rNew.GetDirection() ), + m_nExtraSpace( rNew.GetExtraSpace() ), + m_nBreakWidth( rNew.GetBreakWidth() ) { #if OSL_DEBUG_LEVEL > 0 ChkOutDev( *this ); @@ -379,21 +407,23 @@ void SwTextSizeInfo::NoteAnimation() const "SwTextSizeInfo::NoteAnimation() changed m_pOut" ); } -SwPosSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev, +SwPositiveSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev, const SwScriptInfo* pSI, const OUString& rText, const TextFrameIndex nIndex, const TextFrameIndex nLength) const { - SwDrawTextInfo aDrawInf( m_pVsh, *pOutDev, pSI, rText, nIndex, nLength ); + SwDrawTextInfo aDrawInf(m_pVsh, *pOutDev, pSI, rText, nIndex, nLength, + /*layout context*/ std::nullopt); aDrawInf.SetFrame( m_pFrame ); aDrawInf.SetFont( m_pFnt ); aDrawInf.SetSnapToGrid( SnapToGrid() ); aDrawInf.SetKanaComp( 0 ); - return SwPosSize(m_pFnt->GetTextSize_( aDrawInf )); + return SwPositiveSize(m_pFnt->GetTextSize_( aDrawInf )); } -SwPosSize SwTextSizeInfo::GetTextSize() const +SwPositiveSize +SwTextSizeInfo::GetTextSize(std::optional<SwLinePortionLayoutContext> nLayoutContext) const { const SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); @@ -406,41 +436,47 @@ SwPosSize SwTextSizeInfo::GetTextSize() const GetKanaComp() : 0 ; - SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen ); + SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen, nLayoutContext); + aDrawInf.SetMeasureLen( m_nMeasureLen ); aDrawInf.SetFrame( m_pFrame ); aDrawInf.SetFont( m_pFnt ); aDrawInf.SetSnapToGrid( SnapToGrid() ); aDrawInf.SetKanaComp( nComp ); - return SwPosSize(m_pFnt->GetTextSize_( aDrawInf )); + return SwPositiveSize(m_pFnt->GetTextSize_( aDrawInf )); } -void SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, const TextFrameIndex nIndex, - const TextFrameIndex nLength, const sal_uInt16 nComp, - sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff, - vcl::TextLayoutCache const*const pCache) const +void SwTextSizeInfo::GetTextSize(const SwScriptInfo* pSI, const TextFrameIndex nIndex, + const TextFrameIndex nLength, + std::optional<SwLinePortionLayoutContext> nLayoutContext, + const sal_uInt16 nComp, SwTwips& nMinSize, + tools::Long& nMaxSizeDiff, SwTwips& nExtraAscent, + SwTwips& nExtraDescent, + vcl::text::TextLayoutCache const* const pCache) const { - SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength, - 0, false, pCache); + SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength, nLayoutContext, 0, + false, pCache); aDrawInf.SetFrame( m_pFrame ); aDrawInf.SetFont( m_pFnt ); aDrawInf.SetSnapToGrid( SnapToGrid() ); aDrawInf.SetKanaComp( nComp ); - SwPosSize aSize( m_pFnt->GetTextSize_( aDrawInf ) ); - nMaxSizeDiff = static_cast<sal_uInt16>(aDrawInf.GetKanaDiff()); + SwPositiveSize aSize( m_pFnt->GetTextSize_( aDrawInf ) ); + nMaxSizeDiff = aDrawInf.GetKanaDiff(); + nExtraAscent = aDrawInf.GetExtraAscent(); + nExtraDescent = aDrawInf.GetExtraDescent(); 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(); OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" ); - SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rScriptInfo, - *m_pText, GetIdx(), nMaxLen, 0, false, pCache ); + SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, &rScriptInfo, *m_pText, GetIdx(), nMaxLen, + /*layout context*/ std::nullopt, 0, false, pCache); aDrawInf.SetFrame( m_pFrame ); aDrawInf.SetFont( m_pFnt ); aDrawInf.SetSnapToGrid( SnapToGrid() ); @@ -454,14 +490,14 @@ 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(); OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" ); - SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rScriptInfo, - *m_pText, GetIdx(), nMaxLen, 0, false, pCache ); + SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, &rScriptInfo, *m_pText, GetIdx(), nMaxLen, + /*layout context*/ std::nullopt, 0, false, pCache); aDrawInf.SetFrame( m_pFrame ); aDrawInf.SetFont( m_pFnt ); aDrawInf.SetSnapToGrid( SnapToGrid() ); @@ -521,39 +557,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 { /** @@ -562,7 +565,7 @@ namespace */ class SwTransparentTextGuard { - ScopedVclPtrInstance<VirtualDevice> m_aContentVDev; + ScopedVclPtrInstance<VirtualDevice> m_aContentVDev { DeviceFormat::WITH_ALPHA }; GDIMetaFile m_aContentMetafile; MapMode m_aNewMapMode; SwRect m_aPorRect; @@ -604,7 +607,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); @@ -619,6 +622,35 @@ SwTransparentTextGuard::~SwTransparentTextGuard() } } +static bool lcl_IsFrameReadonly(SwTextFrame* pFrame) +{ + const SwFlyFrame* pFly; + const SwSection* pSection; + + if( pFrame && pFrame->IsInFly()) + { + pFly = pFrame->FindFlyFrame(); + if (pFly->GetFormat()->GetEditInReadonly().GetValue()) + { + const SwFrame* pLower = pFly->Lower(); + if (pLower && !pLower->IsNoTextFrame()) + { + return false; + } + } + } + // edit in readonly sections + else if ( pFrame && pFrame->IsInSct() && + nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) && + pSection->IsEditInReadonlyFlag() ) + { + return false; + } + + return true; +} + + void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPor, TextFrameIndex const nStart, TextFrameIndex const nLength, const bool bKern, const bool bWrong, @@ -642,18 +674,25 @@ void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPo bool bCfgIsAutoGrammar = false; SvtLinguConfig().GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bCfgIsAutoGrammar; const bool bBullet = OnWin() && GetOpt().IsBlank() && IsNoSymbol(); - const bool bTmpWrong = bWrong && OnWin() && GetOpt().IsOnlineSpell(); + bool bTmpWrong = bWrong && OnWin() && GetOpt().IsOnlineSpell(); + SfxObjectShell* pObjShell = m_pFrame->GetDoc().GetDocShell(); + if (bTmpWrong && pObjShell) + { + if (pObjShell->IsReadOnly() && lcl_IsFrameReadonly(m_pFrame)) + bTmpWrong = false; + } + const bool bTmpGrammarCheck = bGrammarCheck && OnWin() && bCfgIsAutoGrammar && GetOpt().IsOnlineSpell(); const bool bTmpSmart = bSmartTag && OnWin() && !GetOpt().IsPagePreview() && SwSmartTagMgr::Get().IsSmartTagsEnabled(); OSL_ENSURE( GetParaPortion(), "No paragraph!"); - SwDrawTextInfo aDrawInf( m_pFrame->getRootFrame()->GetCurrShell(), *m_pOut, pSI, rText, nStart, nLength, - rPor.Width(), bBullet ); + SwDrawTextInfo aDrawInf(m_pFrame->getRootFrame()->GetCurrShell(), *m_pOut, pSI, rText, nStart, + nLength, rPor.GetLayoutContext(), rPor.Width(), bBullet); 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 +720,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 +755,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() ) @@ -954,8 +995,8 @@ 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()) ); + SwTwips nOldWidth = rPor.Width(); + const_cast<SwLinePortion&>(rPor).Width(aFontSize.Width()); rTextPaintInfo.DrawText( aTmp, rPor ); const_cast<SwLinePortion&>(rPor).Width( nOldWidth ); rNonConstTextPaintInfo.SetFont( const_cast<SwFont*>(pOldFnt) ); @@ -988,7 +1029,7 @@ void SwTextPaintInfo::DrawTab( const SwLinePortion &rPor ) const const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ? CHAR_TAB_RTL : CHAR_TAB; const sal_uInt8 nOptions = DRAW_SPECIAL_OPTIONS_CENTER | DRAW_SPECIAL_OPTIONS_ROTATE; - lcl_DrawSpecial( *this, rPor, aRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions ); + lcl_DrawSpecial( *this, rPor, aRect, SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor(), cChar, nOptions ); } void SwTextPaintInfo::DrawLineBreak( const SwLinePortion &rPor ) const @@ -996,7 +1037,14 @@ void SwTextPaintInfo::DrawLineBreak( const SwLinePortion &rPor ) const if( !OnWin() ) return; - sal_uInt16 nOldWidth = rPor.Width(); + SwLineBreakClear eClear = SwLineBreakClear::NONE; + if (rPor.IsBreakPortion()) + { + const auto& rBreakPortion = static_cast<const SwBreakPortion&>(rPor); + eClear = rBreakPortion.GetClear(); + } + + SwTwips nOldWidth = rPor.Width(); const_cast<SwLinePortion&>(rPor).Width( LINE_BREAK_WIDTH ); SwRect aRect; @@ -1008,7 +1056,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, SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor(), cChar, nOptions ); + + if (eClear != SwLineBreakClear::NONE) + { + // Paint indicator if this clear is left/right/all. + m_pOut->Push(vcl::PushFlags::LINECOLOR); + m_pOut->SetLineColor(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor()); + 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 ); @@ -1054,7 +1119,7 @@ void SwTextPaintInfo::DrawPostIts( bool bScript ) const Size aSize; Point aTmp; - const sal_uInt16 nPostItsWidth = SwViewOption::GetPostItsWidth( GetOut() ); + const SwTwips nPostItsWidth = SwViewOption::GetPostItsWidth(GetOut()); const sal_uInt16 nFontHeight = m_pFnt->GetHeight( m_pVsh, *GetOut() ); const sal_uInt16 nFontAscent = m_pFnt->GetAscent( m_pVsh, *GetOut() ); @@ -1089,8 +1154,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 +1165,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 +1192,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 +1203,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(); @@ -1161,8 +1224,8 @@ void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const if(aIntersect.HasArea()) { SwPosition const aPosition(m_pFrame->MapViewToModelPos(GetIdx())); - const ::sw::mark::IMark* pFieldmark = - m_pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition); + const ::sw::mark::MarkBase* pFieldmark = + m_pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition); bool bIsStartMark = (TextFrameIndex(1) == GetLen() && CH_TXT_ATR_FIELDSTART == GetText()[sal_Int32(GetIdx())]); if(pFieldmark) { @@ -1171,12 +1234,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,175 +1271,312 @@ 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() ) + pTmpOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + + if (aFillColor == COL_TRANSPARENT) + pTmpOut->SetFillColor(); + else + pTmpOut->SetFillColor(aFillColor); + pTmpOut->SetLineColor(); + + DrawRect( aIntersect, false ); + + pTmpOut->Pop(); +} + +void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const +{ + SwRect aDrawArea; + CalcRect( rPor, &aDrawArea ); + if ( aDrawArea.HasArea() ) { - 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; + PaintCharacterBorder(*m_pFnt, aDrawArea, GetTextFrame()->IsVertical(), + GetTextFrame()->IsVertLRBT(), rPor.GetJoinBorderWithPrev(), + rPor.GetJoinBorderWithNext()); + } +} - 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 ); +namespace { - drawcontinue: +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; +} +} - if ( !draw ) - return; +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; - if ( !full ) - { - pPos = const_cast<SwLinePortion *>(&rPor); - nIdx = GetIdx(); + if (!pView->IsSpotlightCharStyles() && !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(u"CharStyleName"_ustr) >>= sCurrentCharStyle; - nLen = pPos->GetLen(); - for (TextFrameIndex i = nIdx + nLen - TextFrameIndex(1); - i >= nIdx; --i) + 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()) + { + UIName sCharStyleDisplayName = SwStyleNameMapper::GetUIName(ProgName(sCurrentCharStyle), + SwGetPoolIdFromName::ChrFmt); + if (comphelper::LibreOfficeKit::isActive()) + { + // For simplicity in kit mode, we render in the document "all styles" that exist + if (const SwCharFormat* pCharFormat = pFrame->GetDoc().FindCharFormatByName(sCharStyleDisplayName)) + { + // Do this so these are stable across views regardless of an individual + // user's selection mode in the style panel. + sCSNumberOrDF = OUString::number(pFrame->GetDoc().GetCharFormats()->GetPos(pCharFormat)); + aFillColor = ColorHash(sCharStyleDisplayName.toString()); + } + } + else + { + if (!sCharStyleDisplayName.isEmpty()) + { + StylesSpotlightColorMap& rCharStylesColorMap = pView->GetStylesSpotlightCharColorMap(); + auto it = rCharStylesColorMap.find(sCharStyleDisplayName.toString()); + if (it != rCharStylesColorMap.end()) { - 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(); + sCSNumberOrDF = OUString::number(it->second.second); + aFillColor = it->second.first; + } + } + } + } + // 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; - const_cast<SwLinePortion&>(rPor).Width( nNewWidth ); - CalcRect( rPor, nullptr, &aIntersect, true ); - const_cast<SwLinePortion&>(rPor).Width( nOldWidth ); + if (!rMap.hasPropertyByName(rPropName)) + continue; - if ( !aIntersect.HasArea() ) - { - return; - } + if (std::find(aHiddenProperties.begin(), aHiddenProperties.end(), rPropName) + != aHiddenProperties.end()) + continue; - break; - } + 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); - pTmpOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + // 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); - pTmpOut->SetFillColor(aFillColor); - pTmpOut->SetLineColor(); + // 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)); - DrawRect( aIntersect, false ); + vcl::Font aFont(pTmpOut->GetFont()); + aFont.SetOrientation(Degree10(0)); + pTmpOut->SetFont(aFont); - pTmpOut->Pop(); -} + pTmpOut->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft); + //pTmpOut->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong); -void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const -{ - SwRect aDrawArea; - CalcRect( rPor, &aDrawArea ); - if ( aDrawArea.HasArea() ) - { - PaintCharacterBorder(*m_pFnt, aDrawArea, GetTextFrame()->IsVertical(), - GetTextFrame()->IsVertLRBT(), rPor.GetJoinBorderWithPrev(), - rPor.GetJoinBorderWithNext()); + 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, + bool bKeepLine, sal_Int16 nCompoundMinLeading, sal_Int16 nTextHyphZoneAlways ) { sal_Int32 nLen = rVals.getLength(); if (0 == nLen) // yet to be initialized? { - rVals.realloc( 3 ); + rVals.realloc( 11 ); PropertyValue *pVal = rVals.getArray(); pVal[0].Name = UPN_HYPH_MIN_LEADING; @@ -1390,13 +1590,53 @@ 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; + + pVal[9].Name = UPN_HYPH_KEEP_LINE; + pVal[9].Handle = UPH_HYPH_KEEP_LINE; + pVal[9].Value <<= bKeepLine; + + pVal[10].Name = UPN_HYPH_ZONE_ALWAYS; + pVal[10].Handle = UPH_HYPH_ZONE_ALWAYS; + pVal[10].Value <<= nTextHyphZoneAlways; } - else if (3 == nLen) // already initialized once? + else if (11 == 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; + pVal[9].Value <<= bKeepLine; + pVal[10].Value <<= nTextHyphZoneAlways; } else { OSL_FAIL( "unexpected size of sequence" ); @@ -1405,7 +1645,7 @@ static void lcl_InitHyphValues( PropertyValues &rVals, const PropertyValues & SwTextFormatInfo::GetHyphValues() const { - OSL_ENSURE( 3 == m_aHyphVals.getLength(), + OSL_ENSURE( 11 == m_aHyphVals.getLength(), "hyphenation values not yet initialized" ); return m_aHyphVals; } @@ -1423,8 +1663,19 @@ 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 sal_Int16 nTextHyphZoneAlways = rAttr.GetTextHyphenZoneAlways(); + const bool bKeep = rAttr.IsKeep(); + const sal_Int16 nKeepType = rAttr.GetKeepType(); + const bool bKeepLine = rAttr.IsKeepLine(); + const sal_Int16 nCompoundMinimalLeading = std::max(rAttr.GetCompoundMinLead(), sal_uInt8(2)); + lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing, + bNoCapsHyphenation, bNoLastWordHyphenation, + nMinimalWordLength, nTextHyphZone, bKeep, nKeepType, + bKeepLine, nCompoundMinimalLeading, nTextHyphZoneAlways ); } return bAuto; } @@ -1450,19 +1701,20 @@ void SwTextFormatInfo::CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwT m_nFirst = 0; m_nRealWidth = 0; m_nForcedLeftMargin = 0; + m_nExtraAscent = 0; + m_nExtraDescent = 0; m_pRest = nullptr; m_nLineHeight = 0; m_nLineNetHeight = 0; SetLineStart(TextFrameIndex(0)); - SvtCTLOptions::TextNumerals const nTextNumerals( - SW_MOD()->GetCTLOptions().GetCTLTextNumerals()); + SvtCTLOptions::TextNumerals const nTextNumerals(SwModule::get()->GetCTLTextNumerals()); // cannot cache for NUMERALS_CONTEXT because we need to know the string // for the whole paragraph now if (nTextNumerals != SvtCTLOptions::NUMERALS_CONTEXT) { // set digit mode to what will be used later to get same results - SwDigitModeModifier const m(*m_pRef, LANGUAGE_NONE /*dummy*/); + SwDigitModeModifier const m(*m_pRef, LANGUAGE_NONE /*dummy*/, nTextNumerals); assert(m_pRef->GetDigitLanguage() != LANGUAGE_NONE); SetCachedVclData(OutputDevice::CreateTextLayoutCache(*m_pText)); } @@ -1483,7 +1735,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(); @@ -1499,9 +1753,9 @@ bool SwTextFormatInfo::IsHyphenate() const if (pShell) { pShell->AppendInfoBarWhenReady( - "hyphenationmissing", SwResId(STR_HYPH_MISSING), + u"hyphenationmissing"_ustr, 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); } } @@ -1546,8 +1800,9 @@ void SwTextFormatInfo::Init() m_cTabDecimal = 0; m_nWidth = m_nRealWidth; m_nForcedLeftMargin = 0; + m_nExtraAscent = 0; + m_nExtraDescent = 0; m_nSoftHyphPos = TextFrameIndex(0); - m_nUnderScorePos = TextFrameIndex(COMPLETE_STRING); m_nLastBookmarkPos = TextFrameIndex(-1); m_cHookChar = 0; SetIdx(TextFrameIndex(0)); @@ -1580,15 +1835,16 @@ SwTextFormatInfo::SwTextFormatInfo( const SwTextFormatInfo& rInf, m_pLastTab(nullptr), m_nSoftHyphPos(TextFrameIndex(0)), m_nLineStart(rInf.GetIdx()), - m_nUnderScorePos(TextFrameIndex(COMPLETE_STRING)), m_nLeft(rInf.m_nLeft), m_nRight(rInf.m_nRight), m_nFirst(rInf.m_nLeft), - m_nRealWidth(sal_uInt16(nActWidth)), + m_nRealWidth(nActWidth), m_nWidth(m_nRealWidth), m_nLineHeight(0), m_nLineNetHeight(0), m_nForcedLeftMargin(0), + m_nExtraAscent(0), + m_nExtraDescent(0), m_bFull(false), m_bFootnoteDone(true), m_bErgoDone(true), @@ -1616,9 +1872,40 @@ 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(); + const SwTwips nHeight = pCurr->GetRealHeight(); for( SwLinePortion *pPor = pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion() ) { if( pPor->IsFootnotePortion() && nHeight > static_cast<SwFootnotePortion*>(pPor)->Orig() ) @@ -1668,11 +1955,6 @@ TextFrameIndex SwTextFormatInfo::ScanPortionEnd(TextFrameIndex const nStart, m_cHookChar = cPos; return i; - case CHAR_UNDERSCORE: - if (TextFrameIndex(COMPLETE_STRING) == m_nUnderScorePos) - m_nUnderScorePos = i; - break; - default: if ( cTabDec ) { @@ -1758,7 +2040,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 +2073,17 @@ 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(); + } + else + { // tdf#158658 Put content after tab into margin like Word. + // Try to limit the paragraph to 55.87cm, it's max tab pos in Word UI. + nLineWidth = o3tl::toTwips(558, o3tl::Length::mm) - X(); + } return nLineWidth; } @@ -1803,6 +2098,7 @@ SwTextSlot::SwTextSlot( , m_pOldGrammarCheckList(nullptr) , nIdx(0) , nLen(0) + , nMeasureLen(0) , pInf(nullptr) { if( rCh.isEmpty() ) @@ -1822,11 +2118,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 +2200,7 @@ SwTextSlot::~SwTextSlot() pInf->SetText( *pOldText ); pInf->SetIdx( nIdx ); pInf->SetLen( nLen ); + pInf->SetMeasureLen( nMeasureLen ); // ST2 // Restore old smart tag list @@ -1928,7 +2229,8 @@ SwFontSave::SwFontSave(const SwTextSizeInfo &rInf, SwFont *pNew, ( ! pNew->GetBackColor() && pFnt->GetBackColor() ) || ( pNew->GetBackColor() && ! pFnt->GetBackColor() ) || ( pNew->GetBackColor() && pFnt->GetBackColor() && - ( *pNew->GetBackColor() != *pFnt->GetBackColor() ) ) ) + ( *pNew->GetBackColor() != *pFnt->GetBackColor() ) ) + || !pNew->GetActualFont().SvxFontSubsetEquals(pFnt->GetActualFont())) { pNew->SetTransparent( true ); pNew->SetAlign( ALIGN_BASELINE ); @@ -1988,4 +2290,30 @@ bool SwTextFormatInfo::CheckCurrentPosBookmark() } } +sal_Int32 SwTextFormatInfo::GetLineSpaceCount(TextFrameIndex nBreakPos) +{ + if ( sal_Int32(nBreakPos) >= GetText().getLength() ) + return 0; + + sal_Int32 nSpaces = 0; + sal_Int32 nInlineSpaces = -1; + for (sal_Int32 i = sal_Int32(GetLineStart()); i < sal_Int32(nBreakPos); ++i) + { + sal_Unicode cChar = GetText()[i]; + if ( cChar == CH_BLANK ) + ++nSpaces; + else + { + if ( nInlineSpaces == -1 ) + { + nInlineSpaces = 0; + nSpaces = 0; + } + else + nInlineSpaces = nSpaces; + } + } + return nInlineSpaces == -1 ? 0: nInlineSpaces; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx index bb245d5b031f..4792ce8399d1 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> @@ -36,8 +37,6 @@ namespace com::sun::star::linguistic2 { class XHyphenatedWord; } class SvxBrushItem; class SvxLineSpacingItem; -class SvxTabStop; -class SvxTabStopItem; class SwFlyPortion; class SwFormatDrop; class SwLinePortion; @@ -60,25 +59,24 @@ 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; + SwTwips m_nDefTabStop; bool m_bListTabStopIncluded; tools::Long m_nListTabStopPosition; 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 + SwTwips GetDefTabStop() const { return m_nDefTabStop; } + void SetDefTabStop(SwTwips nNew) const { const_cast<SwLineInfo*>(this)->m_nDefTabStop = nNew; } // vertical alignment @@ -126,7 +124,7 @@ public: class SwTextSizeInfo : public SwTextInfo { private: - typedef std::map< SwLinePortion const *, sal_uInt16 > SwTextPortionMap; + typedef std::map<SwLinePortion const*, SwTwips> SwTextPortionMap; protected: // during formatting, a small database is built, mapping portion pointers @@ -145,7 +143,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 +152,8 @@ protected: const OUString *m_pText; TextFrameIndex m_nIdx; TextFrameIndex m_nLen; + TextFrameIndex m_nMeasureLen; + std::optional<SwLinePortionLayoutContext> m_nLayoutContext; sal_uInt16 m_nKanaIdx; bool m_bOnWin : 1; bool m_bNotEOL : 1; @@ -173,6 +173,8 @@ protected: bool m_bForbiddenChars : 1; // Forbidden start/endline characters bool m_bSnapToGrid : 1; // paragraph snaps to grid sal_uInt8 m_nDirection : 2; // writing direction: 0/90/180/270 degree + SwTwips m_nExtraSpace; // extra space before shrinking = nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth) + SwTwips m_nBreakWidth; // break width to calculate space width at justification protected: void CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, @@ -182,12 +184,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; } @@ -244,36 +246,48 @@ public: sal_uInt16 GetTextHeight() const; - SwPosSize GetTextSize( OutputDevice* pOut, const SwScriptInfo* pSI, + SwPositiveSize GetTextSize( OutputDevice* pOut, const SwScriptInfo* pSI, const OUString& rText, TextFrameIndex nIdx, TextFrameIndex nLen ) const; - SwPosSize GetTextSize() const; - void GetTextSize( const SwScriptInfo* pSI, TextFrameIndex nIdx, - TextFrameIndex nLen, const sal_uInt16 nComp, - sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff, - vcl::TextLayoutCache const* = nullptr) const; - inline SwPosSize GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx, + SwPositiveSize GetTextSize(std::optional<SwLinePortionLayoutContext> nLayoutContext + = std::nullopt) const; + void GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx, TextFrameIndex nLen, + std::optional<SwLinePortionLayoutContext> nLayoutContext, + const sal_uInt16 nComp, SwTwips& nMinSize, tools::Long& nMaxSizeDiff, + SwTwips& nExtraAscent, SwTwips& nExtraDescent, + vcl::text::TextLayoutCache const* = nullptr) const; + inline SwPositiveSize GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx, TextFrameIndex nLen) const; - inline SwPosSize GetTextSize( const OUString &rText ) const; + inline SwPositiveSize GetTextSize( const OUString &rText ) const; 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; } + const std::optional<SwLinePortionLayoutContext> & GetLayoutContext() const { return m_nLayoutContext; } + + void SetLayoutContext(std::optional<SwLinePortionLayoutContext> nNew) + { + m_nLayoutContext = nNew; + } + // No Bullets for the symbol font! bool IsNoSymbol() const { return RTL_TEXTENCODING_SYMBOL != m_pFnt->GetCharSet( m_pFnt->GetActual() ); } @@ -286,16 +300,23 @@ public: bool HasHint(TextFrameIndex nPos) const; + // extra space before shrinking = nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth) + void SetExtraSpace(SwTwips nVal) { m_nExtraSpace = nVal; } + SwTwips GetExtraSpace() const { return m_nExtraSpace; } + // set break width to calculate space width later + void SetBreakWidth(SwTwips nVal) { m_nBreakWidth = nVal; } + SwTwips GetBreakWidth() const { return m_nBreakWidth; } + // If Kana Compression is enabled, a minimum and maximum portion width // is calculated. We format lines with minimal size and share remaining // space among compressed kanas. // During formatting, the maximum values of compressible portions are // stored in m_aMaxWidth and discarded after a line has been formatted. - void SetMaxWidthDiff( const SwLinePortion *nKey, sal_uInt16 nVal ) + void SetMaxWidthDiff(const SwLinePortion* nKey, SwTwips nVal) { m_aMaxWidth.insert( std::make_pair( nKey, nVal ) ); }; - sal_uInt16 GetMaxWidthDiff( const SwLinePortion *nKey ) + SwTwips GetMaxWidthDiff(const SwLinePortion* nKey) { SwTextPortionMap::iterator it = m_aMaxWidth.find( nKey ); @@ -325,11 +346,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 +375,7 @@ class SwTextPaintInfo : public SwTextSizeInfo const bool bGrammarCheck = false ); SwTextPaintInfo &operator=(const SwTextPaintInfo&) = delete; + void NotifyURL_(const SwLinePortion& rPor) const; protected: SwTextPaintInfo() @@ -399,8 +421,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 +434,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 +467,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; } @@ -469,7 +501,6 @@ class SwTextFormatInfo : public SwTextPaintInfo TextFrameIndex m_nSoftHyphPos; ///< SoftHyphPos for Hyphenation TextFrameIndex m_nLineStart; ///< Current line start in rText - TextFrameIndex m_nUnderScorePos; ///< enlarge repaint if underscore has been found TextFrameIndex m_nLastBookmarkPos; ///< need to check for bookmarks at every portion // #i34348# Changed type from sal_uInt16 to SwTwips SwTwips m_nLeft; // Left margin @@ -477,11 +508,13 @@ class SwTextFormatInfo : public SwTextPaintInfo SwTwips m_nFirst; // EZE /// First or left margin, depending on context. SwTwips m_nLeftMargin = 0; - sal_uInt16 m_nRealWidth; // "real" line width - sal_uInt16 m_nWidth; // "virtual" line width - sal_uInt16 m_nLineHeight; // Final height after CalcLine - sal_uInt16 m_nLineNetHeight; // line height without spacing - sal_uInt16 m_nForcedLeftMargin; // Shift of left margin due to frame + SwTwips m_nRealWidth; // "real" line width + SwTwips m_nWidth; // "virtual" line width + SwTwips m_nLineHeight; // Final height after CalcLine + SwTwips m_nLineNetHeight; // line height without spacing + SwTwips m_nForcedLeftMargin; // Shift of left margin due to frame + SwTwips m_nExtraAscent = 0; // Enlarge clipping area for glyphs above the line height + SwTwips m_nExtraDescent = 0; // Enlarge clipping area for glyphs below the line height bool m_bFull : 1; // Line is full bool m_bFootnoteDone : 1; // Footnote already formatted @@ -509,6 +542,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 ); @@ -524,8 +567,8 @@ public: SwTextFormatInfo( const SwTextFormatInfo& rInf, SwLineLayout& rLay, SwTwips nActWidth ); - sal_uInt16 Width() const { return m_nWidth; } - void Width( const sal_uInt16 nNew ) { m_nWidth = nNew; } + SwTwips Width() const { return m_nWidth; } + void Width(const SwTwips nNew) { m_nWidth = nNew; } void Init(); /** @@ -545,10 +588,10 @@ public: SwTwips First() const { return m_nFirst; } void First( const SwTwips nNew ) { m_nFirst = nNew; } void LeftMargin( const SwTwips nNew) { m_nLeftMargin = nNew; } - sal_uInt16 RealWidth() const { return m_nRealWidth; } - void RealWidth( const sal_uInt16 nNew ) { m_nRealWidth = nNew; } - sal_uInt16 ForcedLeftMargin() const { return m_nForcedLeftMargin; } - void ForcedLeftMargin( const sal_uInt16 nN ) { m_nForcedLeftMargin = nN; } + SwTwips RealWidth() const { return m_nRealWidth; } + void RealWidth(const SwTwips nNew) { m_nRealWidth = nNew; } + SwTwips ForcedLeftMargin() const { return m_nForcedLeftMargin; } + void ForcedLeftMargin(const SwTwips nN) { m_nForcedLeftMargin = nN; } sal_uInt8 &MaxHyph() { return m_nMaxHyph; } const sal_uInt8 &MaxHyph() const { return m_nMaxHyph; } @@ -558,7 +601,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,15 +628,22 @@ 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; } // these are used during fly calculation - sal_uInt16 GetLineHeight() const { return m_nLineHeight; } - void SetLineHeight( const sal_uInt16 nNew ) { m_nLineHeight = nNew; } - sal_uInt16 GetLineNetHeight() const { return m_nLineNetHeight; } - void SetLineNetHeight( const sal_uInt16 nNew ) { m_nLineNetHeight = nNew; } + SwTwips GetLineHeight() const { return m_nLineHeight; } + void SetLineHeight(const SwTwips nNew) { m_nLineHeight = nNew; } + SwTwips GetLineNetHeight() const { return m_nLineNetHeight; } + void SetLineNetHeight(const SwTwips nNew) { m_nLineNetHeight = nNew; } const SwLinePortion *GetUnderflow() const { return m_pUnderflow; } SwLinePortion *GetUnderflow() { return m_pUnderflow; } @@ -637,8 +687,12 @@ public: // Should the hyphenate helper be discarded? bool IsHyphenate() const; - TextFrameIndex GetUnderScorePos() const { return m_nUnderScorePos; } - void SetUnderScorePos(TextFrameIndex const nNew) { m_nUnderScorePos = nNew; } + + SwTwips GetExtraAscent() const { return m_nExtraAscent; } + void SetExtraAscent(SwTwips nNew) { m_nExtraAscent = std::max(m_nExtraAscent, nNew); } + + SwTwips GetExtraDescent() const { return m_nExtraDescent; } + void SetExtraDescent(SwTwips nNew) { m_nExtraDescent = std::max(m_nExtraDescent, nNew); } // Calls HyphenateWord() of Hyphenator css::uno::Reference< css::linguistic2::XHyphenatedWord > @@ -661,6 +715,9 @@ public: void SetTabOverflow( bool bOverflow ) { m_bTabOverflow = bOverflow; } bool IsTabOverflow() const { return m_bTabOverflow; } + // get line space count between line start and break position + // by stripping also terminating spaces + sal_Int32 GetLineSpaceCount(TextFrameIndex nBreakPos); }; /** @@ -673,7 +730,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 +738,7 @@ class SwTextSlot final std::unique_ptr<sw::WrongListIterator> m_pTempIter; TextFrameIndex nIdx; TextFrameIndex nLen; + TextFrameIndex nMeasureLen; bool bOn; SwTextSizeInfo *pInf; @@ -715,12 +773,18 @@ inline sal_uInt16 SwTextSizeInfo::GetTextHeight() const return const_cast<SwFont*>(GetFont())->GetHeight( m_pVsh, *GetOut() ); } -inline SwPosSize SwTextSizeInfo::GetTextSize( const OUString &rText ) const +inline sal_uInt16 SwTextSizeInfo::GetHangingBaseline() const +{ + assert(GetOut()); + return const_cast<SwFont*>(GetFont())->GetHangingBaseline( m_pVsh, *GetOut() ); +} + +inline SwPositiveSize SwTextSizeInfo::GetTextSize( const OUString &rText ) const { return GetTextSize(m_pOut, nullptr, rText, TextFrameIndex(0), TextFrameIndex(rText.getLength())); } -inline SwPosSize SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, +inline SwPositiveSize SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, TextFrameIndex const nNewIdx, TextFrameIndex const nNewLen) const { diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx index a5944e49e357..c8abfee4868a 100644 --- a/sw/source/core/text/itradj.cxx +++ b/sw/source/core/text/itradj.cxx @@ -20,6 +20,9 @@ #include <sal/config.h> #include <o3tl/safeint.hxx> +#include <com/sun/star/i18n/WordType.hpp> +#include <swscanner.hxx> +#include <i18nutil/kashida.hxx> #include <IDocumentSettingAccess.hxx> #include <doc.hxx> @@ -45,8 +48,34 @@ void SwTextAdjuster::FormatBlock( ) const SwLinePortion *pFly = nullptr; bool bSkip = !IsLastBlock() && + // don't skip, if the last paragraph line needs space shrinking + m_pCurr->ExtraShrunkWidth() <= m_pCurr->Width() && m_nStart + m_pCurr->GetLen() >= TextFrameIndex(GetInfo().GetText().getLength()); + // tdf#162725 if the last line is longer, than the paragraph width, + // it contains shrinking spaces: don't skip block format here + if( bSkip ) + { + // sum width of the text portions to calculate the line width without shrinking + tools::Long nBreakWidth = 0; + const SwLinePortion *pPos = m_pCurr->GetNextPortion(); + while( pPos && bSkip ) + { + if( !pPos->InGlueGrp() && + // don't calculate with the terminating space, + // otherwise it would result justified line mistakenly + ( pPos->GetNextPortion() || !pPos->IsHolePortion() ) ) + { + nBreakWidth += pPos->Width(); + } + + if( nBreakWidth > m_pCurr->Width() ) + bSkip = false; + + pPos = pPos->GetNextPortion(); + } + } + // Multi-line fields are tricky, because we need to check whether there are // any other text portions in the paragraph. if( bSkip ) @@ -110,138 +139,101 @@ void SwTextAdjuster::FormatBlock( ) GetInfo().GetParaPortion()->GetRepaint().SetOffset(0); } -static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, - sal_Int32& rKashidas, TextFrameIndex& nGluePortion) +static bool lcl_ComputeKashidaPositions(SwTextSizeInfo& rInf, SwTextIter& rItr, + TextFrameIndex& nGluePortion, + const tools::Long nGluePortionWidth, + SwLineLayout* pCurrLine, TextFrameIndex nLineBaseIndex) { // i60594 validate Kashida justification TextFrameIndex nIdx = rItr.GetStart(); TextFrameIndex nEnd = rItr.GetEnd(); - // Note on calling KashidaJustify(): - // Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean - // total number of kashida positions, or the number of kashida positions after some positions - // have been dropped. - // Here we want the clean total, which is OK: We have called ClearKashidaInvalid() before. - rKashidas = rSI.KashidaJustify ( nullptr, nullptr, rItr.GetStart(), rItr.GetLength() ); - - if (rKashidas <= 0) // nothing to do - return true; - - // kashida positions found in SwScriptInfo are not necessarily valid in every font - // if two characters are replaced by a ligature glyph, there will be no place for a kashida - std::vector<TextFrameIndex> aKashidaPos; - rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aKashidaPos); - assert(aKashidaPos.size() >= o3tl::make_unsigned(rKashidas)); - std::vector<TextFrameIndex> aKashidaPosDropped(aKashidaPos.size()); - sal_Int32 nKashidaIdx = 0; - while ( rKashidas && nIdx < nEnd ) + std::vector<TextFrameIndex> aKashidaPositions; + std::vector<tools::Long> aKashidaWidths; + tools::Long nMaxKashidaWidth = 0; + + // Parse the text, and apply the kashida insertion rules + std::function<LanguageType(sal_Int32, sal_Int32, bool)> const pGetLangOfChar( + [&rInf](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const bNoChar) + { return rInf.GetTextFrame()->GetLangOfChar(TextFrameIndex{ nBegin }, nScript, bNoChar); }); + SwScanner aScanner(pGetLangOfChar, rInf.GetText(), nullptr, ModelToViewHelper(), + i18n::WordType::DICTIONARY_WORD, sal_Int32(nIdx), sal_Int32(nEnd)); + + std::vector<bool> aValidPositions; + while (aScanner.NextWord()) { - rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() ); - TextFrameIndex nNext = rItr.GetNextAttr(); - - // is there also a script change before? - // if there is, nNext should point to the script change - TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx ); - if( nNextScript < nNext ) - nNext = nNextScript; - - if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd) - nNext = nEnd; - sal_Int32 nKashidasInAttr = rSI.KashidaJustify ( nullptr, nullptr, nIdx, nNext - nIdx ); - if (nKashidasInAttr > 0) + const OUString& rWord = aScanner.GetWord(); + + // Fetch the set of valid positions from VCL, where possible + if (SwScriptInfo::IsKashidaScriptText(rInf.GetText(), TextFrameIndex{ aScanner.GetBegin() }, + TextFrameIndex{ aScanner.GetLen() })) { + aValidPositions.clear(); + + rItr.SeekAndChgAttrIter(TextFrameIndex{ aScanner.GetBegin() }, rInf.GetRefDev()); + // Kashida glyph looks suspicious, skip Kashida justification - if ( rInf.GetOut()->GetMinKashida() <= 0 ) - { - return false; - } + auto nFontMinKashida = rInf.GetRefDev()->GetMinKashida(); + if (nFontMinKashida <= 0) + continue; - sal_Int32 nKashidasDropped = 0; - if ( !SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) ) - { - nKashidasDropped = nKashidasInAttr; - rKashidas -= nKashidasDropped; - } - else + vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetRefDev()->GetLayoutMode(); + rInf.GetRefDev()->SetLayoutMode(nOldLayout + | vcl::text::ComplexTextLayoutFlags::BiDiRtl); + + rInf.GetRefDev()->GetWordKashidaPositions(rWord, &aValidPositions); + + rInf.GetRefDev()->SetLayoutMode(nOldLayout); + + auto stKashidaPos = i18nutil::GetWordKashidaPosition(rWord, aValidPositions); + if (stKashidaPos.has_value()) { - ComplexTextLayoutFlags nOldLayout = rInf.GetOut()->GetLayoutMode(); - rInf.GetOut()->SetLayoutMode ( nOldLayout | ComplexTextLayoutFlags::BiDiRtl ); - nKashidasDropped = rInf.GetOut()->ValidateKashidas( - rInf.GetText(), sal_Int32(nIdx), sal_Int32(nNext - nIdx), - nKashidasInAttr, - reinterpret_cast<sal_Int32*>(aKashidaPos.data() + nKashidaIdx), - reinterpret_cast<sal_Int32*>(aKashidaPosDropped.data())); - rInf.GetOut()->SetLayoutMode ( nOldLayout ); - if ( nKashidasDropped ) + TextFrameIndex nNewKashidaPos{ aScanner.GetBegin() + stKashidaPos->nIndex }; + + // tdf#164098: The above algorithm can return out-of-range kashida positions. This + // can happen if, for example, a single word is split across multiple lines, and + // the best kashida candidate position is on the first line. + if (nNewKashidaPos >= nIdx && nNewKashidaPos < nEnd) { - rSI.MarkKashidasInvalid(nKashidasDropped, aKashidaPosDropped.data()); - rKashidas -= nKashidasDropped; - nGluePortion -= TextFrameIndex(nKashidasDropped); + aKashidaPositions.push_back(nNewKashidaPos - nLineBaseIndex); + aKashidaWidths.push_back(nFontMinKashida); + nMaxKashidaWidth = std::max(nMaxKashidaWidth, nFontMinKashida); } } - nKashidaIdx += nKashidasInAttr; } - nIdx = nNext; } - // return false if all kashidas have been eliminated - return (rKashidas > 0); -} + nGluePortion += TextFrameIndex{ aKashidaPositions.size() }; -static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, sal_Int32& rKashidas, - TextFrameIndex& nGluePortion, const tools::Long nGluePortionWidth, tools::Long& nSpaceAdd ) -{ - // check kashida width - // if width is smaller than minimal kashida width allowed by fonts in the current line - // drop one kashida after the other until kashida width is OK - while (rKashidas) + // The line may not have enough extra space for all possible kashida. + // Remove them from the beginning of the line to the end. + std::reverse(aKashidaPositions.begin(), aKashidaPositions.end()); + std::reverse(aKashidaWidths.begin(), aKashidaWidths.end()); + + while (nGluePortion && !aKashidaPositions.empty()) { - bool bAddSpaceChanged = false; - TextFrameIndex nIdx = rItr.GetStart(); - TextFrameIndex nEnd = rItr.GetEnd(); - while ( nIdx < nEnd ) + tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); + if (nSpaceAdd / SPACING_PRECISION_FACTOR >= nMaxKashidaWidth) { - rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() ); - TextFrameIndex nNext = rItr.GetNextAttr(); - - // is there also a script change before? - // if there is, nNext should point to the script change - TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx ); - if( nNextScript < nNext ) - nNext = nNextScript; + break; + } - if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd) - nNext = nEnd; - sal_Int32 nKashidasInAttr = rSI.KashidaJustify ( nullptr, nullptr, nIdx, nNext - nIdx ); + aKashidaPositions.pop_back(); + aKashidaWidths.pop_back(); - tools::Long nFontMinKashida = rInf.GetOut()->GetMinKashida(); - if ( nFontMinKashida && nKashidasInAttr > 0 && SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) ) - { - sal_Int32 nKashidasDropped = 0; - while ( rKashidas && nGluePortion && nKashidasInAttr > 0 && - nSpaceAdd / SPACING_PRECISION_FACTOR < nFontMinKashida ) - { - --nGluePortion; - --rKashidas; - --nKashidasInAttr; - ++nKashidasDropped; - if( !rKashidas || !nGluePortion ) // nothing left, return false to - return false; // do regular blank justification - - nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); - bAddSpaceChanged = true; - } - if( nKashidasDropped ) - rSI.MarkKashidasInvalid( nKashidasDropped, nIdx, nNext - nIdx ); - } - if ( bAddSpaceChanged ) - break; // start all over again - nIdx = nNext; + nMaxKashidaWidth = 0; + if (!aKashidaWidths.empty()) + { + nMaxKashidaWidth = *std::max_element(aKashidaWidths.begin(), aKashidaWidths.end()); } - if ( !bAddSpaceChanged ) - break; // everything was OK + + --nGluePortion; } - return true; + + std::reverse(aKashidaPositions.begin(), aKashidaPositions.end()); + pCurrLine->SetKashida(std::move(aKashidaPositions)); + + return !aKashidaWidths.empty(); } // CalcNewBlock() must only be called _after_ CalcLine()! @@ -264,20 +256,15 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, SwTextSizeInfo aInf ( GetTextFrame() ); SwTextIter aItr ( GetTextFrame(), &aInf ); - if ( rSI.CountKashida() ) + TextFrameIndex nLineBase{ 0 }; + if (rSI.ParagraphContainsKashidaScript()) { while (aItr.GetCurr() != pCurrent && aItr.GetNext()) aItr.Next(); - if( bSkipKashida ) - { - rSI.SetNoKashidaLine ( aItr.GetStart(), aItr.GetLength()); - } - else - { - rSI.ClearKashidaInvalid ( aItr.GetStart(), aItr.GetLength() ); - rSI.ClearNoKashidaLine( aItr.GetStart(), aItr.GetLength() ); - } + nLineBase = aItr.GetStart(); + rSI.ReplaceKashidaPositions({}); + pCurrent->SetKashida({}); } // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width! @@ -290,6 +277,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 ) { @@ -348,18 +337,16 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, const tools::Long nGluePortionWidth = static_cast<SwGluePortion*>(pPos)->GetPrtGlue() * SPACING_PRECISION_FACTOR; - sal_Int32 nKashidas = 0; - if( nGluePortion && rSI.CountKashida() && !bSkipKashida ) + if (rSI.ParagraphContainsKashidaScript() && !bSkipKashida) { - // kashida positions found in SwScriptInfo are not necessarily valid in every font - // if two characters are replaced by a ligature glyph, there will be no place for a kashida - if ( !lcl_CheckKashidaPositions ( rSI, aInf, aItr, nKashidas, nGluePortion )) + if (!lcl_ComputeKashidaPositions(aInf, aItr, nGluePortion, nGluePortionWidth, + pCurrent, nLineBase)) { - // all kashida positions are invalid + // no kashidas left // do regular blank justification pCurrent->FinishSpaceAdd(); - GetInfo().SetIdx( m_nStart ); - CalcNewBlock( pCurrent, pStopAt, nReal, true ); + GetInfo().SetIdx(m_nStart); + CalcNewBlock(pCurrent, pStopAt, nReal, true); return; } } @@ -367,22 +354,22 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, if( nGluePortion ) { tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); - - // i60594 - if( rSI.CountKashida() && !bSkipKashida ) - { - if( !lcl_CheckKashidaWidth( rSI, aInf, aItr, nKashidas, nGluePortion, nGluePortionWidth, nSpaceAdd )) - { - // no kashidas left - // do regular blank justification - pCurrent->FinishSpaceAdd(); - GetInfo().SetIdx( m_nStart ); - CalcNewBlock( pCurrent, pStopAt, nReal, true ); - return; - } - } - - pCurrent->SetLLSpaceAdd( nSpaceAdd , nSpaceIdx ); + // shrink, if not shrunk line width exceed the set line width + // i.e. if pCurrent->ExtraShrunkWidth() > 0 + // tdf#163720 but at hyphenated lines, still nBreakWidth contains the correct + // not shrunk line width (ExtraShrunkWidth + hyphen length), so use that + if ( pCurrent->ExtraShrunkWidth() > nBreakWidth ) + nBreakWidth = pCurrent->ExtraShrunkWidth(); + // 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 + : ( nSpaceAdd < 0 ) + // shrink, if portions exceed the line width available before an image + ? -nSpaceAdd + LONG_MAX/2 + : 0; + + pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub : nSpaceAdd, nSpaceIdx ); pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() ); } else if (IsOneBlock() && nCharCnt > TextFrameIndex(1)) @@ -399,6 +386,10 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, else ++nGluePortion; } + else + { + nBreakWidth += pPos->Width(); + } GetInfo().SetIdx( GetInfo().GetIdx() + pPos->GetLen() ); if ( pPos == pStopAt ) { @@ -407,6 +398,33 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, } pPos = pPos->GetNextPortion(); } + + // tdf#164140: Rebuild kashida position indices after line adjustment + if (rSI.ParagraphContainsKashidaScript()) + { + std::vector<TextFrameIndex> aKashidaPositions; + + SwTextSizeInfo aKashInf(GetTextFrame()); + SwTextIter aKashItr(GetTextFrame(), &aKashInf); + + while (true) + { + const SwLineLayout* pCurrLine = aKashItr.GetCurr(); + for (const auto& nPos : pCurrLine->GetKashida()) + { + aKashidaPositions.push_back(nPos + aKashItr.GetStart()); + } + + if (!aKashItr.GetNextLine()) + { + break; + } + + aKashItr.NextLine(); + } + + rSI.ReplaceKashidaPositions(std::move(aKashidaPositions)); + } } SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent ) @@ -434,7 +452,7 @@ SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent ) { // 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() ) @@ -488,7 +506,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 +527,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 +542,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 @@ -545,10 +563,10 @@ SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent, SwTwips nReal ) { tools::Long nRealWidth; - const sal_uInt16 nRealHeight = GetLineHeight(); - const sal_uInt16 nLineHeight = pCurrent->Height(); + const SwTwips nRealHeight = GetLineHeight(); + const SwTwips nLineHeight = pCurrent->Height(); - sal_uInt16 nPrtWidth = pCurrent->PrtWidth(); + SwTwips nPrtWidth = pCurrent->PrtWidth(); SwLinePortion *pLast = pCurrent->FindLastPortion(); if( GetInfo().IsMulti() ) @@ -579,7 +597,7 @@ SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent, pLast->Append( pRight ); if( tools::Long( nPrtWidth )< nRealWidth ) - pRight->PrtWidth( sal_uInt16( nRealWidth - nPrtWidth ) ); + pRight->PrtWidth(nRealWidth - nPrtWidth); // pCurrent->Width() is set to the real size, because we attach the // MarginPortions. @@ -588,7 +606,7 @@ SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent, // implicitly. GetLeftMarginAdjust() and IsJustified() think they have a // line filled with chars. - pCurrent->PrtWidth( sal_uInt16( nRealWidth ) ); + pCurrent->PrtWidth(nRealWidth); return pRight; } @@ -702,7 +720,7 @@ SwFlyPortion *SwTextAdjuster::CalcFlyPortion( const tools::Long nRealWidth, { SwTextFly aTextFly( GetTextFrame() ); - const sal_uInt16 nCurrWidth = m_pCurr->PrtWidth(); + const SwTwips nCurrWidth = m_pCurr->PrtWidth(); SwFlyPortion *pFlyPortion = nullptr; SwRect aLineVert( rCurrRect ); @@ -734,7 +752,7 @@ SwFlyPortion *SwTextAdjuster::CalcFlyPortion( const tools::Long nRealWidth, aLocal.Width( nRealWidth - aLocal.Left() ); GetInfo().GetParaPortion()->SetFly(); pFlyPortion = new SwFlyPortion( aLocal ); - pFlyPortion->Height( sal_uInt16( rCurrRect.Height() ) ); + pFlyPortion->Height(rCurrRect.Height()); // The Width could be smaller than the FixWidth, thus: pFlyPortion->AdjFixWidth(); } @@ -747,7 +765,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 +798,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 +849,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..4fb47e279bba 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -23,6 +23,7 @@ #include <hintids.hxx> #include <editeng/charscaleitem.hxx> +#include <editeng/cmapitem.hxx> #include <svl/itemiter.hxx> #include <svx/svdobj.hxx> #include <vcl/svapp.hxx> @@ -32,11 +33,13 @@ #include <fmtflcnt.hxx> #include <fmtcntnt.hxx> #include <fmtftn.hxx> +#include <fmtpdsc.hxx> #include <frmatr.hxx> #include <frmfmt.hxx> #include <fmtfld.hxx> #include <doc.hxx> #include <IDocumentLayoutAccess.hxx> +#include <IDocumentSettingAccess.hxx> #include <txatbase.hxx> #include <viewsh.hxx> #include <rootfrm.hxx> @@ -57,6 +60,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; @@ -150,9 +157,7 @@ SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const nPosition) const bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, OutputDevice* pOut) { - std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara - ? sw::MapViewToModel(*m_pMergedPara, nNewPos) - : std::make_pair(m_pTextNode, sal_Int32(nNewPos))); + std::pair<SwTextNode const*, sal_Int32> const pos{SeekNewPos(nNewPos, nullptr)}; bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == m_nPosition ? m_pFont->IsFntChg() : Seek( nNewPos ); @@ -260,6 +265,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 +273,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 +284,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 +293,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)) { @@ -295,15 +301,90 @@ void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos) if ( pTextAttr->GetAnyEnd() > nNewPos ) Chg( pTextAttr ); m_nStartIndex++; } +} + +void SwAttrIter::SeekToEnd() +{ + if (m_pTextNode->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH)) + { + SfxItemPool & rPool{const_cast<SwAttrPool&>(m_pTextNode->GetDoc().GetAttrPool())}; + SwFormatAutoFormat const& rListAutoFormat{m_pTextNode->GetAttr(RES_PARATR_LIST_AUTOFMT)}; + std::shared_ptr<SfxItemSet> const pSet{rListAutoFormat.GetStyleHandle()}; + if (!pSet) + { + return; + } + if (pSet->HasItem(RES_TXTATR_CHARFMT)) + { + SwFormatCharFormat const& rCharFormat{pSet->Get(RES_TXTATR_CHARFMT)}; + m_pEndCharFormatAttr.reset(new SwTextAttrEnd{ + SfxPoolItemHolder{rPool, &rCharFormat}, -1, -1}); + Chg(m_pEndCharFormatAttr.get()); + } + // note: RES_TXTATR_CHARFMT should be cleared here but it looks like + // SwAttrHandler only looks at RES_CHRATR_* anyway + m_pEndAutoFormatAttr.reset(new SwTextAttrEnd{ + SfxPoolItemHolder{rPool, &rListAutoFormat}, -1, -1}); + Chg(m_pEndAutoFormatAttr.get()); + } +} + +std::pair<SwTextNode const*, sal_Int32> +SwAttrIter::SeekNewPos(TextFrameIndex const nNewPos, bool *const o_pIsToEnd) +{ + std::pair<SwTextNode const*, sal_Int32> newPos{ m_pMergedPara + ? sw::MapViewToModel(*m_pMergedPara, nNewPos) + : std::make_pair(m_pTextNode, sal_Int32(nNewPos))}; + + bool isToEnd{false}; + if (m_pMergedPara) + { + if (m_pMergedPara->extents.empty()) + { + isToEnd = true; + assert(m_pMergedPara->pLastNode == newPos.first); + } + else + { + auto const& rLast{m_pMergedPara->extents.back()}; + isToEnd = rLast.pNode == newPos.first && rLast.nEnd == newPos.second; + // for text formatting: use *last* node if all text is hidden + if (isToEnd + && m_pMergedPara->pLastNode != newPos.first // implies there is hidden text + && m_pViewShell->GetLayout()->GetParagraphBreakMode() == sw::ParagraphBreakMode::Hidden + && m_pTextNode->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH)) + { + TextFrameIndex nHiddenStart(COMPLETE_STRING); + TextFrameIndex nHiddenEnd(0); + m_pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0), nHiddenStart, nHiddenEnd); + if (TextFrameIndex(0) == nHiddenStart + && TextFrameIndex(m_pMergedPara->mergedText.getLength()) <= nHiddenEnd) + { + newPos.first = m_pMergedPara->pLastNode; + newPos.second = m_pMergedPara->pLastNode->Len(); + } + } + } + } + else + { + isToEnd = newPos.second == m_pTextNode->Len(); + } + if (o_pIsToEnd) + { + *o_pIsToEnd = isToEnd; + } + return newPos; } bool SwAttrIter::Seek(TextFrameIndex const nNewPos) { // note: nNewPos isn't necessarily an index returned from GetNextAttr - std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara - ? sw::MapViewToModel(*m_pMergedPara, nNewPos) - : std::make_pair(m_pTextNode, sal_Int32(nNewPos))); + bool isToEnd{false}; + std::pair<SwTextNode const*, sal_Int32> const newPos{SeekNewPos(nNewPos, &isToEnd)}; if ( m_pRedline && m_pRedline->ExtOn() ) m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), newPos.second); @@ -329,7 +410,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. @@ -363,7 +443,8 @@ bool SwAttrIter::Seek(TextFrameIndex const nNewPos) m_pMergedPara->mergedText, nullptr, nullptr); } } - if (m_pMergedPara || m_pTextNode->GetpSwpHints()) + // also reset it if the RES_PARATR_LIST_AUTOFMT has been applied! + if (m_pMergedPara || m_pTextNode->GetpSwpHints() || m_pEndAutoFormatAttr) { if( m_pRedline ) m_pRedline->Clear( nullptr ); @@ -371,6 +452,8 @@ bool SwAttrIter::Seek(TextFrameIndex const nNewPos) // reset font to its original state m_aAttrHandler.Reset(); m_aAttrHandler.ResetFont( *m_pFont ); + m_pEndCharFormatAttr.reset(); + m_pEndAutoFormatAttr.reset(); if( m_nPropFont ) m_pFont->SetProportion( m_nPropFont ); @@ -419,6 +502,11 @@ bool SwAttrIter::Seek(TextFrameIndex const nNewPos) } } + if (isToEnd && !m_pEndAutoFormatAttr) + { + SeekToEnd(); + } + m_pFont->SetActual( m_pScriptInfo->WhichFont(nNewPos) ); if( m_pRedline ) @@ -443,7 +531,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 +547,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 +558,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 +597,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 +628,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 +643,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 +664,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 +675,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 +688,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 +719,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 +728,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 +743,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 +833,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 @@ -778,6 +868,93 @@ TextFrameIndex SwAttrIter::GetNextAttr() const } } +namespace +{ +class FormatBreakTracker +{ +private: + std::optional<SvxCaseMap> m_nCaseMap; + + bool m_bNeedsBreak = false; + + void SetCaseMap(SvxCaseMap nValue) + { + if (m_nCaseMap != nValue) + m_bNeedsBreak = true; + + m_nCaseMap = nValue; + } + +public: + void HandleItemSet(const SfxItemSet& rSet) + { + if (const SvxCaseMapItem* pItem = rSet.GetItem(RES_CHRATR_CASEMAP)) + SetCaseMap(pItem->GetCaseMap()); + } + + void Reset() { m_bNeedsBreak = false; } + + bool NeedsBreak() const { return m_bNeedsBreak; } +}; + +bool HasFormatBreakAttribute(FormatBreakTracker* pTracker, const SwTextAttr* pAttr) +{ + pTracker->Reset(); + + switch (pAttr->Which()) + { + case RES_TXTATR_AUTOFMT: + case RES_TXTATR_CHARFMT: + { + const SfxItemSet& rSet((pAttr->Which() == RES_TXTATR_CHARFMT) + ? static_cast<SfxItemSet const&>( + pAttr->GetCharFormat().GetCharFormat()->GetAttrSet()) + : *pAttr->GetAutoFormat().GetStyleHandle()); + + pTracker->HandleItemSet(rSet); + } + break; + } + + if (pAttr->IsFormatIgnoreStart() || pAttr->IsFormatIgnoreEnd()) + pTracker->Reset(); + + return pTracker->NeedsBreak(); +} +} + +TextFrameIndex SwAttrIter::GetNextLayoutBreakAttr() const +{ + size_t nStartIndex(m_nStartIndex); + SwTextNode const* pTextNode(m_pTextNode); + + sal_Int32 nNext = std::numeric_limits<sal_Int32>::max(); + + auto* pHints = pTextNode->GetpSwpHints(); + if (!pHints) + { + return TextFrameIndex{ nNext }; + } + + FormatBreakTracker stTracker; + stTracker.HandleItemSet(pTextNode->GetSwAttrSet()); + + for (size_t i = 0; i < pHints->Count(); ++i) + { + SwTextAttr* const pAttr(pHints->Get(i)); + if (HasFormatBreakAttribute(&stTracker, pAttr)) + { + if (i >= nStartIndex) + { + nNext = pAttr->GetStart(); + break; + } + } + } + + return TextFrameIndex{ nNext }; +} + namespace { class SwMinMaxArgs @@ -869,7 +1046,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 +1055,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 +1065,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; @@ -899,9 +1076,9 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) if( !bIsDrawFrameFormat ) { // Does the frame contain a table at the start or the end? - const SwNodes& rNodes = pNd->GetDoc()->GetNodes(); + 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 ) { @@ -946,14 +1123,14 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) } const SvxLRSpaceItem &rLR = pNd->GetLRSpace(); - nMin += rLR.GetLeft(); - nMin += rLR.GetRight(); - nMax += rLR.GetLeft(); - nMax += rLR.GetRight(); + nMin += rLR.ResolveLeft({}); + nMin += rLR.ResolveRight({}); + nMax += rLR.ResolveLeft({}); + nMax += rLR.ResolveRight({}); if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() ) { - pIn->Minimum( nMin ); + rIn.Minimum( nMin ); return; } @@ -966,33 +1143,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 +1180,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,28 +1197,29 @@ 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.ResolveTextLeft({}) + GetLeftMarginWithNum(true); short nFLOffs; // For enumerations a negative first line indentation is probably filled already - if( !GetFirstLineOfsWithNum( nFLOffs ) || nFLOffs > nLROffset ) + if (!GetFirstLineOfsWithNum(nFLOffs, {}) || nFLOffs > nLROffset) nLROffset = nFLOffs; SwMinMaxNodeArgs aNodeArgs; aNodeArgs.m_nMinWidth = 0; aNodeArgs.m_nMaxWidth = 0; aNodeArgs.m_nLeftRest = nLROffset; - aNodeArgs.m_nRightRest = rSpace.GetRight(); + aNodeArgs.m_nRightRest = rRightMargin.ResolveRight({}); 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 +1229,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.ResolveRight({}) - aNodeArgs.m_nRightRest); aNodeArgs.m_nRightRest -= aNodeArgs.m_nRightDiff; if (aNodeArgs.m_nRightRest < 0) aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nRightRest; @@ -1173,8 +1351,8 @@ void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rM else nCurrentWidth = pFrameFormat->GetFrameSize().GetWidth(); } - nCurrentWidth += rLR.GetLeft(); - nCurrentWidth += rLR.GetRight(); + nCurrentWidth += rLR.ResolveLeft({}); + nCurrentWidth += rLR.ResolveRight({}); aArg.m_nWordAdd = nOldWidth + nOldAdd; aArg.m_nWordWidth = nCurrentWidth; aArg.m_nRowWidth += nCurrentWidth; @@ -1224,7 +1402,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.ResolveRight({}); rAbsMin += nLROffset; rAbsMin += nAdd; @@ -1301,7 +1479,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 +1610,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 +1620,151 @@ 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; +} + +bool SwTextFrame::HasSplitFlyDrawObjs() const +{ + return !GetSplitFlyDrawObjs().empty(); +} + +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 (SvxBreak const eBreak = GetBreakItem().GetBreak(); + eBreak == SvxBreak::ColumnBefore || eBreak == SvxBreak::ColumnBoth + || eBreak == SvxBreak::PageBefore || eBreak == SvxBreak::PageBoth + || GetPageDescItem().GetPageDesc() != nullptr) + { + return false; + } + + SwRectFnSet fnUpper(GetUpper()); + if (fnUpper.YDiff(fnUpper.GetBottom(getFrameArea()), fnUpper.GetPrtBottom(*GetUpper())) <= 0) + { + 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 +1785,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..d721ca46c826 100644 --- a/sw/source/core/text/itratr.hxx +++ b/sw/source/core/text/itratr.hxx @@ -21,18 +21,21 @@ #include <o3tl/deleter.hxx> #include "atrhndl.hxx" #include <swfont.hxx> +#include <txatbase.hxx> namespace sw { struct MergedPara; } -class SwTextAttr; class SwTextNode; class SwRedlineItr; class SwViewShell; class SwTextFrame; -class SwAttrIter +class SAL_DLLPUBLIC_RTTI SwAttrIter { friend class SwFontSave; protected: + struct Destr{ void operator()(SwTextAttr *const pAttr) { SwTextAttr::Destroy(pAttr); } }; + ::std::unique_ptr<SwTextAttr, Destr> m_pEndCharFormatAttr; + ::std::unique_ptr<SwTextAttr, Destr> m_pEndAutoFormatAttr; SwAttrHandler m_aAttrHandler; SwViewShell *m_pViewShell; @@ -57,18 +60,20 @@ private: const SwTextNode* m_pTextNode; sw::MergedPara const* m_pMergedPara; + std::pair<SwTextNode const*, sal_Int32> SeekNewPos(TextFrameIndex nNewPos, bool * o_pIsToEnd); void SeekFwd(sal_Int32 nOldPos, sal_Int32 nNewPos); + void SeekToEnd(); 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,12 +81,13 @@ 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 // char position. TextFrameIndex GetNextAttr() const; + TextFrameIndex GetNextLayoutBreakAttr() const; /// Enables the attributes used at char pos nPos in the logical font bool Seek(TextFrameIndex nPos); // Creates the font at the specified position via Seek() and checks diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx index f7c6380cb3bb..1a8c500ef92f 100644 --- a/sw/source/core/text/itrcrsr.cxx +++ b/sw/source/core/text/itrcrsr.cxx @@ -54,7 +54,7 @@ static void lcl_GetCharRectInsideField( SwTextSizeInfo& rInf, SwRect& rOrig, const SwCursorMoveState& rCMS, const SwLinePortion& rPor ) { - OSL_ENSURE( rCMS.m_pSpecialPos, "Information about special pos missing" ); + assert(rCMS.m_pSpecialPos && "Information about special pos missing"); if ( rPor.InFieldGrp() && !static_cast<const SwFieldPortion&>(rPor).GetExp().isEmpty() ) { @@ -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,17 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p GetInfo().SetFont( GetFnt() ); const SwTextNode *const pNode = m_pFrame->GetTextNodeForParaProps(); - const SvxLRSpaceItem &rSpace = pNode->GetSwAttrSet().GetLRSpace(); + auto stMetrics = GetFnt()->GetFontUnitMetrics(); + + SvxFirstLineIndentItem const& rFirstLine(pNode->GetSwAttrSet().GetFirstLineIndent()); + SvxTextLeftMarginItem const& rTextLeftMargin(pNode->GetSwAttrSet().GetTextLeftMargin()); + SvxRightMarginItem const& rRightMargin(pNode->GetSwAttrSet().GetRightMargin()); + // #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. @@ -184,16 +189,13 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p if ( m_pFrame->IsRightToLeft() ) { // this calculation is identical this the calculation for L2R layout - see below - mnLeft = m_pFrame->getFrameArea().Left() + - m_pFrame->getFramePrintArea().Left() + - nLMWithNum - - pNode->GetLeftMarginWithNum() - - // #i95907# - // #i111284# - // rSpace.GetLeft() + rSpace.GetTextLeft(); - ( bListLevelIndentsApplicableAndLabelAlignmentActive - ? 0 - : ( rSpace.GetLeft() - rSpace.GetTextLeft() ) ); + mnLeft = m_pFrame->getFrameArea().Left() + m_pFrame->getFramePrintArea().Left() + nLMWithNum + - pNode->GetLeftMarginWithNum() - + // #i95907# + // #i111284# + // rSpace.GetLeft() + rSpace.GetTextLeft(); + (rTextLeftMargin.ResolveLeft(rFirstLine, stMetrics) + - rTextLeftMargin.ResolveTextLeft(stMetrics)); } else { @@ -203,32 +205,37 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p !pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) ) { // this calculation is identical this the calculation for R2L layout - see above - mnLeft = m_pFrame->getFrameArea().Left() + - m_pFrame->getFramePrintArea().Left() + - nLMWithNum - - pNode->GetLeftMarginWithNum() - - // #i95907# - // #i111284# - ( bListLevelIndentsApplicableAndLabelAlignmentActive - ? 0 - : ( rSpace.GetLeft() - rSpace.GetTextLeft() ) ); + mnLeft = m_pFrame->getFrameArea().Left() + m_pFrame->getFramePrintArea().Left() + + nLMWithNum - pNode->GetLeftMarginWithNum() - + // #i95907# + // #i111284# + (rTextLeftMargin.ResolveLeft(rFirstLine, stMetrics) + - rTextLeftMargin.ResolveTextLeft(stMetrics)); } else { - mnLeft = m_pFrame->getFrameArea().Left() + - std::max( tools::Long( rSpace.GetTextLeft() + nLMWithNum ), - m_pFrame->getFramePrintArea().Left() ); + mnLeft + = m_pFrame->getFrameArea().Left() + + std::max(tools::Long(rTextLeftMargin.ResolveTextLeft(stMetrics) + nLMWithNum), + m_pFrame->getFramePrintArea().Left()); } } mnRight = m_pFrame->getFrameArea().Left() + m_pFrame->getFramePrintArea().Left() + m_pFrame->getFramePrintArea().Width(); - if( mnLeft >= mnRight && - // #i53066# Omit adjustment of nLeft for numbered - // paras inside cells inside new documents: - ( pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) || - !m_pFrame->IsInTab() || - ( !nLMWithNum && (!bLabelAlignmentActive || bListLevelIndentsApplicable) ) ) ) + // tdf#163913: Apply font-relative adjustment to the margins + mnLeft += rTextLeftMargin.ResolveLeftVariablePart(rFirstLine, stMetrics); + mnRight -= rRightMargin.ResolveRightVariablePart(stMetrics); + + if (mnLeft >= mnRight && + // #i53066# Omit adjustment of nLeft for numbered + // paras inside cells inside new documents: + (pNode->getIDocumentSettingAccess()->get( + DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) + || !m_pFrame->IsInTab() + || (bListLevelIndentsApplicable + && nLMWithNum == rTextLeftMargin.ResolveTextLeft(stMetrics)) + || (!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 @@ -241,8 +248,7 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p { short nFLOfst = 0; tools::Long nFirstLineOfs = 0; - if( !pNode->GetFirstLineOfsWithNum( nFLOfst ) && - rSpace.IsAutoFirst() ) + if (!pNode->GetFirstLineOfsWithNum(nFLOfst, stMetrics) && rFirstLine.IsAutoFirst()) { nFirstLineOfs = GetFnt()->GetSize( GetFnt()->GetActual() ).Height(); LanguageType const aLang = m_pFrame->GetLangOfChar( @@ -250,49 +256,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" ); } } } @@ -319,9 +330,10 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p } else { - mnFirst = m_pFrame->getFrameArea().Left() + - std::max( rSpace.GetTextLeft() + nLMWithNum+ nFirstLineOfs, - m_pFrame->getFramePrintArea().Left() ); + mnFirst = m_pFrame->getFrameArea().Left() + + std::max(rTextLeftMargin.ResolveTextLeft(stMetrics) + nLMWithNum + + nFirstLineOfs, + m_pFrame->getFramePrintArea().Left()); } // Note: <SwTextFrame::GetAdditionalFirstLineOffset()> returns a negative @@ -396,6 +408,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 +472,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 +480,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 +523,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 +549,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(); @@ -609,7 +653,12 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, if ( aInf.GetIdx() + pPor->GetLen() < nOfst + nExtra ) { if ( pPor->InSpaceGrp() && nSpaceAdd ) - nX += pPor->PrtWidth() + + // tdf#163042 In the case of shrunk lines with a single portion, + // adjust the line width to show the cursor in the correct position + nX += ( ( std::abs( m_pCurr->Width() - pPor->PrtWidth() ) <= 1 && + m_pCurr->ExtraShrunkWidth() > 0 ) + ? m_pCurr->ExtraShrunkWidth() + : pPor->PrtWidth() ) + pPor->CalcSpacing( nSpaceAdd, aInf ); else { @@ -670,7 +719,6 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, { if( pPor->IsMultiPortion() ) { - nTmpAscent = AdjustBaseLine( *m_pCurr, pPor ); GetInfo().SetMulti( true ); pOrig->Pos().AdjustY(nTmpAscent - nPorAscent ); @@ -882,9 +930,22 @@ 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(); + TextFrameIndex nMaxLen = TextFrameIndex(aInf.GetText().getLength()) - aInf.GetIdx(); + aInf.SetLen( std::min(nMaxLen, pPor->GetLen()) ); pPor->SetLen( nOfst - aInf.GetIdx() ); - aInf.SetLen( pPor->GetLen() ); + 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 +961,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 +1001,7 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, { nX -= GetInfo().GetFont()->GetRightBorderSpace(); } - } + } } bWidth = false; break; @@ -957,7 +1023,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 +1143,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 +1269,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 +1292,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 +1312,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()) { @@ -1291,6 +1345,46 @@ static bool ConsiderNextPortionForCursorOffset(const SwLinePortion* pPor, sal_uI return true; } +static auto SearchLine(SwLineLayout const*const pLineOfFoundPor, + SwLinePortion const*const pFoundPor, + int & rLines, std::vector<SwFieldPortion const*> & rPortions, + SwLineLayout const*const pLine) -> bool +{ + for (SwLinePortion const* pLP = pLine; pLP; pLP = pLP->GetNextPortion()) + { + if (pLP == pFoundPor) + { + return true; + } + if (pLP->InFieldGrp()) + { + SwFieldPortion const* pField(static_cast<SwFieldPortion const*>(pLP)); + if (!pField->IsFollow()) + { + rLines = 0; + rPortions.clear(); + } + if (pLine == pLineOfFoundPor) + { + rPortions.emplace_back(pField); + } + } + else if (pLP->IsMultiPortion()) + { + SwMultiPortion const*const pMulti(static_cast<SwMultiPortion const*>(pLP)); + for (SwLineLayout const* pMLine = &pMulti->GetRoot(); + pMLine; pMLine = pMLine->GetNext()) + { + if (SearchLine(pLineOfFoundPor, pFoundPor, rLines, rPortions, pMLine)) + { + return true; + } + } + } + } + return (pLine == pLineOfFoundPor); +} + // Return: Offset in String TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, const Point &rPoint, bool bChgNode, SwCursorMoveState* pCMS ) const @@ -1313,21 +1407,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(); @@ -1339,14 +1429,18 @@ 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(); + // tdf#16342 In the case of shrunk lines with a single portion, + // adjust the line width to move the cursor to the click position + SwTwips nWidth = + ( std::abs( m_pCurr->Width() - pPor->Width() ) <= 1 && m_pCurr->ExtraShrunkWidth() > 0 ) + ? m_pCurr->ExtraShrunkWidth() + : 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 +1464,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con } } - sal_uInt16 nWidth30; + SwTwips nWidth30; if ( pPor->IsPostItsPortion() ) nWidth30 = 0; else @@ -1380,9 +1474,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 +1483,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 +1525,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 +1576,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,10 +1596,18 @@ 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()) { + if (pPor->IsBreakPortion()) + { + return nCurrStart; + } if ( nWidth ) { // no quick return for as-character frames, we want to peek inside @@ -1553,37 +1654,39 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con return nCurrStart; } } - else + else if (pPor->IsPostItsPortion()) { - if ( pPor->IsPostItsPortion() || pPor->IsBreakPortion() || - pPor->InToxRefGrp() ) + if (SwPostItsPortion* pPostItsPortion = dynamic_cast<SwPostItsPortion*>(pPor)) { - SwPostItsPortion* pPostItsPortion = pPor->IsPostItsPortion() ? dynamic_cast<SwPostItsPortion*>(pPor) : nullptr; - if (pPostItsPortion) + if (!pPostItsPortion->IsScript()) // tdf#141079 { - if (!pPostItsPortion->IsScript()) // tdf#141079 - { - // Offset would be nCurrStart + nLength below, do the same for post-it portions. - nCurrStart += pPor->GetLen(); - } + // Offset would be nCurrStart + nLength below, do the same for post-it portions. + nCurrStart += pPor->GetLen(); } - return nCurrStart; } - if ( pPor->InFieldGrp() ) + return nCurrStart; + } + else if (pPor->InToxRefGrp()) + { + return nCurrStart; + } + else if (pPor->InFieldGrp()) + { + if (bRightOver && !static_cast<SwFieldPortion*>(pPor)->HasFollow()) { - if( bRightOver && !static_cast<SwFieldPortion*>(pPor)->HasFollow() ) - { - nCurrStart += static_cast<SwFieldPortion*>(pPor)->GetFieldLen(); - } - return nCurrStart; + nCurrStart += static_cast<SwFieldPortion*>(pPor)->GetFieldLen(); } + return nCurrStart; } } // 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 +1728,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 +1743,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 +1756,20 @@ 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!" ); - SwDrawTextInfo aDrawInf( aSizeInf.GetVsh(), - *aSizeInf.GetOut(), - &pPara->GetScriptInfo(), - aSizeInf.GetText(), - aSizeInf.GetIdx(), - pPor->GetLen() ); + // protect against bugs elsewhere + SAL_WARN_IF( aSizeInf.GetIdx().get() + pPor->GetLen().get() > aSizeInf.GetText().getLength(), "sw", "portion and text are out of sync" ); + TextFrameIndex nSafeLen( std::min(pPor->GetLen().get(), aSizeInf.GetText().getLength() - aSizeInf.GetIdx().get()) ); + + SwDrawTextInfo aDrawInf(aSizeInf.GetVsh(), *aSizeInf.GetOut(), + &pPara->GetScriptInfo(), aSizeInf.GetText(), + aSizeInf.GetIdx(), nSafeLen, aSizeInf.GetLayoutContext()); // Drop portion works like a multi portion, just its parts are not portions if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 ) @@ -1689,12 +1793,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 +1833,27 @@ 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()) + { + if (SearchLine(m_pCurr, pPor, nLines, portions, pLine)) + { + break; + } + ++nLines; + } + for (SwFieldPortion const* pField : portions) + { + pCMS->m_pSpecialPos->nCharOfst += pField->GetExp().getLength(); + } + pCMS->m_pSpecialPos->nLineOfst = nLines; + } nLength = TextFrameIndex(0); } @@ -1736,11 +1861,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 +1875,13 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con // (BugId: 9692 + Change in feshview) SwFlyInContentFrame *pTmp = pFlyPor->GetFlyFrame(); SwFrame* pLower = pTmp->GetLower(); - bool bChgNodeInner = pLower - && (pLower->IsTextFrame() || pLower->IsLayoutFrame()); + // Allow non-text-frames to get SwGrfNode for as-char anchored images into pPos + // instead of the closest SwTextNode, to be consistent with at-char behavior. + bool bChgNodeInner + = pLower + && (pLower->IsTextFrame() || pLower->IsLayoutFrame() + || (pLower->IsNoTextFrame() + && (!pCMS || pCMS->m_eState != CursorMoveState::SetOnlyText))); Point aTmpPoint( rPoint ); if ( m_pFrame->IsRightToLeft() ) @@ -1765,7 +1890,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 +1942,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 +1956,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 +2049,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..2d19e046430f 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,20 @@ #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 <EnhancedPDFExportHelper.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 <unotxdoc.hxx> using namespace ::com::sun::star; @@ -126,7 +140,7 @@ void SwTextFormatter::Insert( SwLineLayout *pLay ) m_pCurr = pLay; } -sal_uInt16 SwTextFormatter::GetFrameRstHeight() const +SwTwips SwTextFormatter::GetFrameRstHeight() const { // We want the rest height relative to the page. // If we're in a table, then pFrame->GetUpper() is not the page. @@ -140,7 +154,17 @@ sal_uInt16 SwTextFormatter::GetFrameRstHeight() const if( 0 > nHeight ) return m_pCurr->Height(); else - return sal_uInt16( nHeight ); + return nHeight; +} + +bool SwTextFormatter::ClearIfIsFirstOfBorderMerge(const SwLinePortion* pPortion) +{ + if (pPortion == m_pFirstOfBorderMerge) + { + m_pFirstOfBorderMerge = nullptr; + return true; + } + return false; } SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) @@ -155,7 +179,6 @@ SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) // Can be seen in 8081.sdw, if you enter text in the first line TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos(); - TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos(); // Save flys and set to 0, or else segmentation fault // Not ClearFly(rInf) ! @@ -168,7 +191,6 @@ SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) // Truncate() rInf.SetUnderflow(nullptr); rInf.SetSoftHyphPos( nSoftHyphPos ); - rInf.SetUnderScorePos( nUnderScorePos ); rInf.SetPaintOfst( GetLeftMargin() ); // We look for the portion with the under-flow position @@ -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,13 +406,51 @@ 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())); - const bool bHasGrid = pGrid && rInf.SnapToGrid() && - GRID_LINES_CHARS == pGrid->GetGridType(); + // tdf#149089: For compatibility with MSO grid layout, do not insert kern portions to + // align successive portions to the char grid when MS_WORD_COMP_GRID_METRICS is set. + // See also tdf#161145. + // tdf#139418: However, in testing, this only seems to apply to horizontal text. + const bool bUseGridKernPors = GetTextFrame()->IsVertical() + || !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::MS_WORD_COMP_GRID_METRICS); + const bool bHasGrid = pGrid && rInf.SnapToGrid() + && SwTextGrid::LinesAndChars == pGrid->GetGridType() && bUseGridKernPors; const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0; @@ -424,7 +483,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) { SwFontScript nNxtActual = rInf.GetFont()->GetActual(); SwFontScript nLstActual = nNxtActual; - sal_uInt16 nLstHeight = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()); + tools::Long nLstHeight = rInf.GetFont()->GetHeight(); bool bAllowBehind = false; const CharClass& rCC = GetAppCharClass(); @@ -467,7 +526,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) if ( pTmpFnt ) { nLstActual = pTmpFnt->GetActual(); - nLstHeight = static_cast<sal_uInt16>(pTmpFnt->GetHeight()); + nLstHeight = pTmpFnt->GetHeight(); } } } @@ -482,18 +541,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 +598,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 +685,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 +706,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(); @@ -658,7 +728,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) // calculate size SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion(); - sal_uInt16 nSumWidth = pPor->Width(); + SwTwips nSumWidth = pPor->Width(); while ( pTmpPor ) { nSumWidth = nSumWidth + pTmpPor->Width(); @@ -670,15 +740,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 +777,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 +859,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 +868,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,8 +897,9 @@ 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() ); + pPor->Height(rInf.GetTextHeight()); bCalc = true; } else @@ -842,32 +923,290 @@ 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; + bool bTextFound = false; + for (const auto& rItem : pContentControl->GetListItems()) + { + pListWidget->Entries.push_back(rItem.m_aDisplayText); + if (rItem.m_aDisplayText == aText) + { + pListWidget->SelectedEntries.push_back(nIndex); + bTextFound = true; + } + ++nIndex; + } + if (!aText.isEmpty() && !bTextFound) + { + // The selected entry has to be an index, if there is no index for it, insert one at + // the start. + pListWidget->Entries.insert(pListWidget->Entries.begin(), aText); + pListWidget->SelectedEntries.push_back(0); + } + 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; + } + + bool bShrinkPageForPostIts = pPDFExtOutDevData->GetIsExportNotesInMargin() + && sw_GetPostIts(rDoc.getIDocumentFieldsAccess(), nullptr); + const SwFont* pFont = rInf.GetFont(); + if (pFont) + { + pDescriptor->TextFont = pFont->GetActualFont(); + if (bShrinkPageForPostIts) + { + // Page area is scaled down so we have space for comments. Scale down the font height + // for the content of the widgets, too. + double fScale = SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale(); + pDescriptor->TextFont.SetFontHeight(pDescriptor->TextFont.GetFontHeight() * fScale); + } + + // Need to transport the color explicitly, so it's applied to both already filled in and + // future content. + pDescriptor->TextColor = pFont->GetColor(); + } + + // 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); + + // PDF spec 12.5.2 Annotation Dictionaries says the default border with is 1pt wide, increase + // the rectangle to compensate for that, otherwise the text will be cut off at the end. + aLocation.AddTop(-20); + aLocation.AddBottom(20); + aLocation.AddLeft(-20); + aLocation.AddRight(20); + + tools::Rectangle aRect = aLocation.SVRect(); + if (bShrinkPageForPostIts) + { + // Map the rectangle of the form widget, similar to how it's done for e.g. hyperlinks. + const SwPageFrame* pPageFrame = pTextFrame->FindPageFrame(); + if (pPageFrame) + { + aRect = SwEnhancedPDFExportHelper::MapSwRectToPDFRect(pPageFrame, aRect); + } + } + pDescriptor->Location = aRect; + + pPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::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) + OUString ExpandFieldmark(Fieldmark* pBM) { - const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters(); + if (pBM->GetFieldname() == ODF_FORMCHECKBOX) + { + ::sw::mark::CheckboxFieldmark const*const pCheckboxFm( + dynamic_cast<CheckboxFieldmark const*>(pBM)); + assert(pCheckboxFm); + return pCheckboxFm->IsChecked() + ? u"\u2612"_ustr + : u"\u2610"_ustr; + } + assert(pBM->GetFieldname() == ODF_FORMDROPDOWN); + const Fieldmark::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 Fieldmark::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 Fieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY); if (pListEntries != pParameters->end()) { uno::Sequence< OUString > vListEntries; @@ -876,7 +1215,6 @@ namespace sw::mark { return vListEntries[nCurrentIdx]; } - static constexpr OUStringLiteral vEnSpaces = u"\u2002\u2002\u2002\u2002\u2002"; return vEnSpaces; } } @@ -892,7 +1230,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 +1243,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 +1256,68 @@ 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); + + if (const SwDocShell* pShell = rDoc.GetDocShell()) + { + rtl::Reference<SwXTextDocument> xDocumentMetadataAccess(pShell->GetBaseModel()); + + 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 +1325,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::Fieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition); if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE) { if (ch == CH_TXT_ATR_FIELDSTART) @@ -970,10 +1368,14 @@ 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; + if (pBM && pBM->GetFieldname() == ODF_FORMTEXT) + pPor->SetFieldmarkText(true); + } } } } @@ -993,25 +1395,23 @@ 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())); - + // until next layout-breaking attribute change: + const TextFrameIndex nNextLayoutBreakAttr = GetNextLayoutBreakAttr(); // 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); + + auto nNextContext = std::min({ nNextChg, nNextLayoutBreakAttr, nNextScript, nNextDir }); + nNextChg = std::min({ nNextChg, nNextAttr, nNextScript, nNextDir, nNextHidden, nNextBookmark }); // Turbo boost: // We assume that font characters are not larger than twice @@ -1030,13 +1430,15 @@ 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 = std::min(nNextChg, nExpect); + nNextContext = std::min(nNextContext, nExpect); + } // we keep an invariant during method calls: // there are no portion ending characters like hard spaces @@ -1056,6 +1458,56 @@ SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf ) pPor->SetLen( nNextChg - rInf.GetIdx() ); rInf.SetLen( pPor->GetLen() ); + + // Generate a new layout context for the text portion. This is necessary + // for the first text portion in a paragraph, or for any successive + // portions that are outside of the bounds of the previous context. + if (!rInf.GetLayoutContext().has_value() + || rInf.GetLayoutContext()->m_nBegin < rInf.GetLineStart().get() + || rInf.GetLayoutContext()->m_nEnd < nNextChg.get()) + { + // The layout context must terminate at special characters + sal_Int32 nEnd = rInf.GetIdx().get(); + for (; nEnd < nNextContext.get(); ++nEnd) + { + bool bAtEnd = false; + switch (rInf.GetText()[nEnd]) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + case CH_TXTATR_TAB: + case CH_TXTATR_NEWLINE: + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + case CHAR_SOFTHYPHEN: + bAtEnd = true; + break; + + default: + break; + } + + if (bAtEnd) + { + break; + } + } + + std::optional<SwLinePortionLayoutContext> nNewContext; + if (rInf.GetIdx().get() != nEnd) + { + nNewContext = SwLinePortionLayoutContext{ rInf.GetIdx().get(), nEnd }; + } + + rInf.SetLayoutContext(nNewContext); + } + + pPor->SetLayoutContext(rInf.GetLayoutContext()); + return pPor; } @@ -1196,32 +1648,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 +1689,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 +1776,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 +1835,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 +1876,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 SwTwips nWidthOfPortionsUpToDecimalPosition = rInf.X() - pLastTabPortion->GetFix(); static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition ); rInf.SetTabDecimal( 0 ); } @@ -1494,11 +1939,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 +2017,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 +2062,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 +2070,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) FormatReset( GetInfo() ); GetInfo().SetNumDone( bOldNumDone ); + GetInfo().SetFootnoteDone(bOldFootnoteDone); GetInfo().SetArrowDone( bOldArrowDone ); GetInfo().SetErgoDone( bOldErgoDone ); @@ -1636,6 +2082,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 +2109,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,8 +2128,17 @@ 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(); + GetInfo().SetExtraSpace(0); // delete old rest if ( GetInfo().GetRest() ) @@ -1690,6 +2153,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) m_pCurr->SetLen(TextFrameIndex(0)); m_pCurr->Width(0); + m_pCurr->ExtraShrunkWidth(0); m_pCurr->Truncate(); } } @@ -1700,7 +2164,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) // the SwLineLayout is wider as well. if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN)) { - sal_uInt16 nSum = 0; + SwTwips nSum = 0; SwLinePortion* pPor = m_pCurr->GetFirstPortion(); while (pPor) @@ -1772,21 +2236,63 @@ 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())); if ( pGrid && GetInfo().SnapToGrid() ) { + // tdf#88752: Grid base height is ignored for table rows in compat mode + if (m_pFrame->IsInTab() + && m_pFrame->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::MS_WORD_COMP_GRID_METRICS)) + { + m_pCurr->SetRealHeight(nLineHeight); + return; + } + const sal_uInt16 nGridWidth = pGrid->GetBaseHeight(); const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight(); const bool bRubyTop = ! pGrid->GetRubyTextBelow(); nLineHeight = nGridWidth + nRubyHeight; - const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight; + const auto nAmpRatio = (m_pCurr->Height() + nLineHeight - 1) / nLineHeight; nLineHeight *= nAmpRatio; - const sal_uInt16 nAsc = m_pCurr->GetAscent() + + // tdf#164871: Handle all types of line spacing in grid layout. + // The prop/auto rule was originally implemented with #99106#, but other spacing + // types were not implemented (perhaps unintentionally). These implementations + // differ from the below non-grid implementations, so cannot be reused. + const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing(); + if (pSpace) + { + switch (pSpace->GetLineSpaceRule()) + { + case SvxLineSpaceRule::Min: + // tdf#164871: MSO idiosyncratically disables the grid extra space + // when the minimum height is 0. While strange, certain documents + // require this and it seems harmless to emulate. + if (pSpace->GetLineHeight() == 0) + { + nLineHeight = m_pCurr->Height() + nRubyHeight; + } + + if (nLineHeight < pSpace->GetLineHeight()) + { + nLineHeight = pSpace->GetLineHeight(); + } + break; + + case SvxLineSpaceRule::Fix: + nLineHeight = pSpace->GetLineHeight(); + break; + + default: + break; + } + } + + const SwTwips nAsc = m_pCurr->GetAscent() + ( bRubyTop ? ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 : ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 ); @@ -1796,7 +2302,6 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) m_pInf->GetParaPortion()->SetFixLineHeight(); // we ignore any line spacing options except from ... - const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing(); if ( ! IsParaLine() && pSpace && SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() ) { @@ -1806,7 +2311,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,8 +2346,8 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) nTmp /= 100; if( !nTmp ) ++nTmp; - nLineHeight = static_cast<sal_uInt16>(nTmp); - sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% + nLineHeight = nTmp; + SwTwips nAsc = (4 * nLineHeight) / 5; // 80% #if 0 // could do clipping here (like Word does) // but at 0.5 its unreadable either way... @@ -1866,7 +2371,7 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) case SvxLineSpaceRule::Fix: { nLineHeight = pSpace->GetLineHeight(); - const sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% + const SwTwips nAsc = (4 * nLineHeight) / 5; // 80% if( nAsc < m_pCurr->GetAscent() || nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() ) m_pCurr->SetClipping( true ); @@ -1892,6 +2397,8 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) if( nTmp < 50 ) nTmp = nTmp ? 50 : 100; + bool bPropLineShrinks = (nTmp < 100); + // extend line height by (nPropLineSpace - 100) percent of the font height nTmp -= 100; nTmp *= m_pCurr->GetTextHeight(); @@ -1899,7 +2406,20 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) nTmp += nLineHeight; if (nTmp < 1) nTmp = 1; - nLineHeight = static_cast<sal_uInt16>(nTmp); + nLineHeight = nTmp; + + // tdf#146081: The height and ascent of the first line may have been + // adjusted above. In order to have consistent line spacing when rendering, + // the same adjustments must be made to the following lines. + if (bPropLineShrinks + && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE)) + { + SwTwips nAsc = (4 * nLineHeight) / 5; // 80% + m_pCurr->SetAscent(nAsc); + m_pCurr->Height(nLineHeight, false); + m_pInf->GetParaPortion()->SetFixLineHeight(); + } break; } case SvxInterLineSpaceRule::Fix: @@ -1941,8 +2461,8 @@ void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const rInf.First( FirstLeft() ); rInf.LeftMargin(GetLeftMargin()); - rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) ); - rInf.Width( rInf.RealWidth() ); + rInf.RealWidth(rInf.Right() - GetLeftMargin()); + rInf.Width(std::max(rInf.RealWidth(), SwTwips(0))); if( const_cast<SwTextFormatter*>(this)->GetRedln() ) { const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() ); @@ -2099,8 +2619,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() ) { @@ -2252,7 +2772,7 @@ bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const { // First we check, whether a fly overlaps with the line. // = GetLineHeight() - const sal_uInt16 nHeight = GetCurr()->GetRealHeight(); + const SwTwips nHeight = GetCurr()->GetRealHeight(); SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight ); SwRect aLineVert( aLine ); @@ -2285,7 +2805,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 +2822,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 +2898,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 +2950,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 +3013,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 +3045,7 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) if( bForced ) { m_pCurr->SetForcedLeftMargin(); - rInf.ForcedLeftMargin( static_cast<sal_uInt16>(aInter.Width()) ); + rInf.ForcedLeftMargin(aInter.Width()); } if( bFullLine ) @@ -2481,7 +3061,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 +3074,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 +3091,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 +3131,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 +3150,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 +3172,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 +3182,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() || @@ -2726,8 +3310,8 @@ void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePorti { // Calculate maximum height and ascent SwLinePortion* pActPor = m_pFirstOfBorderMerge; - sal_uInt16 nMaxAscent = 0; - sal_uInt16 nMaxHeight = 0; + SwTwips nMaxAscent = 0; + SwTwips nMaxHeight = 0; bool bReachCurrent = false; while( pActPor ) { @@ -2767,6 +3351,16 @@ void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePorti Seek(rInf.GetIdx()); } +namespace sw { + bool IsShowHiddenChars(SwViewShell const*const pViewShell) + { + SwViewOption const*const pOpt{pViewShell ? pViewShell->GetViewOptions() : nullptr}; + const bool bShowInDocView{pViewShell && pViewShell->GetWin() && pOpt->IsShowHiddenChar()}; + const bool bShowForPrinting{pViewShell && pOpt->IsShowHiddenChar(true) && pOpt->IsPrinting()}; + return (bShowInDocView || bShowForPrinting); + } +} + namespace { // calculates and sets optimal repaint offset for the current line tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis, diff --git a/sw/source/core/text/itrform2.hxx b/sw/source/core/text/itrform2.hxx index bd986e4be324..18ee69f2fac4 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; @@ -186,7 +186,7 @@ public: void Insert( SwLineLayout *pLine ); // The remaining height to the page border - sal_uInt16 GetFrameRstHeight() const; + SwTwips GetFrameRstHeight() const; // How wide would you be without any bounds (Flys etc.)? SwTwips CalcFitToContent_( ); @@ -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..6e9d48a3a8f3 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,15 +126,14 @@ 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() ); -// sal_uInt16 nFntAscent = GetInfo().GetFont()->GetAscent( GetInfo().GetVsh(), GetInfo().GetOut() ); -#endif - // maybe catch-up adjustment GetAdjusted(); + AddExtraBlankWidth(); GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() ); GetInfo().ResetSpaceIdx(); GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() ); @@ -140,13 +147,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 +200,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 @@ -195,7 +217,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, GetInfo().GetPos().Y() + nTmpHeight > rPaint.Top() + rPaint.Height() ) { bClip = false; - rClip.ChgClip( rPaint, m_pFrame, m_pCurr->HasUnderscore() ); + rClip.ChgClip(rPaint, m_pFrame, m_pCurr->GetExtraAscent(), m_pCurr->GetExtraDescent()); } #if OSL_DEBUG_LEVEL > 1 static bool bClipAlways = false; @@ -228,7 +250,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, // tdf#117448 at small fixed line height, enlarge clipping area in table cells // to show previously clipped text content on the area of paragraph margins if ( rFrame.IsInTab() ) - rClip.ChgClip( aLineRect, m_pFrame, false, rFrame.GetTopMargin(), rFrame.GetBottomMargin() ); + rClip.ChgClip(aLineRect, m_pFrame, rFrame.GetTopMargin(), rFrame.GetBottomMargin()); else rClip.ChgClip( aLineRect, m_pFrame ); bClip = false; @@ -243,7 +265,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 ); @@ -350,7 +372,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, GetInfo().X() + pPor->Width() + ( pPor->Height() / 2 ) > nMaxRight ) { bClip = false; - rClip.ChgClip( rPaint, m_pFrame, m_pCurr->HasUnderscore() ); + rClip.ChgClip(rPaint, m_pFrame, m_pCurr->GetExtraAscent(), m_pCurr->GetExtraDescent()); } // Portions, which lay "below" the text like post-its @@ -385,9 +407,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 +430,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 +456,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,13 +487,51 @@ 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 delete GetInfo().GetUnderFnt(); GetInfo().SetUnderFnt( nullptr ); - // paint remaining stuff + // paint remaining stuff, e.g. the line ending symbols, pilcrow (¶) and the line break if( bDrawInWindow ) { // If special vertical alignment is enabled, GetInfo().Y() is the @@ -455,6 +564,12 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, GetInfo().Y( GetInfo().GetPos().Y() + AdjustBaseLine( *m_pCurr, &aEnd ) ); GetInfo().X( GetInfo().X() + + // tdf#163042 In the case of shrunk lines with a single portion, adjust + // the line width (if needed, i.e. if the shrunk line doesn't end in a space) + // to show the terminating pilcrow at the correct position, and not before that + ( ( !( pEndTempl->GetNextPortion() && pEndTempl->GetNextPortion()->IsHolePortion() ) && + std::abs( m_pCurr->Width() - m_pCurr->GetFirstPortion()->Width() ) <= 1 && m_pCurr->ExtraShrunkWidth() > 0 ) + ? m_pCurr->ExtraShrunkWidth() - m_pCurr->Width() : 0 ) + ( GetCurr()->IsHanging() ? GetCurr()->GetHangingMargin() : 0 ) ); aEnd.Paint( GetInfo() ); GetInfo().Y( nOldY ); @@ -543,12 +658,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..eb3a9a859854 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 @@ -144,9 +144,9 @@ private: SwTwips mnLeft; SwTwips mnRight; SwTwips mnFirst; - sal_uInt16 mnDropLeft; - sal_uInt16 mnDropHeight; - sal_uInt16 mnDropDescent; + SwTwips mnDropLeft; + SwTwips mnDropHeight; + SwTwips mnDropDescent; sal_uInt16 mnDropLines; SvxAdjust mnAdjust; // #i91133# @@ -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) @@ -188,8 +188,7 @@ public: bool IsLastBlock() const { return m_bLastBlock; } bool IsLastCenter() const { return m_bLastCenter; } SvxAdjust GetAdjust() const { return mnAdjust; } - sal_uInt16 GetLineWidth() const - { return sal_uInt16( Right() - GetLeftMargin() + 1 ); } + SwTwips GetLineWidth() const { return Right() - GetLeftMargin() + 1; } SwTwips GetLeftMin() const { return std::min(mnFirst, mnLeft); } bool HasNegFirst() const { return mnFirst < mnLeft; } @@ -201,11 +200,11 @@ public: // DropCaps sal_uInt16 GetDropLines() const { return mnDropLines; } void SetDropLines( const sal_uInt16 nNew ) { mnDropLines = nNew; } - sal_uInt16 GetDropLeft() const { return mnDropLeft; } - sal_uInt16 GetDropHeight() const { return mnDropHeight; } - void SetDropHeight( const sal_uInt16 nNew ) { mnDropHeight = nNew; } - sal_uInt16 GetDropDescent() const { return mnDropDescent; } - void SetDropDescent( const sal_uInt16 nNew ) { mnDropDescent = nNew; } + SwTwips GetDropLeft() const { return mnDropLeft; } + SwTwips GetDropHeight() const { return mnDropHeight; } + void SetDropHeight(const SwTwips nNew) { mnDropHeight = nNew; } + SwTwips GetDropDescent() const { return mnDropDescent; } + void SetDropDescent(const SwTwips nNew) { mnDropDescent = nNew; } void DropInit(); // Returns the TextPos for start and end of the current line without whitespace @@ -270,6 +269,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 +287,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 +335,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..eb5a2aa01892 100644 --- a/sw/source/core/text/pordrop.hxx +++ b/sw/source/core/text/pordrop.hxx @@ -38,7 +38,7 @@ class SwDropPortionPart std::unique_ptr<SwDropPortionPart> m_pFollow; std::unique_ptr<SwFont> m_pFnt; TextFrameIndex m_nLen; - sal_uInt16 m_nWidth; + SwTwips m_nWidth; bool m_bJoinBorderWithNext; bool m_bJoinBorderWithPrev; @@ -51,8 +51,8 @@ public: void SetFollow( std::unique_ptr<SwDropPortionPart> pNew ) { m_pFollow = std::move(pNew); }; SwFont& GetFont() const { return *m_pFnt; } TextFrameIndex GetLen() const { return m_nLen; } - sal_uInt16 GetWidth() const { return m_nWidth; } - void SetWidth( sal_uInt16 nNew ) { m_nWidth = nNew; } + SwTwips GetWidth() const { return m_nWidth; } + void SetWidth(SwTwips nNew) { m_nWidth = nNew; } bool GetJoinBorderWithPrev() const { return m_bJoinBorderWithPrev; } bool GetJoinBorderWithNext() const { return m_bJoinBorderWithNext; } @@ -60,38 +60,39 @@ 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; std::unique_ptr<SwDropPortionPart> m_pPart; // due to script/attribute changes sal_uInt16 m_nLines; // Line count - sal_uInt16 m_nDropHeight; // Height - sal_uInt16 m_nDropDescent; // Distance to the next line - sal_uInt16 m_nDistance; // Distance to the text - sal_uInt16 m_nFix; // Fixed position - short m_nY; // Y Offset + SwTwips m_nDropHeight; // Height + SwTwips m_nDropDescent; // Distance to the next line + SwTwips m_nDistance; // Distance to the text + SwTwips m_nFix; // Fixed position + SwTwips m_nY; // Y Offset bool FormatText( SwTextFormatInfo &rInf ); void PaintText( const SwTextPaintInfo &rInf ) const; public: SwDropPortion( const sal_uInt16 nLineCnt, - const sal_uInt16 nDropHeight, - const sal_uInt16 nDropDescent, - const sal_uInt16 nDistance ); + const SwTwips nDropHeight, + const SwTwips nDropDescent, + const SwTwips nDistance ); virtual ~SwDropPortion() override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; void PaintDrop( const SwTextPaintInfo &rInf ) const; virtual bool Format( SwTextFormatInfo &rInf ) override; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; sal_uInt16 GetLines() const { return m_nLines; } - sal_uInt16 GetDistance() const { return m_nDistance; } - sal_uInt16 GetDropHeight() const { return m_nDropHeight; } - sal_uInt16 GetDropDescent() const { return m_nDropDescent; } - sal_uInt16 GetDropLeft() const { return Width() + m_nFix; } + SwTwips GetDistance() const { return m_nDistance; } + SwTwips GetDropHeight() const { return m_nDropHeight; } + SwTwips GetDropDescent() const { return m_nDropDescent; } + SwTwips GetDropLeft() const { return Width() + m_nFix; } SwDropPortionPart* GetPart() const { return m_pPart.get(); } void SetPart( std::unique_ptr<SwDropPortionPart> pNew ) { m_pPart = std::move(pNew); } diff --git a/sw/source/core/text/porexp.cxx b/sw/source/core/text/porexp.cxx index 29f470c1490c..afdf71da21c0 100644 --- a/sw/source/core/text/porexp.cxx +++ b/sw/source/core/text/porexp.cxx @@ -18,11 +18,12 @@ */ #include <viewopt.hxx> +#include <IDocumentSettingAccess.hxx> #include <SwPortionHandler.hxx> #include "inftxt.hxx" #include "porexp.hxx" -TextFrameIndex SwExpandPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +TextFrameIndex SwExpandPortion::GetModelPositionForViewPoint(const SwTwips nOfst) const { return SwLinePortion::GetModelPositionForViewPoint( nOfst ); } bool SwExpandPortion::GetExpText( const SwTextSizeInfo&, OUString &rText ) const @@ -38,7 +39,17 @@ void SwExpandPortion::HandlePortion( SwPortionHandler& rPH ) const rPH.Special( GetLen(), OUString(), GetWhichPor() ); } -SwPosSize SwExpandPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +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); +} + +SwPositiveSize SwExpandPortion::GetTextSize( const SwTextSizeInfo &rInf ) const { SwTextSlot aDiffText( &rInf, this, false, false ); return rInf.GetTextSize(); @@ -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,51 @@ 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 (rInf.GetOpt().IsViewMetaChars() && rInf.GetOpt().IsHardBlank()) + { + // Draw tilde or degree sign + OUString aMarker = (m_cChar == CHAR_HARDBLANK ? + rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess() + .get(DocumentSettingId::USE_VARIABLE_WIDTH_NBSP) + ? u"~"_ustr + : u"°"_ustr + : u"-"_ustr); //CHAR_HARDHYPHEN + + SwPositiveSize 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(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor()); + 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 +261,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 ) { @@ -223,7 +289,7 @@ void SwPostItsPortion::Paint( const SwTextPaintInfo &rInf ) const rInf.DrawPostIts( IsScript() ); } -sal_uInt16 SwPostItsPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +SwTwips SwPostItsPortion::GetViewWidth(const SwTextSizeInfo& rInf) const { // Unbelievable: PostIts are always visible return rInf.OnWin() ? SwViewOption::GetPostItsWidth( rInf.GetOut() ) : 0; diff --git a/sw/source/core/text/porexp.hxx b/sw/source/core/text/porexp.hxx index 9c7be5be5aaf..baf45c7cf2dd 100644 --- a/sw/source/core/text/porexp.hxx +++ b/sw/source/core/text/porexp.hxx @@ -26,15 +26,19 @@ class SwExpandPortion : public SwTextPortion public: SwExpandPortion() { SetWhichPor( PortionType::Expand ); } virtual bool Format( SwTextFormatInfo &rInf ) override; - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override; + + void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, + TextFrameIndex& nOffset) const override; }; +/// Non-breaking space or non-breaking hyphen. class SwBlankPortion : public SwExpandPortion { sal_Unicode m_cChar; @@ -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 @@ -63,7 +70,7 @@ public: explicit SwPostItsPortion( bool bScrpt ); virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; bool IsScript() const { return m_bScript; } }; diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx index fdb2e4442916..ad4fa9290310 100644 --- a/sw/source/core/text/porfld.cxx +++ b/sw/source/core/text/porfld.cxx @@ -21,10 +21,16 @@ #include <com/sun/star/i18n/ScriptType.hpp> #include <com/sun/star/i18n/XBreakIterator.hpp> -#include <vcl/graph.hxx> +#include <utility> + +#include <comphelper/string.hxx> #include <editeng/brushitem.hxx> +#include <o3tl/deleter.hxx> +#include <vcl/graph.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 +48,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 +65,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; @@ -66,21 +73,20 @@ SwFieldPortion *SwFieldPortion::Clone( const OUString &rExpand ) const void SwFieldPortion::TakeNextOffset( const SwFieldPortion* pField ) { - OSL_ENSURE( pField, "TakeNextOffset: Missing Source" ); + assert(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 +106,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() ) ); @@ -115,12 +119,12 @@ SwFieldPortion::~SwFieldPortion() m_pFont.reset(); } -sal_uInt16 SwFieldPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +SwTwips 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 +141,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 +160,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 +173,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 +182,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 +204,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 ); @@ -202,7 +224,7 @@ void SwFieldPortion::CheckScript( const SwTextSizeInfo &rInf ) return; SwFontScript nActual = m_pFont ? m_pFont->GetActual() : rInf.GetFont()->GetActual(); - sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, 0 ); + sal_Int16 nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, 0 ); sal_Int32 nChg = 0; if( i18n::ScriptType::WEAK == nScript ) { @@ -316,10 +338,6 @@ bool SwFieldPortion::Format( SwTextFormatInfo &rInf ) } rInf.SetLen( nFullLen ); - if (TextFrameIndex(COMPLETE_STRING) != rInf.GetUnderScorePos() && - rInf.GetUnderScorePos() > rInf.GetIdx() ) - rInf.SetUnderScorePos( rInf.GetIdx() ); - if( m_pFont ) m_pFont->AllocFontCacheId( rInf.GetVsh(), m_pFont->GetActual() ); @@ -381,25 +399,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; + } + }; + if (IsHook(nNew, true)) + { + if (nNew == CH_BREAK) + { + bFull = true; } - default: ; + aNew = aNew.copy(1); + ++nNextOfst; } // Even if there is no more text left for a follow field, @@ -410,8 +440,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 +472,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 +485,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,20 +493,30 @@ 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 +SwPositiveSize SwFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const { SwFontSave aSave( rInf, m_pFont.get() ); - SwPosSize aSize( SwExpandPortion::GetTextSize( rInf ) ); + SwPositiveSize aSize( SwExpandPortion::GetTextSize( rInf ) ); return aSize; } @@ -498,13 +547,12 @@ bool SwHiddenPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) SwNumberPortion::SwNumberPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFont, const bool bLft, - const bool bCntr, - const sal_uInt16 nMinDst, + const bool bCntr, const SwTwips 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 ); @@ -512,7 +560,7 @@ SwNumberPortion::SwNumberPortion( const OUString &rExpand, SetCenter( bCntr ); } -TextFrameIndex SwNumberPortion::GetModelPositionForViewPoint(const sal_uInt16) const +TextFrameIndex SwNumberPortion::GetModelPositionForViewPoint(const SwTwips) const { return TextFrameIndex(0); } @@ -550,15 +598,20 @@ 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() - - rInf.First() - + rInf.ForcedLeftMargin(); + + rInf.GetTextFrame() + ->GetTextNodeForParaProps() + ->GetSwAttrSet() + .GetFirstLineIndent() + .ResolveTextFirstLineOffset({}) + - rInf.First() + rInf.ForcedLeftMargin(); } else { @@ -595,10 +648,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; } @@ -636,9 +689,9 @@ void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const } // calculate the width of the number portion, including follows - const sal_uInt16 nOldWidth = Width(); - sal_uInt16 nSumWidth = 0; - sal_uInt16 nOffset = 0; + const SwTwips nOldWidth = Width(); + SwTwips nSumWidth = 0; + SwTwips nOffset = 0; const SwLinePortion* pTmp = this; while ( pTmp && pTmp->InNumberGrp() ) @@ -686,7 +739,7 @@ void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const // logical const: reset width SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this); bPaintSpace = bPaintSpace && m_nFixWidth < nOldWidth; - sal_uInt16 nSpaceOffs = m_nFixWidth; + SwTwips nSpaceOffs = m_nFixWidth; pThis->Width( m_nFixWidth ); if( ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) || @@ -702,7 +755,7 @@ void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const if( IsCenter() ) { /* #110778# a / 2 * 2 == a is not a tautology */ - sal_uInt16 nTmpOffset = nOffset; + SwTwips nTmpOffset = nOffset; nOffset /= 2; if( nOffset < m_nMinDist ) nOffset = nTmpOffset - m_nMinDist; @@ -729,7 +782,7 @@ void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const pThis->Width( nOldWidth - nSpaceOffs + 12 ); { - SwTextSlot aDiffText( &aInf, this, true, false, " " ); + SwTextSlot aDiffText( &aInf, this, true, false, u" "_ustr ); aInf.DrawText( *this, aInf.GetLen(), true ); } } @@ -742,7 +795,7 @@ SwBulletPortion::SwBulletPortion( const sal_UCS4 cBullet, std::unique_ptr<SwFont> pFont, const bool bLft, const bool bCntr, - const sal_uInt16 nMinDst, + const SwTwips nMinDst, const bool bLabelAlignmentPosAndSpaceModeActive ) : SwNumberPortion( OUString(&cBullet, 1) + rBulletFollowedBy, std::move(pFont), bLft, bCntr, nMinDst, @@ -757,7 +810,7 @@ SwGrfNumPortion::SwGrfNumPortion( const OUString& rGraphicFollowedBy, const SvxBrushItem* pGrfBrush, OUString const & referer, const SwFormatVertOrient* pGrfOrient, const Size& rGrfSize, - const bool bLft, const bool bCntr, const sal_uInt16 nMinDst, + const bool bLft, const bool bCntr, const SwTwips nMinDst, const bool bLabelAlignmentPosAndSpaceModeActive ) : SwNumberPortion( rGraphicFollowedBy, nullptr, bLft, bCntr, nMinDst, bLabelAlignmentPosAndSpaceModeActive ), @@ -785,14 +838,14 @@ 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) ); + Height(m_nGrfHeight); m_bNoPaint = false; } -SwGrfNumPortion::~SwGrfNumPortion() +void SwGrfNumPortion::ImplDestroy() { if ( IsAnimated() ) { @@ -803,6 +856,11 @@ SwGrfNumPortion::~SwGrfNumPortion() m_pBrush.reset(); } +SwGrfNumPortion::~SwGrfNumPortion() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + void SwGrfNumPortion::StopAnimation( const OutputDevice* pOut ) { if ( IsAnimated() ) @@ -817,7 +875,7 @@ bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf ) { SetHide( false ); // Width( nFixWidth ); - sal_uInt16 nFollowedByWidth( 0 ); + SwTwips nFollowedByWidth(0); if ( mbLabelAlignmentPosAndSpaceModeActive ) { SwFieldPortion::Format( rInf ); @@ -834,7 +892,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 +927,7 @@ bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf ) } if( Width() < nDiff ) - Width( sal_uInt16(nDiff) ); + Width( nDiff ); return bFull; } @@ -900,7 +958,7 @@ void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const if( m_nFixWidth < Width() && !bTmpLeft ) { - sal_uInt16 nOffset = Width() - m_nFixWidth; + SwTwips nOffset = Width() - m_nFixWidth; if( nOffset < m_nMinDist ) nOffset = 0; else @@ -935,7 +993,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 +1017,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 +1053,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 +1124,7 @@ void SwTextFrame::StopAnimation( const OutputDevice* pOut ) */ SwCombinedPortion::SwCombinedPortion( const OUString &rText ) : SwFieldPortion( rText ) + , m_aWidth{ 0, 0, 0 } , m_nUpPos(0) , m_nLowPos(0) , m_nProportion(55) @@ -1166,7 +1229,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); + 2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3; } } } @@ -1178,7 +1241,7 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) m_nProportion = 55; // In nMainAscent/Descent we store the ascent and descent // of the original surrounding font - sal_uInt16 nMaxDescent, nMaxAscent, nMaxWidth; + SwTwips nMaxDescent, nMaxAscent, nMaxWidth; sal_uInt16 nMainDescent = rInf.GetFont()->GetHeight( pSh, *rInf.GetOut() ); const sal_uInt16 nMainAscent = rInf.GetFont()->GetAscent( pSh, *rInf.GetOut() ); nMainDescent = nMainDescent - nMainAscent; @@ -1213,7 +1276,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] = aSize.Width(); if( i == nTop ) // enter the second line { m_nLowPos = nMaxDescent; @@ -1228,7 +1291,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 ) @@ -1257,8 +1320,8 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) Height( nMainAscent + nMainDescent ); // We calculate the x positions of the characters in both lines... - sal_uInt16 nTopDiff = 0; - sal_uInt16 nBotDiff = 0; + SwTwips nTopDiff = 0; + SwTwips nBotDiff = 0; if( nMaxWidth > Width() ) { nTopDiff = ( nMaxWidth - Width() ) / 2; @@ -1291,7 +1354,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(); @@ -1304,7 +1367,7 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) return bFull; } -sal_uInt16 SwCombinedPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +SwTwips SwCombinedPortion::GetViewWidth(const SwTextSizeInfo& rInf) const { if( !GetLen() ) // for the dummy part at the end of the line, where return 0; // the combined portion doesn't fit. @@ -1350,4 +1413,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::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::pdf::StructElement::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..da9624ffd854 100644 --- a/sw/source/core/text/porfld.hxx +++ b/sw/source/core/text/porfld.hxx @@ -30,10 +30,12 @@ class SvxBrushItem; class SwFormatVertOrient; +enum class SwJumpEditFormat; class SwFieldPortion : public SwExpandPortion { friend class SwTextFormatter; + protected: OUString m_aExpand; // The expanded field std::unique_ptr<SwFont> m_pFont; // For multi-line fields @@ -41,7 +43,7 @@ protected: TextFrameIndex m_nNextScriptChg; TextFrameIndex m_nFieldLen; //< Length of field text, 1 for normal fields, any number for input fields // TODO ^ do we need this as member or is base class len enough? - sal_uInt16 m_nViewWidth; // Screen width for empty fields + SwTwips m_nViewWidth; // Screen width for empty fields bool m_bFollow : 1; // 2nd or later part of a field bool m_bLeft : 1; // Used by SwNumberPortion bool m_bHide : 1; // Used by SwNumberPortion @@ -50,8 +52,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 +61,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; } @@ -77,7 +78,7 @@ public: // Empty fields are also allowed virtual SwLinePortion *Compress() override; - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; bool IsFollow() const { return m_bFollow; } void SetFollow( bool bNew ) { m_bFollow = bNew; } @@ -103,10 +104,15 @@ public: virtual SwFieldPortion *Clone( const OUString &rExpand ) const; // Extra GetTextSize because of pFnt - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override; + + void SetContentControl(bool bContentControl) { m_bContentControl = bContentControl; } + + void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, + TextFrameIndex& nOffset) const override; }; /** @@ -128,19 +134,18 @@ public: class SwNumberPortion : public SwFieldPortion { protected: - sal_uInt16 m_nFixWidth; // See Glues - sal_uInt16 m_nMinDist; // Minimal distance to the text + SwTwips m_nFixWidth; // See Glues + SwTwips m_nMinDist; // Minimal distance to the text bool mbLabelAlignmentPosAndSpaceModeActive; public: SwNumberPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFnt, const bool bLeft, - const bool bCenter, - const sal_uInt16 nMinDst, + const bool bCenter, const SwTwips nMinDst, const bool bLabelAlignmentPosAndSpaceModeActive ); virtual void Paint( const SwTextPaintInfo &rInf ) const override; - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; // Field cloner for SplitGlue @@ -156,7 +161,7 @@ public: std::unique_ptr<SwFont> pFnt, const bool bLeft, const bool bCenter, - const sal_uInt16 nMinDst, + const SwTwips nMinDst, const bool bLabelAlignmentPosAndSpaceModeActive ); }; @@ -175,7 +180,7 @@ public: const Size& rGrfSize, const bool bLeft, const bool bCenter, - const sal_uInt16 nMinDst, + const SwTwips nMinDst, const bool bLabelAlignmentPosAndSpaceModeActive ); virtual ~SwGrfNumPortion() override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; @@ -194,6 +199,8 @@ public: SwTwips GetRelPos() const { return m_nYPos; } SwTwips GetGrfHeight() const { return m_nGrfHeight; } sal_Int16 GetOrient() const { return m_eOrient; } +private: + void ImplDestroy(); }; /** @@ -206,25 +213,25 @@ 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 + SwTwips m_aPos[6]; // up to six X positions + o3tl::enumarray<SwFontScript, SwTwips> 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 + SwTwips m_nUpPos; // the Y position of the upper baseline + SwTwips m_nLowPos; // the Y position of the lower baseline sal_uInt8 m_nProportion; // relative font height public: explicit SwCombinedPortion( const OUString &rExpand ); virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; }; -namespace sw::mark { class IFieldmark; } +namespace sw::mark { class Fieldmark; } class SwFieldFormDropDownPortion : public SwFieldPortion { public: - explicit SwFieldFormDropDownPortion(sw::mark::IFieldmark *pFieldMark, const OUString &rExpand) + explicit SwFieldFormDropDownPortion(sw::mark::Fieldmark *pFieldMark, const OUString &rExpand) : SwFieldPortion(rExpand) , m_pFieldMark(pFieldMark) { @@ -235,14 +242,14 @@ public: virtual void Paint( const SwTextPaintInfo &rInf ) const override; private: - sw::mark::IFieldmark* m_pFieldMark; + sw::mark::Fieldmark* m_pFieldMark; }; class SwFieldFormDatePortion : public SwFieldPortion { public: - explicit SwFieldFormDatePortion(sw::mark::IFieldmark *pFieldMark, bool bStart) - : SwFieldPortion("") + explicit SwFieldFormDatePortion(sw::mark::Fieldmark *pFieldMark, bool bStart) + : SwFieldPortion(u""_ustr) , m_pFieldMark(pFieldMark) , m_bStart(bStart) { @@ -253,8 +260,29 @@ public: virtual void Paint( const SwTextPaintInfo &rInf ) const override; private: - sw::mark::IFieldmark* m_pFieldMark; + sw::mark::Fieldmark* m_pFieldMark; bool m_bStart; }; +class SwJumpFieldPortion final : public SwFieldPortion +{ +public: + explicit SwJumpFieldPortion(OUString aExpand, OUString aHelp, std::unique_ptr<SwFont> pFont, + SwJumpEditFormat 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: + SwJumpEditFormat 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..b0202f7fecca 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(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 SwTwips nNewWidth = 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() : @@ -153,30 +164,32 @@ void SwTextFrame::MoveFlyInCnt(SwTextFrame *pNew, if ( nullptr == pObjs ) return; - for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i ) + size_t i = 0; + while (GetDrawObjs() && i < pObjs->size()) { // 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 ); } - else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr ) + else { RemoveDrawObj( *pAnchoredObj ); pNew->AppendDrawObj( *pAnchoredObj ); } - --i; + continue; // pObjs shrunk } } + ++i; } } @@ -211,8 +224,8 @@ void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const rInf.GetTextFrame()->SwitchHorizontalToVertical(aRepaintRect); if(!((m_pFly->IsCompletePaint() || - m_pFly->getFrameArea().IsOver(aRepaintRect)) && - SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), m_pFly->getRootFrame()->GetCurrShell()))) + m_pFly->getFrameArea().Overlaps(aRepaintRect)) && + SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), *m_pFly->getRootFrame()->GetCurrShell()))) return; SwRect aRect(m_pFly->getFrameArea()); @@ -226,7 +239,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 +249,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 +284,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 +374,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 +405,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() + 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..b22deb6ca4a2 100644 --- a/sw/source/core/text/porftn.hxx +++ b/sw/source/core/text/porftn.hxx @@ -30,18 +30,18 @@ class SwTextFootnote; class SwFootnotePortion : public SwFieldPortion { SwTextFootnote *m_pFootnote; - sal_uInt16 m_nOrigHeight; + SwTwips m_nOrigHeight; // #i98418# bool mbPreferredScriptTypeSet; SwFontScript mnPreferredScriptType; public: SwFootnotePortion( const OUString &rExpand, SwTextFootnote *pFootnote, - sal_uInt16 nOrig = USHRT_MAX ); - sal_uInt16& Orig() { return m_nOrigHeight; } + SwTwips nOrig = std::numeric_limits<SwTwips>::max()); + SwTwips& Orig() { return m_nOrigHeight; } virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; // #i98418# @@ -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; @@ -94,7 +94,7 @@ class SwErgoSumPortion : public SwFieldPortion { public: SwErgoSumPortion( const OUString &rExp, std::u16string_view rStr ); - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; // Field cloner for SplitGlue diff --git a/sw/source/core/text/porglue.cxx b/sw/source/core/text/porglue.cxx index 7c09ded23a2f..cd5eaf2643b5 100644 --- a/sw/source/core/text/porglue.cxx +++ b/sw/source/core/text/porglue.cxx @@ -25,14 +25,14 @@ #include "porfly.hxx" #include <comphelper/string.hxx> -SwGluePortion::SwGluePortion( const sal_uInt16 nInitFixWidth ) +SwGluePortion::SwGluePortion(const SwTwips nInitFixWidth) : m_nFixWidth( nInitFixWidth ) { PrtWidth( m_nFixWidth ); SetWhichPor( PortionType::Glue ); } -TextFrameIndex SwGluePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +TextFrameIndex SwGluePortion::GetModelPositionForViewPoint(const SwTwips nOfst) const { // FIXME why nOfst > GetLen() ? is that supposed to be > Width() ? if( !GetLen() || nOfst > sal_Int32(GetLen()) || !Width() ) @@ -41,12 +41,12 @@ TextFrameIndex SwGluePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfs return TextFrameIndex(nOfst / (Width() / sal_Int32(GetLen()))); } -SwPosSize SwGluePortion::GetTextSize( const SwTextSizeInfo &rInf ) const +SwPositiveSize SwGluePortion::GetTextSize( const SwTextSizeInfo &rInf ) const { if (TextFrameIndex(1) >= GetLen() || rInf.GetLen() > GetLen() || !Width() || !GetLen()) - return SwPosSize(*this); + return SwPositiveSize(*this); else - return SwPosSize((Width() / sal_Int32(GetLen())) * sal_Int32(rInf.GetLen()), Height()); + return SwPositiveSize((Width() / sal_Int32(GetLen())) * sal_Int32(rInf.GetLen()), Height()); } bool SwGluePortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const @@ -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); @@ -87,7 +88,7 @@ void SwGluePortion::Paint( const SwTextPaintInfo &rInf ) const if (TextFrameIndex(1) == GetLen()) { OUString aBullet( CH_BULLET ); - SwPosSize aBulletSize( rInf.GetTextSize( aBullet ) ); + SwPositiveSize aBulletSize( rInf.GetTextSize( aBullet ) ); Point aPos( rInf.GetPos() ); aPos.AdjustX((Width()/2) - (aBulletSize.Width()/2) ); SwTextPaintInfo aInf( rInf, &aBullet ); @@ -128,13 +129,25 @@ 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! */ SwFixPortion::SwFixPortion( const SwRect &rRect ) - :SwGluePortion( sal_uInt16(rRect.Width()) ), m_nFix( sal_uInt16(rRect.Left()) ) + :SwGluePortion(rRect.Width()), m_nFix(rRect.Left()) { - Height( sal_uInt16(rRect.Height()) ); + Height(rRect.Height()); SetWhichPor( PortionType::Fix ); } @@ -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..bf72ac571151 100644 --- a/sw/source/core/text/porglue.hxx +++ b/sw/source/core/text/porglue.hxx @@ -24,36 +24,43 @@ class SwRect; class SwLineLayout; +/// A glue portion is either a base class for other portions that want to have a certain width to +/// push text out (e.g. tab portions) or used to align SwQuoVadisPortion instances on the right for +/// footnotes. class SwGluePortion : public SwLinePortion { private: - sal_uInt16 m_nFixWidth; + SwTwips m_nFixWidth; public: - explicit SwGluePortion( const sal_uInt16 nInitFixWidth ); + explicit SwGluePortion(const SwTwips nInitFixWidth); void Join( SwGluePortion *pVictim ); inline tools::Long GetPrtGlue() const; - sal_uInt16 GetFixWidth() const { return m_nFixWidth; } - void SetFixWidth( const sal_uInt16 nNew ) { m_nFixWidth = nNew; } + SwTwips GetFixWidth() const { return m_nFixWidth; } + void SetFixWidth(const SwTwips nNew) { m_nFixWidth = nNew; } void MoveGlue( SwGluePortion *pTarget, const tools::Long nPrtGlue ); inline void MoveAllGlue( SwGluePortion *pTarget ); inline void MoveHalfGlue( SwGluePortion *pTarget ); inline void AdjFixWidth(); virtual void Paint( const SwTextPaintInfo &rInf ) const override; - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + + void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const override; }; class SwFixPortion : public SwGluePortion { - sal_uInt16 m_nFix; // The width offset in the line + SwTwips m_nFix; // The width offset in the line public: explicit SwFixPortion( const SwRect &rFlyRect ); SwFixPortion(); - void SetFix( const sal_uInt16 nNewFix ) { m_nFix = nNewFix; } - sal_uInt16 GetFix() const { return m_nFix; } + void SetFix(const SwTwips nNewFix) { m_nFix = nNewFix; } + SwTwips 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..a8340089add1 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 @@ -57,7 +60,7 @@ public: class SwSoftHyphPortion : public SwHyphPortion { bool m_bExpand; - sal_uInt16 m_nViewWidth; + SwTwips m_nViewWidth; public: SwSoftHyphPortion(); @@ -69,7 +72,7 @@ public: void SetExpand( const bool bNew ) { m_bExpand = bNew; } bool IsExpand() const { return m_bExpand; } - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override; diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx index 3d26e990c50c..e321a16160dc 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,140 +41,51 @@ #include <optional> #include <editeng/adjustitem.hxx> #include <editeng/charhiddenitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/scripthintitem.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> +#include <i18nutil/kashida.hxx> +#include <i18nutil/scriptchangescanner.hxx> +#include <unotxdoc.hxx> -#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. -static bool isBehChar(sal_Unicode cCh) -{ - bool bRet = false; - switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) - { - 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: - case U_JG_BURUSHASKI_YEH_BARREE: - bRet = true; - break; - default: - bRet = false; - break; - } - - return bRet; -} - -// Yeh and charters that behave like Yeh in final form. -static bool isYehChar(sal_Unicode cCh) -{ - bool bRet = false; - switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) - { - case U_JG_YEH: - case U_JG_FARSI_YEH: - case U_JG_YEH_BARREE: - case U_JG_BURUSHASKI_YEH_BARREE: - case U_JG_YEH_WITH_TAIL: - bRet = true; - break; - default: - bRet = false; - break; - } - - return bRet; -} - -static bool isTransparentChar ( sal_Unicode cCh ) -{ - return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT; -} - -// Checks if cCh + cNectCh builds a ligature (used for Kashidas) -static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) -{ - // Lam + Alef - return ( isLamChar ( cCh ) && isAlefChar ( cNextCh )); -} - -// Checks if cCh is connectable to cPrevCh (used for Kashidas) -static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) -{ - const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE ); - bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING; - - // check for ligatures cPrevChar + cChar - if( bRet ) - bRet = !lcl_IsLigature( cPrevCh, cCh ); - - return bRet; -} - -static bool lcl_HasStrongLTR ( std::u16string_view rText, sal_Int32 nStart, sal_Int32 nEnd ) - { - for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx ) - { - const UCharDirection nCharDir = u_charDirection ( rText[ nCharIdx ] ); - if ( nCharDir == U_LEFT_TO_RIGHT || - nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || - nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) - return true; - } - return false; - } +using namespace ::com::sun::star; +using namespace i18n::ScriptType; // This is (meant to be) functionally equivalent to 'delete m_pNext' where // deleting a SwLineLayout recursively deletes the owned m_pNext SwLineLayout. @@ -185,23 +96,20 @@ 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); + SwPositiveSize::Height(nNew); if (bText) m_nTextHeight = nNew; } @@ -318,30 +226,44 @@ void SwLineLayout::CreateSpaceAdd( const tools::Long nInit ) SetLLSpaceAdd( nInit, 0 ); } -// Returns true if there are only blanks in [nStt, nEnd[ -static bool lcl_HasOnlyBlanks(std::u16string_view rText, TextFrameIndex nStt, TextFrameIndex 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 isFieldMarkPortion) { - 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 0x2003: // EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x3000: // IDEOGRAPHIC SPACE + continue; + case 0x2002: // EN SPACE : + if (isFieldMarkPortion) + return false; + else + continue; + default: + return false; } } - return bBlankOnly; + return true; } // Swapped out from FormatLine() void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) { - const sal_uInt16 nLineWidth = rInf.RealWidth(); + const SwTwips nLineWidth = rInf.RealWidth(); sal_uInt16 nFlyAscent = 0; sal_uInt16 nFlyHeight = 0; sal_uInt16 nFlyDescent = 0; + + // If this line has a clearing break, then this is the portion's height. + sal_uInt16 nBreakHeight = 0; + bool bOnlyPostIts = true; SetHanging( false ); @@ -361,6 +283,12 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) bool bHasBlankPortion = false; bool bHasOnlyBlankPortions = true; + bool bHasTabPortions = false; + bool bHasNonBlankPortions = false; + SwTwips nTabPortionAscent = 0; + SwTwips nTabPortionHeight = 0; + SwTwips nSpacePortionAscent = 0; + SwTwips nSpacePortionHeight = 0; bool bHasFlyPortion = false; if( mpNextPortion ) @@ -374,7 +302,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 +317,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 +329,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,15 +341,41 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) AddPrtWidth( pPos->Width() ); // #i3952# - if ( bIgnoreBlanksAndTabsForLineHeightCalculation ) + if (bIgnoreBlanksAndTabsForLineHeightCalculation) { + bHasTabPortions |= pPos->InTabGrp(); + bool isSpacePortion = false; if ( pPos->InTabGrp() || pPos->IsHolePortion() || ( pPos->IsTextPortion() && - lcl_HasOnlyBlanks( rInf.GetText(), nPorSttIdx, nPorSttIdx + pPos->GetLen() ) ) ) + (isSpacePortion = lcl_HasOnlyBlanks( rInf.GetText(), nPorSttIdx, nPorSttIdx + pPos->GetLen(), + pPos->IsFieldmarkText() ) ) ) ) { pLast = pPos; + if (pPos->InTabGrp()) + { + if (nTabPortionAscent < pPos->GetAscent()) + { + nTabPortionAscent = pPos->GetAscent(); + } + if (nTabPortionHeight < pPos->Height()) + { + nTabPortionHeight = pPos->Height(); + } + } + else if (isSpacePortion) + { + if (nSpacePortionAscent < pPos->GetAscent()) + { + nSpacePortionAscent = pPos->GetAscent(); + } + if (nSpacePortionHeight < pPos->Height()) + { + nSpacePortionHeight = pPos->Height(); + } + bHasBlankPortion = true; + } + bTmpDummy &= !pPos->InTabGrp(); pPos = pPos->GetNextPortion(); - bHasBlankPortion = true; continue; } } @@ -426,7 +383,10 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) // Ignore drop portion height // tdf#130804 ... and bookmark portions if ((pPos->IsDropPortion() && static_cast<SwDropPortion*>(pPos)->GetLines() > 1) - || pPos->GetWhichPor() == PortionType::Bookmark) + || pPos->GetWhichPor() == PortionType::Bookmark + || (pPos->GetWhichPor() == PortionType::HiddenText + && rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::IGNORE_HIDDEN_CHARS_FOR_LINE_CALCULATION))) { pLast = pPos; pPos = pPos->GetNextPortion(); @@ -434,11 +394,12 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) } bHasOnlyBlankPortions = false; + bHasNonBlankPortions = true; // 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 +412,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 +534,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 ); @@ -585,20 +564,70 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) // #i3952# if ( bIgnoreBlanksAndTabsForLineHeightCalculation && - lcl_HasOnlyBlanks( rInf.GetText(), rInf.GetLineStart(), rInf.GetLineStart() + GetLen() ) ) + lcl_HasOnlyBlanks(rInf.GetText(), rInf.GetLineStart(), + rInf.GetLineStart() + GetLen(), + false)) { bHasBlankPortion = true; } + else + { + bHasOnlyBlankPortions = false; + bHasNonBlankPortions = true; + } } - // #i3952# - if ( bHasBlankPortion && bHasOnlyBlankPortions ) + if (!rInf.IsNewLine() + && TextFrameIndex(rInf.GetText().getLength()) <= rInf.GetIdx() + && !bHasNonBlankPortions + && rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH)) + { + // Word: for empty last line, line height is based on paragraph marker + // formatting, ignoring blanks/tabs + rLine.SeekAndChg(rInf); + SetAscent(rInf.GetAscent()); + Height(rInf.GetTextHeight()); + } + else if (bIgnoreBlanksAndTabsForLineHeightCalculation && !bHasNonBlankPortions && + (bHasTabPortions || (bHasBlankPortion && (nSpacePortionAscent > 0 || nSpacePortionHeight > 0)))) + { + //Word increases line height if _only_ spaces and|or tabstops are in a line + if (bHasTabPortions) + { + mnAscent = nTabPortionAscent; + Height(nTabPortionHeight, true); + } + else if (bHasBlankPortion) + { + if( mnAscent < nSpacePortionAscent ) + mnAscent = nSpacePortionAscent; + if (Height() < nSpacePortionHeight) + Height(nSpacePortionHeight, true); + } + } + // #i3952# Whitespace does not increase line height + else 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 +659,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 +755,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,19 +778,28 @@ 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_bRest = m_bBlinking = m_bClipping = m_bContent = m_bRedline - = m_bRedlineEnd = m_bForcedLeftMargin = m_bHanging = false; + 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; } SwLineLayout::SwLineLayout() : m_pNext( nullptr ), m_nRealHeight( 0 ), - m_nTextHeight( 0 ), - m_bUnderscore( false ) + m_nTextHeight( 0 ) { ResetFlags(); SetWhichPor( PortionType::Lay ); @@ -826,19 +868,103 @@ SwFontScript SwScriptInfo::WhichFont(sal_Int32 nIdx, OUString const& rText) return lcl_ScriptToFont(nScript); } +static Color getBookmarkColor(const SwTextNode& rNode, sw::mark::Bookmark* 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, pBookmark); + if (const SwDocShell* pShell = rDoc.GetDocShell()) + { + rtl::Reference<SwXTextDocument> xModel = pShell->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); + + const uno::Reference<rdf::XRepository> xRepository = + xModel->getRDFRepository(); + const uno::Reference<container::XEnumeration> xEnum( + xRepository->getStatements(css::uno::Reference<css::rdf::XResource>(xRef), 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 OUString getBookmarkType(const SwTextNode& rNode, sw::mark::Bookmark* pBookmark) +{ + // search ODF_PREFIX in metadata, otherwise use empty string; + OUString sRet; + + try + { + SwDoc& rDoc = const_cast<SwDoc&>(rNode.GetDoc()); + const rtl::Reference< SwXBookmark > xRef = SwXBookmark::CreateXBookmark(rDoc, pBookmark); + if (const SwDocShell* pShell = rDoc.GetDocShell()) + { + static uno::Reference< uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext()); + + static uno::Reference< rdf::XURI > xODF_PREFIX( + rdf::URI::createKnown(xContext, rdf::URIs::RDF_TYPE), uno::UNO_SET_THROW); + + rtl::Reference<SwXTextDocument> xDocumentMetadataAccess( + pShell->GetBaseModel()); + const uno::Reference<rdf::XRepository> xRepository = + xDocumentMetadataAccess->getRDFRepository(); + const uno::Reference<container::XEnumeration> xEnum( + xRepository->getStatements(css::uno::Reference<css::rdf::XResource>(xRef), xODF_PREFIX, 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() ) + sRet = xObject->getValue(); + } + } + } + catch (const lang::IllegalArgumentException&) + { + } + + return sRet; +} + 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::pair<sw::mark::Bookmark*, SwScriptInfo::MarkKind>> & rBookmarks, + std::vector<std::tuple<TextFrameIndex, SwScriptInfo::MarkKind, Color, SwMarkName, 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); + OUString sType = getBookmarkType(*pNode, it.first); switch (it.second) { case SwScriptInfo::MarkKind::Start: @@ -852,41 +978,40 @@ static void InitBookmarks( // start of first[/end of last] extent, and the other one // is outside this merged paragraph, is it deleted or not? // assume "no" because the line break it contains isn't deleted. - SwPosition const& rStart(it.first->GetMarkStart()); - SwPosition const& rEnd(it.first->GetMarkEnd()); - assert(&rStart.nNode.GetNode() == pNode); + auto [/*const SwPosition&*/ rStart, rEnd] = it.first->GetMarkStartEnd(); + 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(), sType); 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(), sType); break; } } @@ -899,13 +1024,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(), sType); } } break; @@ -913,36 +1038,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(), sType); 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(), sType); break; } else @@ -957,26 +1082,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(), sType); } break; } @@ -990,6 +1115,10 @@ static void InitBookmarks( break; } } + if (iter == end) + { + break; // remaining marks are hidden + } } } @@ -1000,8 +1129,9 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, InitScriptInfo( rNode, pMerged, m_nDefaultDir == UBIDI_RTL ); } -void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, - sw::MergedPara const*const pMerged, bool bRTL) +// note: must not use pMerged->pParaPropsNode to avoid circular dependency +void SwScriptInfo::InitScriptInfoHidden(const SwTextNode& rNode, + sw::MergedPara const*const pMerged) { assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); @@ -1016,18 +1146,30 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, SwTextNode const* pNode(nullptr); TextFrameIndex nOffset(0); std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter; - for (auto iter = pMerged->extents.begin(); iter != pMerged->extents.end(); - oPrevIter = iter, ++iter) + if (pMerged->extents.empty()) + { + Range aRange(0, pMerged->pLastNode->Len() > 0 ? pMerged->pLastNode->Len() - 1 : 0); + MultiSelection aHiddenMulti(aRange); + CalcHiddenRanges(*pMerged->pLastNode, aHiddenMulti, nullptr); + if (aHiddenMulti.GetRangeCount() != 0) + { + m_HiddenChg.push_back(TextFrameIndex(0)); + m_HiddenChg.push_back(TextFrameIndex(0)); + } + } + else for (auto iter = pMerged->extents.begin(); + iter != pMerged->extents.end(); oPrevIter = iter) { if (iter->pNode == pNode) { nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + ++iter; continue; // skip extents at end of previous node } pNode = iter->pNode; Range aRange( 0, pNode->Len() > 0 ? pNode->Len() - 1 : 0 ); MultiSelection aHiddenMulti( aRange ); - std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks; + std::vector<std::pair<sw::mark::Bookmark*, MarkKind>> bookmarks; CalcHiddenRanges(*pNode, aHiddenMulti, &bookmarks); InitBookmarks(oPrevIter, iter, pMerged->extents.end(), nOffset, bookmarks, m_Bookmarks); @@ -1037,74 +1179,159 @@ 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 { Range aRange( 0, !rText.isEmpty() ? rText.getLength() - 1 : 0 ); MultiSelection aHiddenMulti( aRange ); - std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks; + std::vector<std::pair<sw::mark::Bookmark*, MarkKind>> bookmarks; CalcHiddenRanges(rNode, aHiddenMulti, &bookmarks); for (auto const& it : bookmarks) { + // don't show __RefHeading__ bookmarks, which are hidden in Navigator, too + // (They are inserted automatically e.g. with the ToC at the beginning of + // the headings) + if (it.first->GetName().toString().startsWith( + IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix())) + { + continue; + } + + // search for custom bookmark boundary mark color + Color c = getBookmarkColor(rNode, it.first); + OUString sType = getBookmarkType(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(), sType); 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(), sType); 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(), sType); 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) ); } } +} + +namespace +{ +i18nutil::ScriptHintProvider lcl_FindScriptTypeHintSpans(const SwTextNode& rNode) +{ + i18nutil::ScriptHintProvider stProvider; + + const SvxScriptHintItem* pItem = rNode.GetSwAttrSet().GetItemIfSet(RES_CHRATR_SCRIPT_HINT); + if (pItem) + { + stProvider.SetParagraphLevelHint(pItem->GetValue()); + } + + const SwpHints* pHints = rNode.GetpSwpHints(); + if (pHints) + { + for (size_t nTmp = 0; nTmp < pHints->Count(); ++nTmp) + { + const SwTextAttr* pTextAttr = pHints->Get(nTmp); + const SvxScriptHintItem* pCharItem + = CharFormat::GetItem(*pTextAttr, RES_CHRATR_SCRIPT_HINT); + if (pCharItem) + { + const sal_Int32 nSt = pTextAttr->GetStart(); + const sal_Int32 nEnd = *pTextAttr->End(); + if (nEnd > nSt) + { + stProvider.AddHint(pCharItem->GetValue(), nSt, nEnd); + } + } + } + } + + return stProvider; +} +} + +void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, + sw::MergedPara const*const pMerged, bool bRTL) +{ + InitScriptInfoHidden(rNode, pMerged); + + const OUString& rText(pMerged ? pMerged->mergedText : rNode.GetText()); // SCRIPT AND SCRIPT RELATED INFORMATION @@ -1120,8 +1347,6 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, size_t nCnt = 0; // counter for compression information arrays size_t nCntComp = 0; - // counter for kashida array - size_t nCntKash = 0; sal_Int16 nScript = i18n::ScriptType::LATIN; @@ -1130,7 +1355,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, auto const& rParaItems((pMerged ? *pMerged->pParaPropsNode : rNode).GetSwAttrSet()); // justification type - const bool bAdjustBlock = SvxAdjust::Block == rParaItems.GetAdjust().GetAdjust(); + m_bAdjustBlock = (SvxAdjust::Block == rParaItems.GetAdjust().GetAdjust()); // FIND INVALID RANGES IN SCRIPT INFO ARRAYS: @@ -1159,15 +1384,6 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, nCntComp++; } } - if ( bAdjustBlock ) - { - while( nCntKash < CountKashida() ) - { - if ( nChg <= GetKashida( nCntKash ) ) - break; - nCntKash++; - } - } } // ADJUST nChg VALUE: @@ -1212,51 +1428,20 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, m_CompressionChanges.erase(m_CompressionChanges.begin() + nCntComp, m_CompressionChanges.end()); - // get the start of the last kashida group - TextFrameIndex nLastKashida = nChg; - if( nCntKash && i18n::ScriptType::COMPLEX == nScript ) + // Construct the script change scanner and advance it to the change range + auto stScriptHints = lcl_FindScriptTypeHintSpans(rNode); + auto pDirScanner = i18nutil::MakeDirectionChangeScanner(rText, m_nDefaultDir); + auto pScriptScanner = i18nutil::MakeScriptChangeScanner( + rText, SvtLanguageOptions::GetI18NScriptTypeOfLanguage(GetAppLanguage()), *pDirScanner, + stScriptHints); + while (!pScriptScanner->AtEnd()) { - --nCntKash; - nLastKashida = GetKashida( nCntKash ); - } - - // remove invalid entries from kashida array - m_Kashida.erase(m_Kashida.begin() + nCntKash, m_Kashida.end()); - - // TAKE CARE OF WEAK CHARACTERS: WE MUST FIND AN APPROPRIATE - // SCRIPT FOR WEAK CHARACTERS AT THE BEGINNING OF A PARAGRAPH - - if (WEAK == g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg))) - { - // If the beginning of the current group is weak, this means that - // all of the characters in this group are weak. We have to assign - // the scripts to these characters depending on the fonts which are - // set for these characters to display them. - TextFrameIndex nEnd( - g_pBreakIt->GetBreakIter()->endOfScript(rText, sal_Int32(nChg), WEAK)); - - if (nEnd > TextFrameIndex(rText.getLength()) || nEnd < TextFrameIndex(0)) - nEnd = TextFrameIndex(rText.getLength()); - - nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); - - SAL_WARN_IF( i18n::ScriptType::LATIN != nScript && - i18n::ScriptType::ASIAN != nScript && - i18n::ScriptType::COMPLEX != nScript, "sw.core", "Wrong default language" ); - - nChg = nEnd; - - // Get next script type or set to weak in order to exit - sal_uInt8 nNextScript = (nEnd < TextFrameIndex(rText.getLength())) - ? static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nEnd))) - : sal_uInt8(WEAK); - - if ( nScript != nNextScript ) + if (static_cast<sal_Int32>(nChg) < pScriptScanner->Peek().m_nEndIndex) { - m_ScriptChanges.emplace_back(nEnd, nScript); - nCnt++; - nScript = nNextScript; + break; } + + pScriptScanner->Advance(); } // UPDATE THE SCRIPT INFO ARRAYS: @@ -1264,60 +1449,12 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, while (nChg < TextFrameIndex(rText.getLength()) || (m_ScriptChanges.empty() && rText.isEmpty())) { - SAL_WARN_IF( i18n::ScriptType::WEAK == nScript, - "sw.core", "Inserting WEAK into SwScriptInfo structure" ); - - TextFrameIndex nSearchStt = nChg; - nChg = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfScript( - rText, sal_Int32(nSearchStt), nScript)); - - if (nChg > TextFrameIndex(rText.getLength()) || nChg < TextFrameIndex(0)) - nChg = TextFrameIndex(rText.getLength()); - - // #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 ); - } + auto stChange = pScriptScanner->Peek(); + pScriptScanner->Advance(); - // 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))) - { - 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 - { - m_ScriptChanges.emplace_back(nChg, nScript); - } - } - else - { - m_ScriptChanges.emplace_back(nChg, nScript); - } + nScript = stChange.m_nScriptType; + nChg = TextFrameIndex{ stChange.m_nEndIndex }; + m_ScriptChanges.emplace_back(nChg, nScript); ++nCnt; // if current script is asian, we search for compressible characters @@ -1340,6 +1477,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 +1485,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: @@ -1389,315 +1529,35 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, } } } - - // we search for connecting opportunities (kashida) - else if ( bAdjustBlock && i18n::ScriptType::COMPLEX == nScript ) + else if (m_bAdjustBlock && i18n::ScriptType::COMPLEX == nScript) { - // sw_redlinehide: this is the only place that uses SwScanner with - // frame text, so we convert to sal_Int32 here - std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfCharM( - [&pMerged](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar) - { - std::pair<SwTextNode const*, sal_Int32> const pos( - sw::MapViewToModel(*pMerged, TextFrameIndex(nBegin))); - return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, script); - }); - std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfChar1( - [&rNode](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar) - { return rNode.GetLang(nBegin, bNoChar ? 0 : 1, script); }); - auto pGetLangOfChar(pMerged ? pGetLangOfCharM : pGetLangOfChar1); - SwScanner aScanner( pGetLangOfChar, rText, nullptr, ModelToViewHelper(), - i18n::WordType::DICTIONARY_WORD, - sal_Int32(nLastKashida), sal_Int32(nChg)); - - // the search has to be performed on a per word base - while ( aScanner.NextWord() ) + if (SwScriptInfo::IsKashidaScriptText( + rText, TextFrameIndex{ stChange.m_nStartIndex }, + TextFrameIndex{ stChange.m_nEndIndex - stChange.m_nStartIndex })) { - const OUString& rWord = aScanner.GetWord(); - - sal_Int32 nIdx = 0; - sal_Int32 nKashidaPos = -1; - sal_Unicode cCh; - sal_Unicode cPrevCh = 0; - - int nPriorityLevel = 7; // 0..6 = level found - // 7 not found - - sal_Int32 nWordLen = rWord.getLength(); - - // ignore trailing vowel chars - while( nWordLen && isTransparentChar( rWord[ nWordLen - 1 ] )) - --nWordLen; - - while (nIdx < nWordLen) - { - cCh = rWord[ nIdx ]; - - // 1. Priority: - // after user inserted kashida - if ( 0x640 == cCh ) - { - nKashidaPos = aScanner.GetBegin() + nIdx; - nPriorityLevel = 0; - } - - // 2. Priority: - // after a Seen or Sad - if (nPriorityLevel >= 1 && nIdx < nWordLen - 1) - { - if( isSeenOrSadChar( cCh ) - && (rWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion - { - nKashidaPos = aScanner.GetBegin() + nIdx; - nPriorityLevel = 1; - } - } - - // 3. Priority: - // before final form of Teh Marbuta, Heh, Dal - if ( nPriorityLevel >= 2 && nIdx > 0 ) - { - if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining) - isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word - ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word - { - - SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) - { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; - nPriorityLevel = 2; - } - } - } - - // 4. Priority: - // before final form of Alef, Tah, Lam, Kaf or Gaf - if ( nPriorityLevel >= 3 && nIdx > 0 ) - { - if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word - (( isLamChar ( cCh ) || // Lam, - isTahChar ( cCh ) || // Tah, - isKafChar ( cCh ) || // Kaf (all dual joining) - isGafChar ( cCh ) ) - && nIdx == nWordLen - 1)) // only at end of word - { - SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) - { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; - nPriorityLevel = 3; - } - } - } - - // 5. Priority: - // before medial Beh-like - if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 ) - { - if ( isBehChar ( cCh ) ) - { - // check if next character is Reh or Yeh-like - sal_Unicode cNextCh = rWord[ nIdx + 1 ]; - if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh )) - { - SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) - { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; - nPriorityLevel = 4; - } - } - } - } - - // 6. Priority: - // before the final form of Waw, Ain, Qaf and Feh - if ( nPriorityLevel >= 5 && nIdx > 0 ) - { - if ( isWawChar ( cCh ) || // Wav (right joining) - // final form may appear in the middle of word - (( isAinChar ( cCh ) || // Ain (dual joining) - isQafChar ( cCh ) || // Qaf (dual joining) - isFehChar ( cCh ) ) // Feh (dual joining) - && nIdx == nWordLen - 1)) // only at end of word - { - SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) - { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; - nPriorityLevel = 5; - } - } - } - - // other connecting possibilities - if ( nPriorityLevel >= 6 && nIdx > 0 ) - { - // Reh, Zain - if ( isRehChar ( cCh ) ) - { - SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) - { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; - nPriorityLevel = 6; - } - } - } - - // Do not consider vowel marks when checking if a character - // can be connected to previous character. - if ( !isTransparentChar ( cCh) ) - cPrevCh = cCh; - - ++nIdx; - } // end of current word - - if ( -1 != nKashidaPos ) - { - m_Kashida.insert(m_Kashida.begin() + nCntKash, TextFrameIndex(nKashidaPos)); - nCntKash++; - } - } // end of kashida search + m_bParagraphContainsKashidaScript = true; + } } if (nChg < TextFrameIndex(rText.getLength())) nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg))); nLastCompression = nChg; - nLastKashida = nChg; } -#if OSL_DEBUG_LEVEL > 0 - // check kashida data - TextFrameIndex nTmpKashidaPos(-1); - bool bWrongKash = false; - for (size_t i = 0; i < m_Kashida.size(); ++i) - { - TextFrameIndex nCurrKashidaPos = GetKashida( i ); - if ( nCurrKashidaPos <= nTmpKashidaPos ) - { - bWrongKash = true; - break; - } - nTmpKashidaPos = nCurrKashidaPos; - } - SAL_WARN_IF( bWrongKash, "sw.core", "Kashida array contains wrong data" ); -#endif - // remove invalid entries from direction information arrays m_DirectionChanges.clear(); // Perform Unicode Bidi Algorithm for text direction information + pDirScanner->Reset(); + while (!pDirScanner->AtEnd()) { - UpdateBidiInfo( rText ); - - // #i16354# Change script type for RTL text to CTL: - // 1. All text in RTL runs will use the CTL font - // #i89825# change the script type also to CTL (hennerdrewes) - // 2. Text in embedded LTR runs that does not have any strong LTR characters (numbers!) - for (size_t nDirIdx = 0; nDirIdx < m_DirectionChanges.size(); ++nDirIdx) - { - const sal_uInt8 nCurrDirType = GetDirType( nDirIdx ); - // nStart is start of RTL run: - const TextFrameIndex nStart = nDirIdx > 0 ? GetDirChg(nDirIdx - 1) : TextFrameIndex(0); - // nEnd is end of RTL run: - const TextFrameIndex nEnd = GetDirChg( nDirIdx ); - - if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run - (nCurrDirType > UBIDI_LTR && // non-strong text in embedded LTR run - !lcl_HasStrongLTR(rText, sal_Int32(nStart), sal_Int32(nEnd)))) - { - // nScriptIdx points into the ScriptArrays: - size_t nScriptIdx = 0; - - // Skip entries in ScriptArray which are not inside the RTL run: - // Make nScriptIdx become the index of the script group with - // 1. nStartPosOfGroup <= nStart and - // 2. nEndPosOfGroup > nStart - while ( GetScriptChg( nScriptIdx ) <= nStart ) - ++nScriptIdx; - - const TextFrameIndex nStartPosOfGroup = nScriptIdx - ? GetScriptChg(nScriptIdx - 1) - : TextFrameIndex(0); - const sal_uInt8 nScriptTypeOfGroup = GetScriptType( nScriptIdx ); - - SAL_WARN_IF( nStartPosOfGroup > nStart || GetScriptChg( nScriptIdx ) <= nStart, - "sw.core", "Script override with CTL font trouble" ); - - // Check if we have to insert a new script change at - // position nStart. If nStartPosOfGroup < nStart, - // we have to insert a new script change: - if (nStart > TextFrameIndex(0) && nStartPosOfGroup < nStart) - { - m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx, - ScriptChangeInfo(nStart, nScriptTypeOfGroup) ); - ++nScriptIdx; - } - - // Remove entries in ScriptArray which end inside the RTL run: - while (nScriptIdx < m_ScriptChanges.size() - && GetScriptChg(nScriptIdx) <= nEnd) - { - m_ScriptChanges.erase(m_ScriptChanges.begin() + nScriptIdx); - } - - // Insert a new entry in ScriptArray for the end of the RTL run: - m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx, - ScriptChangeInfo(nEnd, i18n::ScriptType::COMPLEX) ); - -#if OSL_DEBUG_LEVEL > 1 - // Check that ScriptChangeInfos are in increasing order of - // position and that we don't have "empty" changes. - sal_uInt8 nLastTyp = i18n::ScriptType::WEAK; - TextFrameIndex nLastPos = TextFrameIndex(0); - for (const auto& rScriptChange : m_ScriptChanges) - { - SAL_WARN_IF( nLastTyp == rScriptChange.type || - nLastPos >= rScriptChange.position, - "sw.core", "Heavy InitScriptType() confusion" ); - nLastPos = rScriptChange.position; - nLastTyp = rScriptChange.type; - } -#endif - } - } - } -} - -void SwScriptInfo::UpdateBidiInfo( const OUString& rText ) -{ - // remove invalid entries from direction information arrays - m_DirectionChanges.clear(); - - // Bidi functions from icu 2.0 + auto stDirChange = pDirScanner->Peek(); + m_DirectionChanges.emplace_back(TextFrameIndex{ stDirChange.m_nEndIndex }, + stDirChange.m_nLevel); - UErrorCode nError = U_ZERO_ERROR; - UBiDi* pBidi = ubidi_openSized( rText.getLength(), 0, &nError ); - nError = U_ZERO_ERROR; - - ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(rText.getStr()), rText.getLength(), - m_nDefaultDir, nullptr, &nError ); - nError = U_ZERO_ERROR; - int nCount = ubidi_countRuns( pBidi, &nError ); - int32_t nStart = 0; - int32_t nEnd; - UBiDiLevel nCurrDir; - for ( int nIdx = 0; nIdx < nCount; ++nIdx ) - { - ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir ); - m_DirectionChanges.emplace_back(TextFrameIndex(nEnd), nCurrDir); - nStart = nEnd; + pDirScanner->Advance(); } - - ubidi_close( pBidi ); } // returns the position of the next character which belongs to another script @@ -1776,29 +1636,48 @@ 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, SwMarkName, OUString>> + SwScriptInfo::GetBookmarks(TextFrameIndex const nPos) { - MarkKind ret{0}; + std::vector<std::tuple<SwScriptInfo::MarkKind, Color, SwMarkName, OUString>> aColors; for (auto const& it : m_Bookmarks) { - if (nPos == it.first) + if (nPos == std::get<0>(it)) { - ret |= it.second; + const SwMarkName& sName = std::get<3>(it); + // filter hidden bookmarks imported from OOXML + // TODO import them as hidden bookmarks + if ( !( sName.toString().startsWith("_Toc") || sName.toString().startsWith("_Ref") ) ) + aColors.push_back(std::tuple<MarkKind, Color, SwMarkName, + OUString>(std::get<1>(it), std::get<2>(it), std::get<3>(it), std::get<4>(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, SwMarkName, OUString> const a, + std::tuple<MarkKind, Color, SwMarkName, 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 +1898,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 @@ -2054,8 +1933,8 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, if( nIdx > nLen || nCompIdx >= nCompCount ) return 0; - tools::Long nSub = 0; - tools::Long nLast = nI ? pKernArray[ nI - 1 ] : 0; + double nSub = 0; + double nLast = nI ? rKernArray[ nI - 1 ] : 0; do { const CompType nType = GetCompType( nCompIdx ); @@ -2067,7 +1946,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,10 +1957,10 @@ 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; + double nMove = 0; if( SwScriptInfo::KANA != nType ) { nLast /= 24000; @@ -2101,10 +1980,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[nI - 1] += nMove; + rKernArray[nI] += -nSub; + ++nI; ++nIdx; } } @@ -2123,111 +2003,27 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, while( nIdx < nTmpChg ) { - nLast = pKernArray[ nI ]; - pKernArray[ nI++ ] -= nSub; + nLast = rKernArray[ nI ]; + rKernArray[nI] += -nSub; + ++nI; ++nIdx; } } while( nIdx < nLen ); return nSub; } -// Note on calling KashidaJustify(): -// Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean -// total number of kashida positions, or the number of kashida positions after some positions -// have been dropped, depending on the state of the m_KashidaInvalid set. - -sal_Int32 SwScriptInfo::KashidaJustify( tools::Long* pKernArray, - tools::Long* pScrArray, - TextFrameIndex const nStt, - TextFrameIndex const nLen, - tools::Long nSpaceAdd ) const -{ - SAL_WARN_IF( !nLen, "sw.core", "Kashida justification without text?!" ); - - if( !IsKashidaLine(nStt)) - return -1; - - // evaluate kashida information in collected in SwScriptInfo - - size_t nCntKash = 0; - while( nCntKash < CountKashida() ) - { - if ( nStt <= GetKashida( nCntKash ) ) - break; - ++nCntKash; - } - - const TextFrameIndex nEnd = nStt + nLen; - - size_t nCntKashEnd = nCntKash; - while ( nCntKashEnd < CountKashida() ) - { - if ( nEnd <= GetKashida( nCntKashEnd ) ) - break; - ++nCntKashEnd; - } - - size_t nActualKashCount = nCntKashEnd - nCntKash; - for (size_t i = nCntKash; i < nCntKashEnd; ++i) - { - if ( nActualKashCount && !IsKashidaValid ( i ) ) - --nActualKashCount; - } - - if ( !pKernArray ) - return nActualKashCount; - - // do nothing if there is no more kashida - if ( nCntKash < CountKashida() ) - { - // skip any invalid kashidas - while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash)) - ++nCntKash; - - TextFrameIndex nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) - ? GetKashida(nCntKash) - : nEnd; - tools::Long nKashAdd = nSpaceAdd; - - while ( nIdx < nEnd ) - { - TextFrameIndex nArrayPos = nIdx - nStt; - - // next kashida position - ++nCntKash; - while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash)) - ++nCntKash; - - nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) ? GetKashida(nCntKash) : nEnd; - if ( nIdx > nEnd ) - nIdx = nEnd; - - const TextFrameIndex nArrayEnd = nIdx - nStt; - - while ( nArrayPos < nArrayEnd ) - { - pKernArray[ sal_Int32(nArrayPos) ] += nKashAdd; - if ( pScrArray ) - pScrArray[ sal_Int32(nArrayPos) ] += nKashAdd; - ++nArrayPos; - } - nKashAdd += nSpaceAdd; - } - } - - return 0; -} - -// Checks if the current text is 'Arabic' text. Note that only the first +// Checks if the text is in Arabic or Syriac. Note that only the first // character has to be checked because a ctl portion only contains one // script, see NewTextPortion -bool SwScriptInfo::IsArabicText(const OUString& rText, +bool SwScriptInfo::IsKashidaScriptText(const OUString& rText, TextFrameIndex const nStt, TextFrameIndex const nLen) { using namespace ::com::sun::star::i18n; static const ScriptTypeList typeList[] = { - { UnicodeScript_kArabic, UnicodeScript_kArabic, sal_Int16(UnicodeScript_kArabic) }, // 11, - { UnicodeScript_kScriptCount, UnicodeScript_kScriptCount, sal_Int16(UnicodeScript_kScriptCount) } // 88 + { UnicodeScript_kArabic, UnicodeScript_kArabic, sal_Int16(UnicodeScript_kArabic) }, // 11, + { UnicodeScript_kSyriac, UnicodeScript_kSyriac, sal_Int16(UnicodeScript_kSyriac) }, // 12, + { UnicodeScript_kScriptCount, UnicodeScript_kScriptCount, + sal_Int16(UnicodeScript_kScriptCount) } // 88 }; // go forward if current position does not hold a regular character: @@ -2253,155 +2049,38 @@ bool SwScriptInfo::IsArabicText(const OUString& rText, { const sal_Unicode cCh = rText[nIdx]; const sal_Int16 type = unicode::getUnicodeScriptType( cCh, typeList, sal_Int16(UnicodeScript_kScriptCount) ); - return type == sal_Int16(UnicodeScript_kArabic); - } - return false; -} - -bool SwScriptInfo::IsKashidaValid(size_t const nKashPos) const -{ - return m_KashidaInvalid.find(nKashPos) == m_KashidaInvalid.end(); -} - -void SwScriptInfo::ClearKashidaInvalid(size_t const nKashPos) -{ - m_KashidaInvalid.erase(nKashPos); -} - -// bMark == true: -// marks the first valid kashida in the given text range as invalid -// bMark == false: -// clears all kashida invalid flags in the given text range -bool SwScriptInfo::MarkOrClearKashidaInvalid( - TextFrameIndex const nStt, TextFrameIndex const nLen, - bool bMark, sal_Int32 nMarkCount) -{ - size_t nCntKash = 0; - while( nCntKash < CountKashida() ) - { - if ( nStt <= GetKashida( nCntKash ) ) - break; - nCntKash++; - } - - const TextFrameIndex nEnd = nStt + nLen; - - while ( nCntKash < CountKashida() ) - { - if ( nEnd <= GetKashida( nCntKash ) ) - break; - if(bMark) - { - if ( MarkKashidaInvalid ( nCntKash ) ) - { - --nMarkCount; - if (!nMarkCount) - return true; - } - } - else - { - ClearKashidaInvalid ( nCntKash ); - } - nCntKash++; + return type == sal_Int16(UnicodeScript_kArabic) || type == sal_Int16(UnicodeScript_kSyriac); } return false; } -bool SwScriptInfo::MarkKashidaInvalid(size_t const nKashPos) -{ - return m_KashidaInvalid.insert(nKashPos).second; -} - -// retrieve the kashida positions in the given text range -void SwScriptInfo::GetKashidaPositions( - TextFrameIndex const nStt, TextFrameIndex const nLen, - std::vector<TextFrameIndex>& rKashidaPosition) +tools::Long SwScriptInfo::CountKashidaPositions(TextFrameIndex nIdx, TextFrameIndex nEnd) const { - size_t nCntKash = 0; - while( nCntKash < CountKashida() ) - { - if ( nStt <= GetKashida( nCntKash ) ) - break; - nCntKash++; - } - - const TextFrameIndex nEnd = nStt + nLen; - - size_t nCntKashEnd = nCntKash; - while ( nCntKashEnd < CountKashida() ) + tools::Long nCount = 0; + for (const auto& nPos : m_Kashida) { - if ( nEnd <= GetKashida( nCntKashEnd ) ) + if (nPos >= nEnd) break; - rKashidaPosition.push_back(GetKashida(nCntKashEnd)); - nCntKashEnd++; - } -} - -void SwScriptInfo::SetNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen) -{ - m_NoKashidaLine.push_back( nStt ); - m_NoKashidaLineEnd.push_back( nStt + nLen ); -} -// determines if the line uses kashida justification -bool SwScriptInfo::IsKashidaLine(TextFrameIndex const nCharIdx) const -{ - for (size_t i = 0; i < m_NoKashidaLine.size(); ++i) - { - if (nCharIdx >= m_NoKashidaLine[i] && nCharIdx < m_NoKashidaLineEnd[i]) - return false; + if (nPos >= nIdx) + ++nCount; } - return true; -} -void SwScriptInfo::ClearNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen) -{ - size_t i = 0; - while (i < m_NoKashidaLine.size()) - { - if (nStt + nLen >= m_NoKashidaLine[i] && nStt < m_NoKashidaLineEnd[i]) - { - m_NoKashidaLine.erase(m_NoKashidaLine.begin() + i); - m_NoKashidaLineEnd.erase(m_NoKashidaLineEnd.begin() + i); - } - else - ++i; - } + return nCount; } -// mark the given character indices as invalid kashida positions -void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt, - const TextFrameIndex* pKashidaPositions) +void SwScriptInfo::ReplaceKashidaPositions(std::vector<TextFrameIndex> aKashidaPositions) { - SAL_WARN_IF( !pKashidaPositions || nCnt == 0, "sw.core", "Where are kashidas?" ); - - size_t nCntKash = 0; - sal_Int32 nKashidaPosIdx = 0; - - while (nCntKash < CountKashida() && nKashidaPosIdx < nCnt) - { - if ( pKashidaPositions [nKashidaPosIdx] > GetKashida( nCntKash ) ) - { - ++nCntKash; - continue; - } - - if ( pKashidaPositions [nKashidaPosIdx] != GetKashida( nCntKash ) || !IsKashidaValid ( nCntKash ) ) - return; // something is wrong - - MarkKashidaInvalid ( nCntKash ); - nKashidaPosIdx++; - } + m_Kashida = std::move(aKashidaPositions); } -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 +2090,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 +2106,8 @@ TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pK ++nCnt; } - if ( pKernArray ) pKernArray[ nI ] += nSpaceSum; - if ( pScrArray ) pScrArray[ nI ] += nSpaceSum; + if (pKernArray) + (*pKernArray)[nI] += nSpaceSum; } return nCnt; @@ -2443,7 +2122,7 @@ SwScriptInfo* SwScriptInfo::GetScriptInfo( const SwTextNode& rTNd, for( SwTextFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) { - pScriptInfo = const_cast<SwScriptInfo*>(pLast->GetScriptInfo()); + pScriptInfo = pLast->GetScriptInfo(); if ( pScriptInfo ) { if (bAllowInvalid || @@ -2485,6 +2164,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 +2215,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,16 +2281,41 @@ 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) + std::vector<std::pair<sw::mark::Bookmark*, MarkKind>> *const pBookmarks) { 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,10 +2341,12 @@ 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); + if (!pIndex->GetOwner() || pIndex->GetOwner()->GetOwnerType() != SwContentIndexOwnerType::Mark) + continue; + auto pMark = static_cast<sw::mark::MarkBase*>(pIndex->GetOwner()); + sw::mark::Bookmark* pBookmark = dynamic_cast<sw::mark::Bookmark*>(pMark); if (pBookmarks && pBookmark) { if (!pBookmark->IsExpanded()) @@ -2614,33 +2364,14 @@ 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(); + auto [/*const SwPosition&*/ rMarkStartPos, rMarkEndPos] = pBookmark->GetMarkStartEnd(); + const sal_Int32 nSt = rMarkStartPos.GetContentIndex(); + const sal_Int32 nEnd = rMarkEndPos.GetContentIndex(); if( nEnd > nSt ) { @@ -2666,7 +2397,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) @@ -2688,7 +2419,7 @@ void SwScriptInfo::selectRedLineDeleted(const SwTextNode& rNode, MultiSelection // Returns a MultiSection indicating the hidden ranges. void SwScriptInfo::CalcHiddenRanges( const SwTextNode& rNode, MultiSelection & rHiddenMulti, - std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks) + std::vector<std::pair<sw::mark::Bookmark*, MarkKind>> *const pBookmarks) { selectHiddenTextProperty(rNode, rHiddenMulti, pBookmarks); @@ -2734,12 +2465,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 +2488,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[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..afb8534de477 100644 --- a/sw/source/core/text/porlay.hxx +++ b/sw/source/core/text/porlay.hxx @@ -75,18 +75,22 @@ 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 + std::vector<TextFrameIndex> m_aKashida; + 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 + SwTwips m_nExtraAscent = 0; + SwTwips m_nExtraDescent = 0; 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; @@ -96,7 +100,6 @@ private: bool m_bRedlineEnd: 1; // Redlining for paragraph mark: tracked change at the end bool m_bForcedLeftMargin : 1; // Left adjustment moved by the Fly bool m_bHanging : 1; // Contains a hanging portion in the margin - bool m_bUnderscore : 1; enum RedlineType m_eRedlineEnd; // redline type of pilcrow and line break symbols @@ -106,14 +109,14 @@ private: void DeleteNext(); public: - // From SwPosSize - using SwPosSize::Height; - virtual void Height(const sal_uInt16 nNew, const bool bText = true) override; + // From SwPositiveSize + using SwPositiveSize::Height; + virtual void Height(const SwTwips nNew, const bool bText = true) override; // From SwLinePortion virtual SwLinePortion *Insert( SwLinePortion *pPortion ) override; virtual SwLinePortion *Append( SwLinePortion *pPortion ) override; - SwLinePortion *GetFirstPortion() const; + SW_DLLPUBLIC SwLinePortion *GetFirstPortion() const; // Flags void ResetFlags(); @@ -123,6 +126,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; } @@ -143,8 +148,6 @@ public: bool HasForcedLeftMargin() const { return m_bForcedLeftMargin; } void SetHanging( const bool bNew ) { m_bHanging = bNew; } bool IsHanging() const { return m_bHanging; } - void SetUnderscore( const bool bNew ) { m_bUnderscore = bNew; } - bool HasUnderscore() const { return m_bUnderscore; } // Respecting empty dummy lines void SetDummy( const bool bNew ) { m_bDummy = bNew; } @@ -165,10 +168,16 @@ 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; } + + void SetExtraAscent(SwTwips nNew) { m_nExtraAscent = nNew; } + SwTwips GetExtraAscent() const { return m_nExtraAscent; } + + void SetExtraDescent(SwTwips nNew) { m_nExtraDescent = nNew; } + SwTwips GetExtraDescent() const { return m_nExtraDescent; } // Creates the glue chain for short lines SwMarginPortion *CalcLeftMargin(); @@ -202,6 +211,9 @@ public: std::deque<sal_uInt16>* GetpKanaComp() const { return m_pKanaComp.get(); } std::deque<sal_uInt16>& GetKanaComp() { return *m_pKanaComp; } + void SetKashida(std::vector<TextFrameIndex> aNew) { m_aKashida = std::move(aNew); } + std::span<const TextFrameIndex> GetKashida() const { return m_aKashida; } + /** determine ascent and descent for positioning of as-character anchored object @@ -240,6 +252,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 +280,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 +298,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 +331,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..f02fc0bae0f0 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 @@ -58,18 +59,17 @@ SwLinePortion *SwLinePortion::Compress() return GetLen() || Width() ? this : nullptr; } -sal_uInt16 SwLinePortion::GetViewWidth( const SwTextSizeInfo & ) const -{ - return 0; -} +SwTwips SwLinePortion::GetViewWidth(const SwTextSizeInfo&) const { return 0; } SwLinePortion::SwLinePortion( ) : mpNextPortion( nullptr ), mnLineLength( 0 ), mnAscent( 0 ), + mnHangingBaseline( 0 ), mnWhichPor( PortionType::NONE ), m_bJoinBorderWithPrev(false), - m_bJoinBorderWithNext(false) + m_bJoinBorderWithNext(false), + m_bIsFieldmarkText(false) { } @@ -79,22 +79,22 @@ void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf, OSL_ENSURE( rInf.OnWin(), "SwLinePortion::PrePaint: don't prepaint on a printer"); OSL_ENSURE( !Width(), "SwLinePortion::PrePaint: For Width()==0 only!"); - const sal_uInt16 nViewWidth = GetViewWidth( rInf ); + const SwTwips nViewWidth = GetViewWidth(rInf); if( ! nViewWidth ) return; - const sal_uInt16 nHalfView = nViewWidth / 2; - sal_uInt16 nLastWidth = pLast->Width(); + const SwTwips nHalfView = nViewWidth / 2; + SwTwips 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; + SwTwips 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 : @@ -107,22 +107,22 @@ void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf, switch (nDir.get()) { case 0: - nPos = sal_uInt16( rInf.X() ); + nPos = rInf.X(); nPos += nLastWidth - nHalfView; aInf.X( nPos ); break; case 900: - nPos = sal_uInt16( rInf.Y() ); + nPos = rInf.Y(); nPos -= nLastWidth - nHalfView; aInf.Y( nPos ); break; case 1800: - nPos = sal_uInt16( rInf.X() ); + nPos = rInf.X(); nPos -= nLastWidth - nHalfView; aInf.X( nPos ); break; case 2700: - nPos = sal_uInt16( rInf.Y() ); + nPos = rInf.Y(); nPos += nLastWidth - nHalfView; aInf.Y( nPos ); break; @@ -138,12 +138,12 @@ void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf, void SwLinePortion::CalcTextSize( const SwTextSizeInfo &rInf ) { if( GetLen() == rInf.GetLen() ) - *static_cast<SwPosSize*>(this) = GetTextSize( rInf ); + *static_cast<SwPositiveSize*>(this) = GetTextSize( rInf ); else { SwTextSizeInfo aInf( rInf ); aInf.SetLen( GetLen() ); - *static_cast<SwPosSize*>(this) = GetTextSize( aInf ); + *static_cast<SwPositiveSize*>(this) = GetTextSize( aInf ); } } @@ -201,6 +201,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; @@ -219,7 +222,7 @@ SwLinePortion *SwLinePortion::FindPrevPortion( const SwLinePortion *pRoot ) return pPos; } -TextFrameIndex SwLinePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +TextFrameIndex SwLinePortion::GetModelPositionForViewPoint(const SwTwips nOfst) const { if( nOfst > ( PrtWidth() / 2 ) ) return GetLen(); @@ -227,11 +230,11 @@ TextFrameIndex SwLinePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfs return TextFrameIndex(0); } -SwPosSize SwLinePortion::GetTextSize( const SwTextSizeInfo & ) const +SwPositiveSize SwLinePortion::GetTextSize( const SwTextSizeInfo & ) const { OSL_ENSURE( false, "SwLinePortion::GetTextSize: don't ask me about sizes, " "I'm only a stupid SwLinePortion" ); - return SwPosSize(); + return SwPositiveSize(); } bool SwLinePortion::Format( SwTextFormatInfo &rInf ) @@ -246,7 +249,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 SwTwips nNewWidth = 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 +268,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 +294,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 +318,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..eb160830c1d4 100644 --- a/sw/source/core/text/porlin.hxx +++ b/sw/source/core/text/porlin.hxx @@ -18,48 +18,42 @@ */ #pragma once +#include <libxml/xmlwriter.h> + #include "possiz.hxx" #include <txttypes.hxx> #include <TextFrameIndex.hxx> #include <rtl/ustring.hxx> +#include <swporlayoutcontext.hxx> class SwTextSizeInfo; class SwTextPaintInfo; class SwTextFormatInfo; class SwPortionHandler; -/// Portion groups -/// @see enum PortionType in txttypes.hxx -#define PORGRP_TXT 0x8000 -#define PORGRP_EXP 0x4000 -#define PORGRP_FLD 0x2000 -#define PORGRP_HYPH 0x1000 -#define PORGRP_NUMBER 0x0800 -#define PORGRP_GLUE 0x0400 -#define PORGRP_FIX 0x0200 -#define PORGRP_TAB 0x0100 -// Small special groups -#define PORGRP_FIXMARG 0x0040 -//#define PORGRP_? 0x0020 -#define PORGRP_TABNOTLFT 0x0010 -#define PORGRP_TOXREF 0x0008 - /// Base class for anything that can be part of a line in the Writer layout. /// Typically owned by SwLineLayout. -class SwLinePortion: public SwPosSize +class SAL_DLLPUBLIC_RTTI SwLinePortion: public SwPositiveSize { 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; + bool m_bIsFieldmarkText; + SwTwips m_nExtraBlankWidth = 0; // width of spaces after the break + SwTwips m_nExtraShrunkWidth = 0; // width of not shrunk line + SwTwips m_nExtraSpaceSize = 0; // extra space over normal space width + + std::optional<SwLinePortionLayoutContext> m_nLayoutContext; void Truncate_(); @@ -73,13 +67,26 @@ 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 ExtraShrunkWidth() const { return m_nExtraShrunkWidth; } + void ExtraShrunkWidth(const SwTwips nNew) { m_nExtraShrunkWidth = nNew; } + SwTwips ExtraSpaceSize() const { return m_nExtraSpaceSize; } + void ExtraSpaceSize(const SwTwips nNew) { m_nExtraSpaceSize = nNew; } + SwTwips GetHangingBaseline() const { return mnHangingBaseline; } + void SetHangingBaseline( const SwTwips nNewBaseline ) { mnHangingBaseline = nNewBaseline; } + const std::optional<SwLinePortionLayoutContext> & GetLayoutContext() const { return m_nLayoutContext; } + void SetLayoutContext(std::optional<SwLinePortionLayoutContext> nNew) + { + m_nLayoutContext = nNew; + } // Insert methods virtual SwLinePortion *Insert( SwLinePortion *pPortion ); @@ -140,9 +147,8 @@ public: SwLinePortion *FindPrevPortion( const SwLinePortion *pRoot ); SwLinePortion *FindLastPortion(); - /// the parameter is actually SwTwips apparently? - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const; void CalcTextSize( const SwTextSizeInfo &rInfo ); // Output @@ -152,45 +158,63 @@ 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; // For SwFieldPortion, SwSoftHyphPortion - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const; + virtual SwTwips 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; bool GetJoinBorderWithPrev() const { return m_bJoinBorderWithPrev; } bool GetJoinBorderWithNext() const { return m_bJoinBorderWithNext; } + bool IsFieldmarkText() const {return m_bIsFieldmarkText;} void SetJoinBorderWithPrev( const bool bJoinPrev ) { m_bJoinBorderWithPrev = bJoinPrev; } void SetJoinBorderWithNext( const bool bJoinNext ) { m_bJoinBorderWithNext = bJoinNext; } + void SetFieldmarkText(bool bSet) { m_bIsFieldmarkText = bSet; } + + virtual void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, + TextFrameIndex& rOffset) const; + void dumpAsXmlAttributes(xmlTextWriterPtr writer, std::u16string_view rText, + TextFrameIndex nOffset) const; }; inline SwLinePortion &SwLinePortion::operator=(const SwLinePortion &rPortion) { - *static_cast<SwPosSize*>(this) = rPortion; + *static_cast<SwPositiveSize*>(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; + m_nExtraShrunkWidth = rPortion.m_nExtraShrunkWidth; + m_nExtraSpaceSize = rPortion.m_nExtraSpaceSize; + m_nLayoutContext = rPortion.m_nLayoutContext; return *this; } inline SwLinePortion::SwLinePortion(const SwLinePortion &rPortion) : - SwPosSize( rPortion ), + SwPositiveSize( 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_bIsFieldmarkText( rPortion.m_bIsFieldmarkText ), + m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth), + m_nExtraShrunkWidth(rPortion.m_nExtraShrunkWidth), + m_nExtraSpaceSize(rPortion.m_nExtraSpaceSize), + m_nLayoutContext(rPortion.m_nLayoutContext) { } diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx index c51fb973ad29..d4495587d5f1 100644 --- a/sw/source/core/text/pormulti.cxx +++ b/sw/source/core/text/pormulti.cxx @@ -31,6 +31,10 @@ #include <charfmt.hxx> #include <layfrm.hxx> #include <SwPortionHandler.hxx> +#include <EnhancedPDFExportHelper.hxx> +#include <com/sun/star/i18n/BreakType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <breakit.hxx> #include "pormulti.hxx" #include "inftxt.hxx" #include "itrpaint.hxx" @@ -119,7 +123,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 +138,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,8 +222,9 @@ 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 { + nSpaceAdd = nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd; return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR; } @@ -351,12 +381,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 +414,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() ); - SwPosSize aSize = rInf.GetTextSize( aStr ); + aTmpFnt.SetActual( m_pBracket->nPreScript ); + SwFontSave aSave( rInf, &aTmpFnt ); + SwPositiveSize 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,9 +447,9 @@ 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() ); - SwPosSize aSize = rInf.GetTextSize( aStr ); + aTmpFnt.SetActual( m_pBracket->nPostScript ); + SwFontSave aSave( rInf, &aTmpFnt ); + SwPositiveSize aSize = rInf.GetTextSize( aStr ); const sal_uInt16 nTmpAsc = rInf.GetAscent(); if( nTmpAsc > m_pBracket->nAscent ) { @@ -479,8 +509,9 @@ 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 { + nSpaceAdd = nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd; return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR; } @@ -592,7 +623,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 +685,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 +714,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 +877,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 +900,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 +937,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 +969,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 +1037,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 +1099,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 +1165,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 +1201,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 +1228,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 +1269,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 +1328,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 +1369,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 +1422,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 +1585,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 +1596,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 +1667,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,13 +1720,15 @@ 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() ); } - else - GetInfo().Y( nOfst + AdjustBaseLine( *pLay, pPor ) ); + else if (!rMulti.IsBidi()) + { + GetInfo().Y(nOfst + AdjustBaseLine(*pLay, pPor)); + } bool bSeeked = true; GetInfo().SetLen( pPor->GetLen() ); @@ -1740,7 +1783,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 +1850,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 +1879,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 +1894,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 ); } @@ -1857,11 +1923,63 @@ static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField return bRet; } +// Determines if any part of the bidi portion fits on the current line +namespace +{ +enum class BidiTruncationType +{ + None, + Truncate, + Underflow +}; + +BidiTruncationType lcl_BidiPortionNeedsTruncation(const SwMultiPortion& rMulti, + const SwTextFormatInfo& rExternalInf, + const SwTextFormatInfo& rLocalInf, + TextFrameIndex const nStartIdx) +{ + if (!rLocalInf.IsUnderflow()) + { + // Some amount of text fits in the bidi portion without triggering underflow, + // so the portion should not be truncated. + return BidiTruncationType::None; + } + + auto nCurrLen = rMulti.GetLen(); + + css::i18n::LineBreakHyphenationOptions aHyphOptions; + css::i18n::LineBreakUserOptions aUserOptions; + css::lang::Locale aLocale; + auto aResult = g_pBreakIt->GetBreakIter()->getLineBreak( + rExternalInf.GetText(), sal_Int32(nStartIdx + nCurrLen), aLocale, + sal_Int32(rExternalInf.GetLineStart()), aHyphOptions, aUserOptions); + + if (aResult.breakIndex < sal_Int32(nStartIdx)) + { + // The bidi portion doesn't fit on the line, and the first break opportunity + // is before the bidi portion. Underflow to the preceding text. + return BidiTruncationType::Underflow; + } + + if (aResult.breakIndex > sal_Int32(nStartIdx) + && aResult.breakIndex <= sal_Int32(nStartIdx + nCurrLen)) + { + // The bidi portion fits on this line, but ended with underflow. + return BidiTruncationType::None; + } + + // The bidi portion doesn't fit on the line, but a break position exists between the bidi + // portion and the preceding text. Truncating is sufficient. + return BidiTruncationType::Truncate; +} +} + // If a multi portion completely has to go to the // next line, this function is called to truncate // the rest of the remaining multi portion -static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf, - TextFrameIndex const nStartIdx) +static void lcl_TruncateMultiPortion(SwMultiPortion& rMulti, SwTextFormatInfo& rInf, + TextFrameIndex const nStartIdx, + BidiTruncationType nBidiTruncType = BidiTruncationType::None) { rMulti.GetRoot().Truncate(); rMulti.GetRoot().SetLen(TextFrameIndex(0)); @@ -1876,6 +1994,18 @@ static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rMulti.Width( 0 ); rMulti.SetLen(TextFrameIndex(0)); rInf.SetIdx( nStartIdx ); + + if (rMulti.IsBidi()) + { + // The truncated portion is a bidi portion. Bidi portions contain ordinary text, and may + // potentially underflow in the case that none of the text fits on the current line. + if (nBidiTruncType == BidiTruncationType::Underflow) + { + // The start of the bidi portion is not a valid break. Instead, a break should be + // inserted into a previous text portion on this line. + rInf.SetUnderflow(&rMulti); + } + } } // Manages the formatting of a SwMultiPortion. External, for the calling @@ -1898,16 +2028,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() ); @@ -1952,7 +2083,9 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, ( rInf.GetTextFrame()->IsVertical() ? pUpperFrame->getFramePrintArea().Width() : pUpperFrame->getFramePrintArea().Height() ) : - USHRT_MAX; + std::numeric_limits<SwTwips>::max(); + if (nMaxWidth < 0) + nMaxWidth = 0; } else nTmpX = rInf.X(); @@ -1995,7 +2128,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())) ); @@ -2010,7 +2143,7 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, bool bRet = false; SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); - const bool bHasGrid = pGrid && GRID_LINES_CHARS == pGrid->GetGridType(); + const bool bHasGrid = pGrid && SwTextGrid::LinesAndChars == pGrid->GetGridType(); bool bRubyTop = false; @@ -2024,12 +2157,21 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, bRet = false; FormatReset( aInf ); aInf.X( nTmpX ); - aInf.Width( sal_uInt16(nActWidth) ); - aInf.RealWidth( sal_uInt16(nActWidth) ); + aInf.Width(nActWidth); + aInf.RealWidth(nActWidth); aInf.SetFirstMulti( bFirstMulti ); aInf.SetNumDone( rInf.IsNumDone() ); aInf.SetFootnoteDone( rInf.IsFootnoteDone() ); + // tdf#157829: Bidi portions contain text; word wrapping should underflow. + // By default, the SwTextFormatInfo constructor assumes the current index is the start of + // a new line. As a result, Writer cut breaks MultiPortions as if they were wider than the + // entire document. This is incorrect behavior for bidi portions. + if (rMulti.IsBidi()) + { + aInf.SetLineStart(rInf.GetLineStart()); + } + // if there's a bookmark at the start of the MultiPortion, it will be // painted with the rotation etc. of the MultiPortion; move it *inside* // so it gets positioned correctly; currently there's no other portion @@ -2283,14 +2425,17 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, else { // we try to keep our ruby portion together - lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx ); + lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx); pTmp = nullptr; + // A follow field portion may still be waiting. If nobody wants + // it, we delete it. + delete pNextSecond; } } else if( rMulti.HasRotation() ) { // we try to keep our rotated portion together - lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx ); + lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx); pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(), rMulti.GetDirection() ); } @@ -2298,8 +2443,11 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, // a new SwBidiPortion, this would cause a memory leak else if( rMulti.IsBidi() && ! m_pMulti ) { - if ( ! rMulti.GetLen() ) - lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx ); + auto nTruncType = lcl_BidiPortionNeedsTruncation(rMulti, rInf, aInf, nStartIdx); + if (nTruncType != BidiTruncationType::None) + { + lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx, nTruncType); + } // If there is a HolePortion at the end of the bidi portion, // it has to be moved behind the bidi portion. Otherwise @@ -2354,10 +2502,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 +2606,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 +2619,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 +2663,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 +2697,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..3cf5449d2814 100644 --- a/sw/source/core/text/porref.cxx +++ b/sw/source/core/text/porref.cxx @@ -40,12 +40,12 @@ SwIsoRefPortion::SwIsoRefPortion() : m_nViewWidth(0) SetWhichPor( PortionType::IsoRef ); } -sal_uInt16 SwIsoRefPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +SwTwips 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/porref.hxx b/sw/source/core/text/porref.hxx index d12803080345..953d7462b3d6 100644 --- a/sw/source/core/text/porref.hxx +++ b/sw/source/core/text/porref.hxx @@ -29,14 +29,14 @@ public: class SwIsoRefPortion : public SwRefPortion { - sal_uInt16 m_nViewWidth; + SwTwips m_nViewWidth; public: SwIsoRefPortion(); virtual bool Format(SwTextFormatInfo& rInf) override; virtual void Paint(const SwTextPaintInfo& rInf) const override; virtual SwLinePortion* Compress() override; - virtual sal_uInt16 GetViewWidth(const SwTextSizeInfo& rInf) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion(SwPortionHandler& rPH) const override; diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx index 6f578e0744cd..53fc1e3eff71 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()). @@ -82,7 +102,7 @@ void SwTmpEndPortion::Paint( const SwTextPaintInfo &rInf ) const } - aFont.SetColor( NON_PRINTING_CHARACTER_COLOR ); + aFont.SetColor( SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor() ); aFont.SetStrikeout( STRIKEOUT_NONE ); aFont.SetUnderline( LINESTYLE_NONE ); const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont); @@ -93,21 +113,27 @@ 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 +TextFrameIndex SwBreakPortion::GetModelPositionForViewPoint(const SwTwips) const { return TextFrameIndex(0); } -sal_uInt16 SwBreakPortion::GetViewWidth( const SwTextSizeInfo & ) const -{ return 0; } +SwTwips SwBreakPortion::GetViewWidth(const SwTextSizeInfo&) const { return 0; } SwLinePortion *SwBreakPortion::Compress() { return (GetNextPortion() && GetNextPortion()->InTextGrp() ? nullptr : this); } @@ -117,37 +143,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 +203,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 +237,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 ) @@ -213,7 +298,7 @@ void SwKernPortion::Paint( const SwTextPaintInfo &rInf ) const rInf.CalcRect( *this, &aClipRect ); SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) ); aClip.ChgClip( aClipRect ); - rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(2), true ); + rInf.DrawText(u" "_ustr, *this, TextFrameIndex(0), TextFrameIndex(2), true ); } } @@ -243,7 +328,7 @@ SwArrowPortion::SwArrowPortion( const SwLinePortion &rPortion ) : SwArrowPortion::SwArrowPortion( const SwTextPaintInfo &rInf ) : m_bLeft( false ) { - Height( static_cast<sal_uInt16>(rInf.GetTextFrame()->getFramePrintArea().Height()) ); + Height(rInf.GetTextFrame()->getFramePrintArea().Height()); m_aPos.setX( rInf.GetTextFrame()->getFrameArea().Left() + rInf.GetTextFrame()->getFramePrintArea().Right() ); m_aPos.setY( rInf.GetTextFrame()->getFrameArea().Top() + @@ -335,10 +420,22 @@ bool SwTextFrame::FormatEmpty() { OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::FormatEmpty with swapped frame" ); - bool bCollapse = EmptyHeight( ) == 1 && IsCollapse( ); + SwTwips nHeight = EmptyHeight(); + bool bCollapse = nHeight == 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,29 +449,40 @@ bool SwTextFrame::FormatEmpty() const SvxLineSpacingItem &rSpacing = aSet.GetLineSpacing(); if( !bCollapse && ( SvxLineSpaceRule::Min == rSpacing.GetLineSpaceRule() || SvxLineSpaceRule::Fix == rSpacing.GetLineSpaceRule() || - aSet.GetLRSpace().IsAutoFirst() ) ) + (rSpacing.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop + && rSpacing.GetPropLineSpace() < 100) || + 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(); - if (dynamic_cast<const sw::mark::IBookmark*>(pMark) != nullptr) + if (!pIndex->GetOwner() || pIndex->GetOwner()->GetOwnerType() != SwContentIndexOwnerType::Mark) + continue; + auto const pMark = static_cast<sw::mark::MarkBase const*>(pIndex->GetOwner()); + if (dynamic_cast<const sw::mark::Bookmark*>(pMark) != nullptr) { // need bookmark portions! return false; } } - SwTwips nHeight = EmptyHeight(); - if (aSet.GetParaGrid().GetValue() && IsInDocBody() ) { @@ -384,7 +492,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 +582,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 +620,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,15 +654,14 @@ void SwHiddenTextPortion::Paint( const SwTextPaintInfo & rInf) const { #ifdef DBG_UTIL OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut()); - Color aCol( SwViewOption::GetFieldShadingsColor() ); - Color aOldColor( pOut->GetFillColor() ); - pOut->SetFillColor( aCol ); + pOut->Push(vcl::PushFlags::FILLCOLOR); + pOut->SetFillColor( rInf.GetOpt().GetFieldShadingsColor() ); Point aPos( rInf.GetPos() ); aPos.AdjustY( -150 ); aPos.AdjustX( -25 ); SwRect aRect( aPos, Size( 100, 200 ) ); pOut->DrawRect( aRect.SVRect() ); - pOut->SetFillColor( aOldColor ); + pOut->Pop(); #else (void)rInf; #endif @@ -549,10 +675,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().IsViewMetaChars()) { return false; } @@ -571,6 +697,7 @@ bool SwControlCharPortion::DoPaint(SwTextPaintInfo const&, } rTmpFont.SetEscapement( CHAR_ZWSP == mcChar ? DFLT_ESC_AUTO_SUB : -25 ); + rTmpFont.SetColor( SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor() ); const sal_uInt16 nProp = 40; rTmpFont.SetProportion( nProp ); // a smaller font @@ -580,6 +707,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; @@ -590,14 +718,15 @@ bool SwBookmarkPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo, // init font: we want OpenSymbol to ensure it doesn't look too crazy; // thin and a bit higher than the surrounding text auto const nOrigAscent(rFont.GetAscent(rTextPaintInfo.GetVsh(), *rTextPaintInfo.GetOut())); - rFont.SetName("OpenSymbol", rFont.GetActual()); + rFont.SetName(u"OpenSymbol"_ustr, rFont.GetActual()); Size aSize(rFont.GetSize(rFont.GetActual())); // use also the external leading (line gap) of the portion, but don't use // 100% of it because i can't figure out how to baseline align that - 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 +797,221 @@ 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; + + auto nHeight = rInf.GetTextSize( aOutString ).Height(); + + 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(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(mnHalfCharWidth * -(2 * m_nEnd - 1 + m_nPoint) ); + + const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos ); + + SwTwips nTypePos = 0; // shift to the position of the next rdf:type label + sal_Int32 nDirection = -1; // start with the closing brackets + bool bStart = true; + 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 ? '[' : ']'); + + if (nDirection == -1 && std::get<0>(it) != SwScriptInfo::MarkKind::End) + { + nDirection = 1; + nTypePos = mnHalfCharWidth * 2; // start label after the opening bracket + } + + // 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 ); + + // show rdf:type labels, left-aligned top position after the opening brackets + // right-aligned bottom position before the closing brackets + // if there are multiple opening or closing brackets, collect + // their length in nTypePos to show non-overlapping labels + OUString sType = std::get<3>(it); + if ( !sType.isEmpty() ) + { + Size aTmpSz = aTmpFont.GetSize( SwFontScript::Latin ); + auto origSize = aTmpSz; + + // calculate label size + aTmpSz.setHeight( std::min( tools::Long(60), 100 * aTmpSz.Height() / 250 ) ); + aTmpSz.setWidth( std::min( tools::Long(60), 100 * aTmpSz.Width() / 250 ) ); + + // vertical rdf:type label position for the opening and closing brackets + sal_Int32 fPos = std::get<0>(it) == SwScriptInfo::MarkKind::Start + ? -0.65 * nHeight + : aTmpSz.Height(); + + if ( aTmpSz.Width() || aTmpSz.Height() ) + { + aTmpFont.SetSize( aTmpSz, SwFontScript::Latin ); + auto aTextSize = rInf.GetTextSize(sType); + + aNewPos.AdjustY(fPos); + if ( nDirection == -1 ) + { + if (bStart) + { + nTypePos += aTextSize.Width(); + bStart = false; + } + else + nTypePos += aTextSize.Width() + + rInf.GetTextSize( " " ).Width() + 2 * mnHalfCharWidth; + } + aNewPos.AdjustX( nDirection * nTypePos ); + + const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos ); + + SwRect aRect( rInf.GetPos(), Size(aTextSize.Width(), -aTextSize.Height() * 0.8) ); + rInf.DrawRect( aRect, true ); // white background + rInf.DrawText( sType, *this ); // label + + // restore original position + aNewPos.AdjustX( -nDirection * nTypePos ); + if ( nDirection == 1 ) + nTypePos += aTextSize.Width() + + rInf.GetTextSize( " " ).Width() - mnHalfCharWidth * 2; + + aNewPos.AdjustY(-fPos); + } + // restore original text size + aTmpSz.setHeight(origSize.Height()); + aTmpSz.setWidth(origSize.Width()); + aTmpFont.SetSize( origSize, SwFontScript::Latin ); + } + + // 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).toString() + " " + 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).toString() + " " + 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(); @@ -678,7 +1022,7 @@ bool SwControlCharPortion::Format( SwTextFormatInfo &rInf ) return false; } -sal_uInt16 SwControlCharPortion::GetViewWidth( const SwTextSizeInfo& rInf ) const +SwTwips SwControlCharPortion::GetViewWidth(const SwTextSizeInfo& rInf) const { if( !mnViewWidth ) mnViewWidth = rInf.GetTextSize(OUString(' ')).Width(); diff --git a/sw/source/core/text/porrst.hxx b/sw/source/core/text/porrst.hxx index c92c50db3faa..15cf813d4d8c 100644 --- a/sw/source/core/text/porrst.hxx +++ b/sw/source/core/text/porrst.hxx @@ -24,21 +24,19 @@ #include <txttypes.hxx> #include <txtfrm.hxx> #include <svx/ctredlin.hxx> +#include <scriptinfo.hxx> +#include <names.hxx> #include "porlin.hxx" #include "portxt.hxx" #include "possiz.hxx" -class SwPortionHandler; class SwTextPaintInfo; -class SwTextSizeInfo; class SwFont; #define LINE_BREAK_WIDTH 150 #define SPECIAL_FONT_HEIGHT 200 -class SwTextFormatInfo; - class SwTmpEndPortion : public SwLinePortion { const FontLineStyle m_eUnderline; @@ -53,24 +51,37 @@ 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; virtual bool Format( SwTextFormatInfo &rInf ) override; - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; // 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 +108,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; @@ -116,16 +128,16 @@ public: // The SwHangingPortion is the corresponding textportion to do that. class SwHangingPortion : public SwTextPortion { - sal_uInt16 m_nInnerWidth; + SwTwips m_nInnerWidth; public: - explicit SwHangingPortion( SwPosSize aSize ) : m_nInnerWidth( aSize.Width() ) + explicit SwHangingPortion( SwPositiveSize aSize ) : m_nInnerWidth( aSize.Width() ) { SetWhichPor( PortionType::Hanging ); SetLen(TextFrameIndex(1)); Height( aSize.Height() ); } - sal_uInt16 GetInnerWidth() const { return m_nInnerWidth; } + SwTwips GetInnerWidth() const { return m_nInnerWidth; } }; // Used to hide text @@ -145,9 +157,9 @@ 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 + mutable SwTwips mnViewWidth; // used to cache a calculated value protected: + mutable SwTwips mnHalfCharWidth; // used to cache a calculated value sal_Unicode mcChar; public: @@ -162,24 +174,46 @@ public: OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const; virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; - virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo& rInf ) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; }; /// for showing bookmark starts and ends; note that in contrast to /// SwControlCharPortion these do not have a character in the text. class SwBookmarkPortion : public SwControlCharPortion { + // custom colors defined by metadata + std::vector<std::tuple<SwScriptInfo::MarkKind, Color, SwMarkName, 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, SwMarkName, 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/portab.hxx b/sw/source/core/text/portab.hxx index 9ffe7be50651..a1552afeffc2 100644 --- a/sw/source/core/text/portab.hxx +++ b/sw/source/core/text/portab.hxx @@ -22,20 +22,20 @@ class SwTabPortion : public SwFixPortion { - const sal_uInt16 m_nTabPos; + const SwTwips m_nTabPos; const sal_Unicode m_cFill; const bool m_bAutoTabStop; // Format() branches either into PreFormat() or PostFormat() - bool PreFormat( SwTextFormatInfo &rInf ); + bool PreFormat(SwTextFormatInfo &rInf, SwTabPortion const*); public: - SwTabPortion( const sal_uInt16 nTabPos, const sal_Unicode cFill, const bool bAutoTab = true ); + SwTabPortion(const SwTwips nTabPos, const sal_Unicode cFill, const bool bAutoTab = true); virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; virtual void FormatEOL( SwTextFormatInfo &rInf ) override; bool PostFormat( SwTextFormatInfo &rInf ); bool IsFilled() const { return 0 != m_cFill; } - sal_uInt16 GetTabPos() const { return m_nTabPos; } + SwTwips GetTabPos() const { return m_nTabPos; } // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override; @@ -44,7 +44,7 @@ public: class SwTabLeftPortion : public SwTabPortion { public: - SwTabLeftPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar, bool bAutoTab ) + SwTabLeftPortion(const SwTwips nTabPosVal, const sal_Unicode cFillChar, bool bAutoTab) : SwTabPortion( nTabPosVal, cFillChar, bAutoTab ) { SetWhichPor( PortionType::TabLeft ); } }; @@ -52,7 +52,7 @@ public: class SwTabRightPortion : public SwTabPortion { public: - SwTabRightPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar ) + SwTabRightPortion(const SwTwips nTabPosVal, const sal_Unicode cFillChar) : SwTabPortion( nTabPosVal, cFillChar ) { SetWhichPor( PortionType::TabRight ); } }; @@ -60,7 +60,7 @@ public: class SwTabCenterPortion : public SwTabPortion { public: - SwTabCenterPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar ) + SwTabCenterPortion(const SwTwips nTabPosVal, const sal_Unicode cFillChar) : SwTabPortion( nTabPosVal, cFillChar ) { SetWhichPor( PortionType::TabCenter ); } }; @@ -74,23 +74,23 @@ class SwTabDecimalPortion : public SwTabPortion * following the tab stop up to the decimal position. This value is * evaluated during pLastTab->FormatEOL. FME 2006-01-06 #127428#. */ - sal_uInt16 mnWidthOfPortionsUpTpDecimalPosition; + SwTwips mnWidthOfPortionsUpTpDecimalPosition; public: - SwTabDecimalPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cTab, + SwTabDecimalPortion(const SwTwips nTabPosVal, const sal_Unicode cTab, const sal_Unicode cFillChar ) : SwTabPortion( nTabPosVal, cFillChar ), mcTab(cTab), - mnWidthOfPortionsUpTpDecimalPosition( USHRT_MAX ) + mnWidthOfPortionsUpTpDecimalPosition( std::numeric_limits<SwTwips>::max() ) { SetWhichPor( PortionType::TabDecimal ); } sal_Unicode GetTabDecimal() const { return mcTab; } - void SetWidthOfPortionsUpToDecimalPosition( sal_uInt16 nNew ) + void SetWidthOfPortionsUpToDecimalPosition(SwTwips nNew) { mnWidthOfPortionsUpTpDecimalPosition = nNew; } - sal_uInt16 GetWidthOfPortionsUpToDecimalPosition() const + SwTwips GetWidthOfPortionsUpToDecimalPosition() const { return mnWidthOfPortionsUpTpDecimalPosition; } @@ -99,7 +99,7 @@ public: class SwAutoTabDecimalPortion : public SwTabDecimalPortion { public: - SwAutoTabDecimalPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cTab, + SwAutoTabDecimalPortion(const SwTwips nTabPosVal, const sal_Unicode cTab, const sal_Unicode cFillChar ) : SwTabDecimalPortion( nTabPosVal, cTab, cFillChar ) { diff --git a/sw/source/core/text/portox.cxx b/sw/source/core/text/portox.cxx index 982ec4f5fb25..f953316c1375 100644 --- a/sw/source/core/text/portox.cxx +++ b/sw/source/core/text/portox.cxx @@ -40,7 +40,7 @@ SwIsoToxPortion::SwIsoToxPortion() : m_nViewWidth(0) SetWhichPor( PortionType::IsoTox ); } -sal_uInt16 SwIsoToxPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +SwTwips SwIsoToxPortion::GetViewWidth(const SwTextSizeInfo& rInf) const { // Although we are const, nViewWidth should be calculated in the last // moment possible @@ -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/portox.hxx b/sw/source/core/text/portox.hxx index 07d7c44fb24e..541963570823 100644 --- a/sw/source/core/text/portox.hxx +++ b/sw/source/core/text/portox.hxx @@ -30,14 +30,14 @@ public: class SwIsoToxPortion : public SwToxPortion { - sal_uInt16 m_nViewWidth; + SwTwips m_nViewWidth; public: SwIsoToxPortion(); virtual bool Format(SwTextFormatInfo& rInf) override; virtual void Paint(const SwTextPaintInfo& rInf) const override; virtual SwLinePortion* Compress() override; - virtual sal_uInt16 GetViewWidth(const SwTextSizeInfo& rInf) const override; + virtual SwTwips GetViewWidth(const SwTextSizeInfo& rInf) const override; // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion(SwPortionHandler& rPH) const override; diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx index ed9d3c517bb5..40473f8458d3 100644 --- a/sw/source/core/text/portxt.cxx +++ b/sw/source/core/text/portxt.cxx @@ -36,6 +36,7 @@ #include <IMark.hxx> #include <pam.hxx> #include <doc.hxx> +#include <o3tl/temporary.hxx> #include <xmloff/odffields.hxx> #include <viewopt.hxx> @@ -43,6 +44,11 @@ using namespace ::sw::mark; using namespace ::com::sun::star; using namespace ::com::sun::star::i18n::ScriptType; +static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const OUString* pStr, + const SwLinePortion& rPor, TextFrameIndex nPos, + TextFrameIndex nEnd, const SwScriptInfo* pSI, + sal_uInt8 nScript); + // Returns for how many characters an extra space has to be added // (for justified alignment). static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, @@ -111,13 +117,12 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, // Kashida Justification: Insert Kashidas if ( nEnd > nPos && pSI && COMPLEX == nScript ) { - if ( SwScriptInfo::IsArabicText( *pStr, nPos, nEnd - nPos ) && pSI->CountKashida() ) + if (pSI->ParagraphContainsKashidaScript() + && SwScriptInfo::IsKashidaScriptText(*pStr, 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) - return TextFrameIndex(nKashRes); + // tdf#163105: For kashida justification, also expand whitespace. + return lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript) + + TextFrameIndex{ pSI->CountKashidaPositions(nPos, nEnd) }; } } @@ -129,7 +134,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() || @@ -144,6 +149,16 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, } } + return lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript); +} + +static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const OUString* pStr, + const SwLinePortion& rPor, TextFrameIndex nPos, + TextFrameIndex nEnd, const SwScriptInfo* pSI, + sal_uInt8 nScript) +{ + TextFrameIndex nCnt(0); + // Here starts the good old "Look for blanks and add space to them" part. // Note: We do not want to add space to an isolated latin blank in front // of some complex characters in RTL environment @@ -208,6 +223,36 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, return nCnt; } +static void GetLimitedStringPart(const SwTextFormatInfo& rInf, TextFrameIndex nIndex, + TextFrameIndex nLength, sal_uInt16 nComp, SwTwips nOriginalWidth, + SwTwips nMaxWidth, TextFrameIndex& rOutLength, SwTwips& rOutWidth) +{ + assert(nLength >= TextFrameIndex(0)); + const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); + rOutLength = nLength; + rOutWidth = nOriginalWidth; + if (nMaxWidth < 0) + nMaxWidth = 0; + while (rOutWidth > nMaxWidth) + { + TextFrameIndex nNewOnLineLengthGuess(rOutLength.get() * nMaxWidth / rOutWidth); + assert(nNewOnLineLengthGuess < rOutLength); + if (nNewOnLineLengthGuess < (rOutLength - TextFrameIndex(1))) + ++nNewOnLineLengthGuess; // to avoid too aggressive decrease + rOutLength = nNewOnLineLengthGuess; + rInf.GetTextSize(&rSI, nIndex, rOutLength, std::nullopt, nComp, rOutWidth, + o3tl::temporary(tools::Long()), o3tl::temporary(SwTwips()), + o3tl::temporary(SwTwips()), rInf.GetCachedVclData().get()); + } +} + +static bool IsMsWordUlTrailSpace(const SwTextFormatInfo& rInf) +{ + const auto& settings = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess(); + return settings.get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS) + && settings.get(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE); +} + SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion) { SwTextPortion *const pNew(new SwTextPortion); @@ -221,7 +266,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 SwTwips nLineWidth = rInf.Width() - rInf.X(); TextFrameIndex nLen = rGuess.CutPos() - rInf.GetIdx(); if (nLen > TextFrameIndex(0)) { @@ -235,7 +280,7 @@ void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess // changing these values requires also changing them in // guess.cxx - sal_uInt16 nItalic = 0; + SwTwips nItalic = 0; if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() ) { nItalic = Height() / 12; @@ -258,6 +303,8 @@ void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess { SetLen( TextFrameIndex(0) ); Width( 0 ); + ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); } } @@ -266,6 +313,8 @@ void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf ) Truncate(); Height( 0 ); Width( 0 ); + ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); SetLen( TextFrameIndex(0) ); SetAscent( 0 ); rInf.SetUnderflow( this ); @@ -277,6 +326,15 @@ static bool lcl_HasContent( const SwFieldPortion& rField, SwTextFormatInfo const return rField.GetExpText( rInf, aText ) && !aText.isEmpty(); } +sal_uInt16 SwTextPortion::GetMaxComp(const SwTextFormatInfo& rInf) const +{ + const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); + return (SwFontScript::CJK == rInf.GetFont()->GetActual()) && rSI.CountCompChg() + && !rInf.IsMulti() && !InFieldGrp() && !IsDropPortion() + ? 10000 + : 0; +} + bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) { // 5744: If only the hyphen does not fit anymore, we still need to wrap @@ -301,8 +359,179 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) return bFull; } - SwTextGuess aGuess; - const bool bFull = !aGuess.Guess( *this, rInf, Height() ); + ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); + std::optional<SwTextGuess> pGuess(std::in_place); + bool bFull = !pGuess->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 + SvxAdjustItem aAdjustItem = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust(); + const SvxAdjust aAdjust = aAdjustItem.GetAdjust(); + bool bFullJustified = bFull && aAdjust == SvxAdjust::Block && + pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING); + bool bInteropSmartJustify = bFullJustified && + rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING); + bool bNoWordSpacing = aAdjustItem.GetPropWordSpacing() == 100 && + aAdjustItem.GetPropWordSpacingMinimum() == 100 && + aAdjustItem.GetPropWordSpacingMaximum() == 100; + // support old ODT documents, where only JustifyLinesWithShrinking was set + bool bOldInterop = bInteropSmartJustify && bNoWordSpacing; + bool bWordSpacing = bFullJustified && (!bNoWordSpacing || bOldInterop); + bool bWordSpacingMaximum = bWordSpacing && !bOldInterop && + aAdjustItem.GetPropWordSpacingMaximum() > aAdjustItem.GetPropWordSpacing(); + bool bWordSpacingMinimum = bWordSpacing && ( bOldInterop || + aAdjustItem.GetPropWordSpacingMinimum() < aAdjustItem.GetPropWordSpacing() ); + + if ( ( bInteropSmartJustify || bWordSpacing || bWordSpacingMaximum || bWordSpacingMinimum ) && + // tdf#164499 no shrinking in tabulated line + ( !rInf.GetLast() || !rInf.GetLast()->InTabGrp() ) && + // tdf#158436 avoid shrinking at underflow, e.g. no-break space after a + // very short word resulted endless loop + !rInf.IsUnderflow() ) + { + sal_Int32 nSpacesInLine = rInf.GetLineSpaceCount( pGuess->BreakPos() ); + sal_Int32 nSpacesInLineOrig = nSpacesInLine; + SwTextSizeInfo aOrigInf( rInf ); + + // 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 + !pGuess->HyphWord().is() && + // no hyphenation at soft hyphen + pGuess->BreakPos() < TextFrameIndex(rInf.GetText().getLength()) && + rInf.GetText()[sal_Int32(pGuess->BreakPos())] != CHAR_SOFTHYPHEN ) + { + ++nSpacesInLine; + } + + // there are spaces in the line, so it's possible to shrink them + if ( nSpacesInLineOrig > 0 ) + { + SwTwips nOldWidth = pGuess->BreakWidth(); + bool bIsPortion = rInf.GetLineWidth() < rInf.GetBreakWidth(); + + // measure ten spaces for higher precision + static constexpr OUStringLiteral STR_BLANK = u" "; + sal_Int16 nSpaceWidth = rInf.GetTextSize(STR_BLANK).Width(); + sal_Int32 nRealSpaces = rInf.GetLineSpaceCount( pGuess->BreakPos() ); + float fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nRealSpaces * nSpaceWidth/10.0))/nRealSpaces; + + float fExpansionOverMax = fSpaceNormal - nSpaceWidth/10.0 * aAdjustItem.GetPropWordSpacingMaximum()/100.0; + ExtraSpaceSize( rInf.GetBreakWidth() > rInf.GetLineWidth()/2 && fExpansionOverMax > 0 ? fExpansionOverMax : 0); + + bool bOrigHyphenated = pGuess->HyphWord().is() && + pGuess->BreakPos() > rInf.GetLineStart(); + // calculate line breaking with desired word spacing, also + // if the desired word spacing is 100%, but there is a greater + // maximum word spacing, and the word is hyphenated at the desired + // word spacing: to skip hyphenation, if the maximum word spacing allows it + if ( bWordSpacing || ( bWordSpacingMaximum && bOrigHyphenated ) ) + { + pGuess.emplace(); + bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine, aAdjustItem.GetPropWordSpacing(), nSpaceWidth ); + sal_Int32 nSpacesInLine2 = rInf.GetLineSpaceCount( pGuess->BreakPos() ); + + if ( rInf.GetBreakWidth() <= rInf.GetLineWidth() ) + { + fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nSpacesInLine2 * nSpaceWidth/10.0))/nSpacesInLine2; + fExpansionOverMax = fSpaceNormal - nSpaceWidth/10.0 * aAdjustItem.GetPropWordSpacingMaximum()/100.0; + ExtraSpaceSize( rInf.GetBreakWidth() > rInf.GetLineWidth()/2 && fExpansionOverMax > 0 ? fExpansionOverMax : 0); + } + } + + sal_Int32 nSpacesInLineShrink = 0; + // TODO if both maximum word spacing or minimum word spacing can disable hyphenation, prefer the last one + if ( bWordSpacingMinimum ) + { + std::optional<SwTextGuess> pGuess2(std::in_place); + SwTwips nOldExtraSpace = rInf.GetExtraSpace(); + // break the line after the hyphenated word, if it's possible + // (hyphenation is disabled in Guess(), when called with GetPropWordSpacingMinimum()) + sal_uInt16 nMinimum = bOldInterop ? 75 : aAdjustItem.GetPropWordSpacingMinimum(); + bool bFull2 = !pGuess2->Guess( *this, rInf, Height(), nSpacesInLine, nMinimum, nSpaceWidth ); + nSpacesInLineShrink = rInf.GetLineSpaceCount( pGuess2->BreakPos() ); + if ( pGuess2->BreakWidth() > nOldWidth ) + { + // instead of the maximum shrinking, break after the word which was hyphenated before + sal_Int32 i = sal_Int32(pGuess->BreakPos()); + sal_Int32 j = sal_Int32(pGuess2->BreakPos()); + // skip terminal spaces + for (; i < j && rInf.GetText()[i] == CH_BLANK; ++i); + for (; j > i && rInf.GetText()[i] == CH_BLANK; --j); + sal_Int32 nOldBreakTrim = i; + sal_Int32 nOldBreak = j - i; + for (; i < j; ++i) + { + sal_Unicode cChar = rInf.GetText()[i]; + // first space after the hyphenated word, and it's not the chosen one + if ( cChar == CH_BLANK ) + { + // using a weighted word spacing, try to break the line after the hyphenated word + sal_Int32 nNewBreak = i - nOldBreakTrim; + SwTwips nWeightedSpacing = nMinimum * (1.0 * nNewBreak/nOldBreak) + + aAdjustItem.GetPropWordSpacing() * (1.0 * (nOldBreak - nNewBreak)/nOldBreak); + std::optional<SwTextGuess> pGuess3(std::in_place); + pGuess3->Guess( *this, rInf, Height(), nSpacesInLineShrink-1, nWeightedSpacing, nSpaceWidth ); + + sal_Int32 nSpacesInLineShrink2 = rInf.GetLineSpaceCount( pGuess3->BreakPos() ); + if ( nSpacesInLineShrink2 == nSpacesInLineShrink ) + { + nNewBreak = i - nOldBreakTrim - 1; + nWeightedSpacing = nMinimum * (1.0 * nNewBreak/nOldBreak) + + aAdjustItem.GetPropWordSpacing() * (1.0 * (nOldBreak - nNewBreak)/nOldBreak); + pGuess3->Guess( *this, rInf, Height(), nSpacesInLineShrink-1, nWeightedSpacing, nSpaceWidth ); + } + + if ( pGuess3->BreakWidth() > nOldWidth ) + { + pGuess2.emplace(); + pGuess2 = std::move(pGuess3); + } + break; + } + } + + nSpacesInLineShrink = rInf.GetLineSpaceCount( pGuess2->BreakPos() ); + if ( rInf.GetBreakWidth() > rInf.GetLineWidth() || bIsPortion ) + { + float fExpansionWeight = static_cast<float>(1/1.7); + float fSpaceShrunk = nSpacesInLineShrink > 0 + ? (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nSpacesInLineShrink * nSpaceWidth/10.0))/nSpacesInLineShrink + : 1; + float z0 = (nSpaceWidth/10.0)/fSpaceShrunk; + float z1 = (nSpaceWidth/10.0+((fSpaceNormal-nSpaceWidth/10.0)*fExpansionWeight))/(nSpaceWidth/10.0); + // TODO shrink line portions only if needed + if ( z1 >= z0 || bIsPortion ) + { + pGuess = std::move(pGuess2); + ExtraSpaceSize(0); + bFull = bFull2; + } + } + else if ( bOldInterop ) + { + pGuess = std::move(pGuess2); + ExtraSpaceSize(0); + bFull = bFull2; + } + } + else + // minimum word spacing is not applicable + rInf.SetExtraSpace(nOldExtraSpace); + } + + if ( pGuess->BreakWidth() != nOldWidth ) + { + ExtraShrunkWidth( pGuess->BreakWidth() ); + ExtraSpaceSize( 0 ); + } + } + } // these are the possible cases: // A Portion fits to current line @@ -320,7 +549,8 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) // case A: line not yet full if ( !bFull ) { - Width( aGuess.BreakWidth() ); + Width( pGuess->BreakWidth() ); + ExtraBlankWidth(pGuess->ExtraBlankWidth()); // Caution! if( !InExpGrp() || InFieldGrp() ) SetLen( rInf.GetLen() ); @@ -336,22 +566,22 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) new SwKernPortion( *this, nKern ); } // special case: hanging portion - else if( bFull && aGuess.GetHangingPortion() ) + else if( pGuess->GetHangingPortion() ) { - Width( aGuess.BreakWidth() ); - SetLen( aGuess.BreakPos() - rInf.GetIdx() ); - aGuess.GetHangingPortion()->SetAscent( GetAscent() ); - Insert( aGuess.ReleaseHangingPortion() ); + Width( pGuess->BreakWidth() ); + SetLen( pGuess->BreakPos() - rInf.GetIdx() ); + pGuess->GetHangingPortion()->SetAscent( GetAscent() ); + Insert( pGuess->ReleaseHangingPortion() ); } // breakPos >= index - else if (aGuess.BreakPos() >= rInf.GetIdx() && aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING)) + else if (pGuess->BreakPos() >= rInf.GetIdx() && pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING)) { // case B1 - if( aGuess.HyphWord().is() && aGuess.BreakPos() > rInf.GetLineStart() - && ( aGuess.BreakPos() > rInf.GetIdx() || + if( pGuess->HyphWord().is() && pGuess->BreakPos() > rInf.GetLineStart() + && ( pGuess->BreakPos() > rInf.GetIdx() || ( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) ) { - CreateHyphen( rInf, aGuess ); + CreateHyphen( rInf, *pGuess ); if ( rInf.GetFly() ) rInf.GetRoot()->SetMidHyph( true ); else @@ -372,14 +602,14 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) && rInf.GetLast()->InTabGrp() && rInf.GetLineStart() + rInf.GetLast()->GetLen() < rInf.GetIdx() && - aGuess.BreakPos() == rInf.GetIdx() && + pGuess->BreakPos() == rInf.GetIdx() && CH_BLANK != rInf.GetChar( rInf.GetIdx() ) && CH_FULL_BLANK != rInf.GetChar( rInf.GetIdx() ) && CH_SIX_PER_EM != rInf.GetChar( rInf.GetIdx() ) ) ) BreakUnderflow( rInf ); // case B2 else if( rInf.GetIdx() > rInf.GetLineStart() || - aGuess.BreakPos() > rInf.GetIdx() || + pGuess->BreakPos() > rInf.GetIdx() || // this is weird: during formatting the follow of a field // the values rInf.GetIdx and rInf.GetLineStart are replaced // IsFakeLineStart indicates GetIdx > GetLineStart @@ -393,34 +623,67 @@ 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( pGuess->BreakWidth() ); + + SetLen( pGuess->BreakPos() - rInf.GetIdx() ); - SetLen( aGuess.BreakPos() - rInf.GetIdx() ); + // Clamp layout context to the end of the line + if(auto stClampedContext = GetLayoutContext(); stClampedContext.has_value()) { + stClampedContext->m_nEnd = pGuess->BreakPos().get(); + SetLayoutContext(stClampedContext); + } - OSL_ENSURE( aGuess.BreakStart() >= aGuess.FieldDiff(), + OSL_ENSURE( pGuess->BreakStart() >= pGuess->FieldDiff(), "Trouble with expanded field portions during line break" ); - TextFrameIndex const nRealStart = aGuess.BreakStart() - aGuess.FieldDiff(); - if( aGuess.BreakPos() < nRealStart && !InExpGrp() ) + TextFrameIndex const nRealStart = pGuess->BreakStart() - pGuess->FieldDiff(); + if( pGuess->BreakPos() < nRealStart && !InExpGrp() ) { - SwHolePortion *pNew = new SwHolePortion( *this ); - pNew->SetLen( nRealStart - aGuess.BreakPos() ); + TextFrameIndex nTotalExtraLen(nRealStart - pGuess->BreakPos()); + TextFrameIndex nExtraLen(nTotalExtraLen); + TextFrameIndex nExtraLenOutOfLine(0); + SwTwips nTotalExtraWidth(pGuess->ExtraBlankWidth()); + SwTwips nExtraWidth(nTotalExtraWidth); + SwTwips nExtraWidthOutOfLine(0); + SwTwips nAvailableLineWidth(rInf.GetLineWidth() - Width()); + const bool bMsWordUlTrailSpace(IsMsWordUlTrailSpace(rInf)); + if (nExtraWidth > nAvailableLineWidth && bMsWordUlTrailSpace) + { + GetLimitedStringPart(rInf, pGuess->BreakPos(), nTotalExtraLen, GetMaxComp(rInf), + nTotalExtraWidth, nAvailableLineWidth, nExtraLen, + nExtraWidth); + nExtraLenOutOfLine = nTotalExtraLen - nExtraLen; + nExtraWidthOutOfLine = nTotalExtraWidth - nExtraWidth; + } + + SwHolePortion* pNew = new SwHolePortion(*this, bMsWordUlTrailSpace); + pNew->SetLen(nExtraLen); + pNew->ExtraBlankWidth(nExtraWidth); Insert( pNew ); + + if (nExtraWidthOutOfLine) + { + // Out-of-line hole portion - will not show underline + SwHolePortion* pNewOutOfLine = new SwHolePortion(*this, false); + pNewOutOfLine->SetLen(nExtraLenOutOfLine); + pNewOutOfLine->ExtraBlankWidth(nExtraWidthOutOfLine); + pNew->Insert(pNewOutOfLine); + } + + // 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(pGuess->BreakStart()); !ch || ch == CH_BREAK) + bFull = false; // Keep following SwBreakPortion / para break in the same line } } else // case C2, last exit - BreakCut( rInf, aGuess ); + BreakCut( rInf, *pGuess ); } // breakPos < index or no breakpos at all else { bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx(); - if (aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING) && - aGuess.BreakPos() != rInf.GetLineStart() && + if (pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING) && + pGuess->BreakPos() != rInf.GetLineStart() && ( !bFirstPor || rInf.GetFly() || rInf.GetLast()->IsFlyPortion() || rInf.IsFirstMulti() ) && ( !rInf.GetLast()->IsBlankPortion() || @@ -430,7 +693,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) } else // case C2, last exit - BreakCut( rInf, aGuess ); + BreakCut(rInf, *pGuess); } return bFull; @@ -439,10 +702,12 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) bool SwTextPortion::Format( SwTextFormatInfo &rInf ) { // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN. - if( rInf.GetLineWidth() < 0 || (!GetLen() && !InExpGrp()) ) + if( rInf.GetLineWidth() + rInf.GetExtraSpace() < 0 || (!GetLen() && !InExpGrp()) ) { Height( 0 ); Width( 0 ); + ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); SetLen( TextFrameIndex(0) ); SetAscent( 0 ); SetNextPortion( nullptr ); // ???? @@ -483,7 +748,7 @@ void SwTextPortion::FormatEOL( SwTextFormatInfo &rInf ) // First set ourselves and the insert, because there could be // a SwLineLayout - sal_uInt16 nBlankSize; + SwTwips nBlankSize; if( nHoleLen == GetLen() ) nBlankSize = Width(); else @@ -491,23 +756,23 @@ 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 ); } -TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const SwTwips nOfst) const { OSL_ENSURE( false, "SwTextPortion::GetModelPositionForViewPoint: don't use this method!" ); return SwLinePortion::GetModelPositionForViewPoint( nOfst ); } // The GetTextSize() assumes that the own length is correct -SwPosSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +SwPositiveSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const { - SwPosSize aSize = rInf.GetTextSize(); + SwPositiveSize aSize = rInf.GetTextSize(GetLayoutContext()); if( !GetJoinBorderWithPrev() ) aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace() ); if( !GetJoinBorderWithNext() ) @@ -543,6 +808,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 ); @@ -578,13 +845,16 @@ TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf, if ( rInf.SnapToGrid() ) { SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame())); - if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars()) + if (pGrid && SwTextGrid::LinesAndChars == pGrid->GetGridType() && pGrid->IsSnapToChars()) return TextFrameIndex(0); } 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,20 +878,23 @@ 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); if ( rInf.SnapToGrid() ) { SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame())); - if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars()) + if (pGrid && SwTextGrid::LinesAndChars == pGrid->GetGridType() && pGrid->IsSnapToChars()) return 0; } 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 +935,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 ); } @@ -694,18 +967,18 @@ void SwTextInputFieldPortion::Paint( const SwTextPaintInfo &rInf ) const // highlight empty input field, elsewhere they are completely invisible for the user SwRect aIntersect; rInf.CalcRect(*this, &aIntersect); - const sal_uInt16 aAreaWidth = rInf.GetTextSize(OUString(' ')).Width(); + const SwTwips aAreaWidth = rInf.GetTextSize(OUString(' ')).Width(); aIntersect.Left(aIntersect.Left() - aAreaWidth/2); aIntersect.Width(aAreaWidth); 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(); @@ -731,55 +1004,85 @@ bool SwTextInputFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString & return true; } -SwPosSize SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +SwPositiveSize SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const { SwTextSlot aFormatText( &rInf, this, true, false ); if (rInf.GetLen() == TextFrameIndex(0)) { - return SwPosSize( 0, 0 ); + return SwPositiveSize( 0, 0 ); } return rInf.GetTextSize(); } -SwHolePortion::SwHolePortion( const SwTextPortion &rPor ) +SwHolePortion::SwHolePortion(const SwTextPortion& rPor, bool bShowUnderline) : m_nBlankWidth( 0 ) + , m_bShowUnderline(bShowUnderline) { 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 +SwPositiveSize SwHolePortion::GetTextSize(const SwTextSizeInfo& rInf) const +{ + SwPositiveSize 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; - if( pOrigFont->GetUnderline() != LINESTYLE_NONE + std::optional<SwFontSave> oFontSave; + if( (!m_bShowUnderline && pOrigFont->GetUnderline() != LINESTYLE_NONE) || pOrigFont->GetOverline() != LINESTYLE_NONE || pOrigFont->GetStrikeout() != STRIKEOUT_NONE ) { pHoleFont.reset(new SwFont( *pOrigFont )); - pHoleFont->SetUnderline( LINESTYLE_NONE ); + if (!m_bShowUnderline) + 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(u" "_ustr, *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 +1096,21 @@ 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)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("show-underline"), + BAD_CAST(OString::boolean(m_bShowUnderline).getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + void SwFieldMarkPortion::Paint( const SwTextPaintInfo & /*rInf*/) const { // These shouldn't be painted! @@ -809,14 +1127,14 @@ void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo& rInf ) const { SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx())); - IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition); + Fieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition); OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, "Where is my form field bookmark???"); if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX) { - const ICheckboxFieldmark* pCheckboxFm = dynamic_cast<ICheckboxFieldmark const*>(pBM); + const CheckboxFieldmark* pCheckboxFm = dynamic_cast<CheckboxFieldmark const*>(pBM); bool bChecked = pCheckboxFm && pCheckboxFm->IsChecked(); rInf.DrawCheckBox(*this, bChecked); } @@ -825,7 +1143,7 @@ void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo& rInf ) const bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo & rInf ) { SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx())); - IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition); + Fieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition); OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, "Where is my form field bookmark???"); if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX) { diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx index ec44a2bafc76..132efe86dd20 100644 --- a/sw/source/core/text/portxt.hxx +++ b/sw/source/core/text/portxt.hxx @@ -35,10 +35,10 @@ public: virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool Format( SwTextFormatInfo &rInf ) override; virtual void FormatEOL( SwTextFormatInfo &rInf ) override; - virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(SwTwips nOfst) const override; + virtual SwPositiveSize 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; @@ -47,6 +47,8 @@ public: // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override; + + sal_uInt16 GetMaxComp(const SwTextFormatInfo &rInf) const; }; class SwTextInputFieldPortion : public SwTextPortion @@ -57,22 +59,28 @@ public: virtual bool Format( SwTextFormatInfo &rInf ) override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; - virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual SwPositiveSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; }; class SwHolePortion : public SwLinePortion { - sal_uInt16 m_nBlankWidth; + SwTwips m_nBlankWidth; + bool m_bShowUnderline; + public: - explicit SwHolePortion( const SwTextPortion &rPor ); - sal_uInt16 GetBlankWidth( ) const { return m_nBlankWidth; } - void SetBlankWidth( const sal_uInt16 nNew ) { m_nBlankWidth = nNew; } + explicit SwHolePortion(const SwTextPortion& rPor, bool bShowUnderline = false); + SwTwips GetBlankWidth() const { return m_nBlankWidth; } + void SetBlankWidth(const SwTwips nNew) { m_nBlankWidth = nNew; } virtual SwLinePortion *Compress() override; virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual SwPositiveSize 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..858ef70da594 100644 --- a/sw/source/core/text/possiz.hxx +++ b/sw/source/core/text/possiz.hxx @@ -19,52 +19,48 @@ #pragma once #include <tools/gen.hxx> -#include <sal/types.h> +#include <swtypes.hxx> -// Compared to the SV sizes SwPosSize is always positive -class SwPosSize +// Compared to the SV sizes SwPositiveSize is always positive +class SwPositiveSize { - 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 ) + SwPositiveSize( 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())) + explicit SwPositiveSize( const Size &rSize ) + : m_nWidth(SwTwips(rSize.Width())) + , m_nHeight(SwTwips(rSize.Height())) { } -#if defined(__COVERITY__) - virtual ~SwPosSize() COVERITY_NOEXCEPT_FALSE {} -#else - virtual ~SwPosSize() {} -#endif - SwPosSize(SwPosSize const &) = default; - SwPosSize(SwPosSize &&) = default; - SwPosSize & operator =(SwPosSize const &) = default; - SwPosSize & operator =(SwPosSize &&) = default; - 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; } + virtual ~SwPositiveSize() {} + SwPositiveSize(SwPositiveSize const &) = default; + SwPositiveSize(SwPositiveSize &&) = default; + SwPositiveSize & operator =(SwPositiveSize const &) = default; + SwPositiveSize & operator =(SwPositiveSize &&) = default; + SwTwips Height() const { return m_nHeight; } + virtual void Height(const SwTwips nNew, const bool = true) { m_nHeight = nNew; } + SwTwips Width() const { return m_nWidth; } + void Width( const SwTwips nNew ) { m_nWidth = nNew; } Size SvLSize() const { return Size( m_nWidth, m_nHeight ); } void SvLSize( const Size &rSize ) { - m_nWidth = 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 ) + SwPositiveSize& 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..87564e2a3fbb 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,13 @@ #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> +#include <officecfg/Office/Writer.hxx> using namespace ::com::sun::star; @@ -59,30 +66,37 @@ 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::Fieldmark 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; + SwNode const* m_pCurrentRedlineNode; public: SwPosition const* GetStartPos() const { return m_pStartPos; } SwPosition const* GetEndPos() const { return m_pEndPos; } - HideIterator(SwTextNode & rTextNode, - bool const isHideRedlines, sw::FieldmarkMode const eMode) + HideIterator(const SwTextNode & rTextNode, + bool const isHideRedlines, sw::FieldmarkMode const eMode, + sw::ParagraphBreakMode const ePBMode) : m_rIDRA(rTextNode.getIDocumentRedlineAccess()) , m_rIDMA(*rTextNode.getIDocumentMarkAccess()) , m_isHideRedlines(isHideRedlines) , m_eFieldmarkMode(eMode) + , m_isHideParagraphBreaks(ePBMode == sw::ParagraphBreakMode::Hidden) , m_Start(rTextNode, 0) - , m_RedlineIndex(m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any)) + , m_RedlineIndex(isHideRedlines ? m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any) : SwRedlineTable::npos) , m_pStartPos(nullptr) , m_pEndPos(&m_Start) + , m_pCurrentRedlineNode(&rTextNode) { } @@ -96,27 +110,34 @@ public: assert(m_pEndPos); if (m_isHideRedlines) { + // GetRedlinePos() returns npos if there is no redline on the + // node but something else could have merged nodes so search again! + if (m_RedlineIndex == SwRedlineTable::npos + && &m_pEndPos->GetNode() != m_pCurrentRedlineNode) + { + m_RedlineIndex = m_rIDRA.GetRedlinePos(m_pEndPos->GetNode(), RedlineType::Any); + m_pCurrentRedlineNode = &m_pEndPos->GetNode(); + } // position on current or next redline for (; m_RedlineIndex < m_rIDRA.GetRedlineTable().size(); ++m_RedlineIndex) { SwRangeRedline const*const pRed = m_rIDRA.GetRedlineTable()[m_RedlineIndex]; - if (m_pEndPos->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 +155,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( + sw::mark::Fieldmark 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 +173,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 +204,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; + } } } }; @@ -201,6 +275,47 @@ public: namespace sw { +void FindParaPropsNodeIgnoreHidden(sw::MergedPara & rMerged, + sw::ParagraphBreakMode const eMode, SwScriptInfo * pScriptInfo) +{ + if (eMode == sw::ParagraphBreakMode::Hidden) + { + ::std::optional<SwScriptInfo> oScriptInfo; + if (pScriptInfo == nullptr) + { + oScriptInfo.emplace(); + pScriptInfo = &*oScriptInfo; + } + // always init: when called from SwTextFrame::SwClientNotify() it is stale! + pScriptInfo->InitScriptInfoHidden(*rMerged.pFirstNode, &rMerged); + TextFrameIndex nHiddenStart{COMPLETE_STRING}; + TextFrameIndex nHiddenEnd{0}; + pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex{0}, nHiddenStart, nHiddenEnd); + if (TextFrameIndex{0} == nHiddenStart) + { + if (nHiddenEnd == TextFrameIndex{rMerged.mergedText.getLength()}) + { + rMerged.pParaPropsNode = const_cast<SwTextNode*>(rMerged.pLastNode); + } + else + { // this requires MapViewToModel to never return a position at + // the end of a node (when all its text is hidden) + rMerged.pParaPropsNode = sw::MapViewToModel(rMerged, nHiddenEnd).first; + } + return; + } + } + if (!rMerged.extents.empty()) + { // para props from first node that isn't empty (OOo/LO compat) + rMerged.pParaPropsNode = rMerged.extents.begin()->pNode; + } + else + { // if every node is empty, the last one wins (Word compat) + // (OOo/LO historically used first one) + rMerged.pParaPropsNode = const_cast<SwTextNode*>(rMerged.pLastNode); + } +} + std::unique_ptr<sw::MergedPara> CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, FrameMode const eMode) @@ -215,30 +330,31 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, std::vector<SwSectionNode *> sections; std::vector<sw::Extent> extents; OUStringBuffer mergedText; - SwTextNode * pParaPropsNode(nullptr); SwTextNode * pNode(&rTextNode); sal_Int32 nLastEnd(0); for (auto iter = HideIterator(rTextNode, rFrame.getRootFrame()->IsHideRedlines(), - rFrame.getRootFrame()->GetFieldmarkMode()); 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 +384,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 +398,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 +424,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()) @@ -355,22 +471,23 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, if (extents.empty()) // there was no text anywhere { assert(mergedText.isEmpty()); - pParaPropsNode = pNode; // if every node is empty, the last one wins } else { assert(!mergedText.isEmpty()); - pParaPropsNode = extents.begin()->pNode; // para props from first node that isn't empty } -// pParaPropsNode = &rTextNode; // well, actually... + auto pRet{std::make_unique<sw::MergedPara>(rFrame, std::move(extents), + mergedText.makeStringAndClear(), &rTextNode, nodes.back())}; + FindParaPropsNodeIgnoreHidden(*pRet, rFrame.getRootFrame()->GetParagraphBreakMode(), nullptr); + assert(pRet->pParaPropsNode); // keep lists up to date with visible nodes - if (pParaPropsNode->IsInList() && !pParaPropsNode->GetNum(rFrame.getRootFrame())) + if (pRet->pParaPropsNode->IsInList() && !pRet->pParaPropsNode->GetNum(rFrame.getRootFrame())) { - pParaPropsNode->AddToListRLHidden(); // try to add it... + pRet->pParaPropsNode->AddToListRLHidden(); // try to add it... } for (auto const pTextNode : nodes) { - if (pTextNode != pParaPropsNode) + if (pTextNode != pRet->pParaPropsNode) { pTextNode->RemoveFromListRLHidden(); } @@ -384,12 +501,12 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, // for non-first nodes that are already merged with this frame, // need to remove here too, otherwise footnotes can be removed only // by lucky accident, e.g. TruncLines(). - auto itExtent(extents.begin()); + auto itExtent(pRet->extents.begin()); for (auto const pTextNode : nodes) { sal_Int32 nLast(0); std::vector<std::pair<sal_Int32, sal_Int32>> hidden; - for ( ; itExtent != extents.end(); ++itExtent) + for ( ; itExtent != pRet->extents.end(); ++itExtent) { if (itExtent->pNode != pTextNode) { @@ -422,12 +539,9 @@ 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), - mergedText.makeStringAndClear(), pParaPropsNode, &rTextNode, - nodes.back())); for (SwTextNode * pTmp : nodes) { pRet->listener.StartListening(pTmp); @@ -441,7 +555,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 +625,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 +760,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; @@ -667,14 +783,14 @@ SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE m_pExt.reset(); } -// The return value of SwRedlineItr::Seek tells you if the current font -// has been manipulated by leaving (-1) or accessing (+1) of a section +/// The return value of SwRedlineItr::Seek tells if the current font +/// has been manipulated by leaving (-1) or entering (+1) a range redline 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() ) - return 0; // Abbreviation: if we're within an ExtendTextInputs + return 0; // shortcut: if we're within an ExtendTextInputs // there can't be other changes of attributes (not even by redlining) if (m_eMode == Mode::Show) { @@ -683,37 +799,57 @@ short SwRedlineItr::Seek(SwFont& rFnt, if (nNew >= m_nEnd) { --nRet; - Clear_( &rFnt ); // We go behind the current section - ++m_nAct; // and check the next one + Clear_( &rFnt ); // We go behind the current range +// ++m_nAct; // don't increment, could be in next range too if overlap } else if (nNew < m_nStart) { --nRet; - Clear_( &rFnt ); // We go in front of the current section + Clear_( &rFnt ); // We go before the current range if (m_nAct > m_nFirst) - m_nAct = m_nFirst; // the test has to run from the beginning + m_nAct = m_nFirst; // need to start over else return nRet + EnterExtend(rFnt, nNode, nNew); // There's none prior to us } else - return nRet + EnterExtend(rFnt, nNode, nNew); // We stayed in the same section + return nRet + EnterExtend(rFnt, nNode, nNew); // We stayed in the same range } if (SwRedlineTable::npos == m_nAct || nOld > nNew) m_nAct = m_nFirst; m_nStart = COMPLETE_STRING; m_nEnd = COMPLETE_STRING; + const SwRedlineTable& rTable = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + ::std::optional<decltype(m_nAct)> oFirstMatch; - 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); + decltype(m_nStart) nStart; + decltype(m_nEnd) nEnd; + if (rTable[m_nAct]->CalcStartEnd(nNode, nStart, nEnd)) + { // previous redline intersected nNode but this one precedes it + continue; + } - if (nNew < m_nEnd) + // redline table is sorted, but here it's not the complete redlines + assert(m_nStart == COMPLETE_STRING || m_nStart <= nStart); + assert(m_nStart == COMPLETE_STRING || m_nStart <= nEnd); + if (oFirstMatch && nNew < nStart) + { + m_nEnd = std::min(m_nEnd, nStart); + break; + } + if (nNew < nEnd) { - if (nNew >= m_nStart) // only possible candidate + m_nStart = nStart; + m_nEnd = std::min(m_nEnd, nEnd); + if (nStart <= nNew) // there can be a format and another redline... { - m_bOn = true; - const SwRangeRedline *pRed = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ]; + if (!oFirstMatch) + { + oFirstMatch.emplace(m_nAct); + } + const SwRangeRedline *pRed = rTable[ m_nAct ]; if (m_pSet) m_pSet->ClearItem(); @@ -721,7 +857,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 +865,24 @@ short SwRedlineItr::Seek(SwFont& rFnt, FillHints( pRed->GetAuthor(), pRed->GetType() ); SfxWhichIter aIter( *m_pSet ); + + // moved text: dark green with double underline or strikethrough + bool bDisplayMovedTextInGreen = officecfg::Office::Writer::Comparison::DisplayMovedTextInGreen::get(); + if ( bDisplayMovedTextInGreen && 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), @@ -745,13 +893,19 @@ short SwRedlineItr::Seek(SwFont& rFnt, } nWhich = aIter.NextWhich(); } - - ++nRet; } - break; + else + { + break; + } } - m_nStart = COMPLETE_STRING; - m_nEnd = COMPLETE_STRING; + } + + if (oFirstMatch) + { + m_bOn = true; + m_nAct = *oFirstMatch; // rewind + ++nRet; // increment only once per m_nStart/m_nEnd range } } else if (m_eMode == Mode::Hide) @@ -773,9 +927,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; @@ -792,14 +946,15 @@ void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType ) switch ( eType ) { case RedlineType::Insert: - SW_MOD()->GetInsertAuthorAttr(nAuthor, *m_pSet); + SwModule::get()->GetInsertAuthorAttr(nAuthor, *m_pSet); break; case RedlineType::Delete: - SW_MOD()->GetDeletedAuthorAttr(nAuthor, *m_pSet); + SwModule::get()->GetDeletedAuthorAttr(nAuthor, *m_pSet); break; case RedlineType::Format: case RedlineType::FmtColl: - SW_MOD()->GetFormatAuthorAttr(nAuthor, *m_pSet); + case RedlineType::ParagraphFormat: + SwModule::get()->GetFormatAuthorAttr(nAuthor, *m_pSet); break; default: break; @@ -837,7 +992,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 +1094,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 +1133,8 @@ bool SwRedlineItr::CheckLine( eRedlineEnd = pRedline->GetType(); bRedlineEnd = true; isBreak = true; + if (pAuthorAtPos) + *pAuthorAtPos = pRedline->GetAuthor(); [[fallthrough]]; case SwComparePosition::OverlapBefore: case SwComparePosition::CollideEnd: @@ -987,7 +1144,7 @@ bool SwRedlineItr::CheckLine( // start to collect text of invisible redlines for ChangesInMargin layout if (rRedlineText.isEmpty() && !pRedline->IsVisible()) { - rRedlineText = const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true); + rRedlineText = pRedline->GetDescr(/*bSimplified=*/true); pPrevRedline = pRedline; isExtendText = true; } @@ -996,7 +1153,7 @@ bool SwRedlineItr::CheckLine( else if (pPrevRedline && !pRedline->IsVisible() && *pRedline->Start() == *pPrevRedline->Start() && *pRedline->End() == *pPrevRedline->End() ) { - OUString sExtendText(const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true)); + OUString sExtendText(pRedline->GetDescr(/*bSimplified=*/true)); if (!sExtendText.isEmpty()) { if (rRedlineText.getLength() < 12) @@ -1004,7 +1161,7 @@ bool SwRedlineItr::CheckLine( // TODO: remove extra space from GetDescr(true), // but show deletion of paragraph or line break rRedlineText = rRedlineText + - const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true).subView(1); + pRedline->GetDescr(/*bSimplified=*/true).subView(1); } else rRedlineText = OUString::Concat(rRedlineText.subView(0, rRedlineText.getLength() - 3)) + "..."; @@ -1032,6 +1189,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 +1211,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 +1227,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 +1252,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..681785bbbb0a 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; @@ -45,8 +46,7 @@ using namespace ::com::sun::star; * The width and height of the drop caps portion are passed as arguments, * the position is calculated from the values in rInf */ -static bool lcl_IsDropFlyInter( const SwTextFormatInfo &rInf, - sal_uInt16 nWidth, sal_uInt16 nHeight ) +static bool lcl_IsDropFlyInter(const SwTextFormatInfo& rInf, SwTwips nWidth, sal_uInt16 nHeight) { const SwTextFly& rTextFly = rInf.GetTextFly(); if( rTextFly.IsOn() ) @@ -102,9 +102,9 @@ SwDropPortionPart::~SwDropPortionPart() /// SwDropPortion CTor, DTor SwDropPortion::SwDropPortion( const sal_uInt16 nLineCnt, - const sal_uInt16 nDrpHeight, - const sal_uInt16 nDrpDescent, - const sal_uInt16 nDist ) + const SwTwips nDrpHeight, + const SwTwips nDrpDescent, + const SwTwips nDist ) : m_nLines( nLineCnt ), m_nDropHeight(nDrpHeight), m_nDropDescent(nDrpDescent), @@ -303,8 +303,8 @@ void SwDropPortion::PaintText( const SwTextPaintInfo &rInf ) const const SwDropPortionPart* pCurrPart = GetPart(); const TextFrameIndex nOldLen = GetLen(); - const sal_uInt16 nOldWidth = Width(); - const sal_uInt16 nOldAscent = GetAscent(); + const SwTwips nOldWidth = Width(); + const SwTwips nOldAscent = GetAscent(); const SwTwips nBasePosY = rInf.Y(); const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY + m_nY ); @@ -324,7 +324,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 ); @@ -352,9 +352,9 @@ void SwDropPortion::PaintDrop( const SwTextPaintInfo &rInf ) const return; // set the lying values - const sal_uInt16 nOldHeight = Height(); - const sal_uInt16 nOldWidth = Width(); - const sal_uInt16 nOldAscent = GetAscent(); + const SwTwips nOldHeight = Height(); + const SwTwips nOldWidth = Width(); + const SwTwips nOldAscent = GetAscent(); const SwTwips nOldPosY = rInf.Y(); const SwTwips nOldPosX = rInf.X(); const SwParaPortion *pPara = rInf.GetParaPortion(); @@ -397,7 +397,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 @@ -432,9 +432,9 @@ bool SwDropPortion::FormatText( SwTextFormatInfo &rInf ) return true; } -SwPosSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +SwPositiveSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const { - sal_uInt16 nMyX = 0; + SwTwips nMyX = 0; TextFrameIndex nIdx(0); const SwDropPortionPart* pCurrPart = GetPart(); @@ -461,7 +461,7 @@ SwPosSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const // robust SwFontSave aFontSave( rInf, pCurrPart ? &pCurrPart->GetFont() : nullptr ); - SwPosSize aPosSize( SwTextPortion::GetTextSize( rInf ) ); + SwPositiveSize aPosSize( SwTextPortion::GetTextSize( rInf ) ); aPosSize.Width( aPosSize.Width() + nMyX ); const_cast<SwTextSizeInfo&>(rInf).SetIdx( nOldIdx ); @@ -475,7 +475,7 @@ SwPosSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const return aPosSize; } -TextFrameIndex SwDropPortion::GetModelPositionForViewPoint(const sal_uInt16) const +TextFrameIndex SwDropPortion::GetModelPositionForViewPoint(const SwTwips) const { return TextFrameIndex(0); } @@ -483,9 +483,9 @@ TextFrameIndex SwDropPortion::GetModelPositionForViewPoint(const sal_uInt16) con 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 nDropHght = 0; + SwTwips nAscent = 0; + SwTwips nHeight = 0; sal_uInt16 nDropLns = 0; const bool bRegisterOld = IsRegisterOn(); m_bRegisterOn = false; @@ -545,8 +545,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 +678,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() ); @@ -698,9 +698,9 @@ class SwDropCapCache { const void* m_aFontCacheId[ DROP_CACHE_SIZE ] = {}; OUString m_aText[ DROP_CACHE_SIZE ]; - sal_uInt16 m_aFactor[ DROP_CACHE_SIZE ]; - sal_uInt16 m_aWishedHeight[ DROP_CACHE_SIZE ] = {}; - short m_aDescent[ DROP_CACHE_SIZE ]; + tools::Long m_aFactor[ DROP_CACHE_SIZE ]; + SwTwips m_aWishedHeight[DROP_CACHE_SIZE] = {}; + SwTwips m_aDescent[DROP_CACHE_SIZE]; sal_uInt16 m_nIndex = 0; public: SwDropCapCache() = default; @@ -751,7 +751,7 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf m_nIndex %= DROP_CACHE_SIZE; nTmpIdx = m_nIndex; - tools::Long nWishedHeight = pDrop->GetDropHeight(); + SwTwips nWishedHeight = pDrop->GetDropHeight(); tools::Long nAscent = 0; // find out biggest font size for initial scaling factor @@ -773,15 +773,15 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf // save keys for cache m_aFontCacheId[ nTmpIdx ] = nFntCacheId; m_aText[ nTmpIdx ] = aStr; - m_aWishedHeight[ nTmpIdx ] = sal_uInt16(nWishedHeight); + m_aWishedHeight[ nTmpIdx ] = nWishedHeight; // save initial scaling factor - m_aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor); + m_aFactor[ nTmpIdx ] = nFactor; } bool bGrow = (pDrop->GetLen() != TextFrameIndex(0)); // for growing control - tools::Long nMax = USHRT_MAX; + tools::Long nMax = std::numeric_limits<tools::Long>::max(); tools::Long nMin = 0; #if OSL_DEBUG_LEVEL > 1 tools::Long nGrow = 0; @@ -793,10 +793,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 +860,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 +890,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 +938,7 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf else { if ( bUseCache ) - m_aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor); + m_aFactor[ nTmpIdx ] = nFactor; nMin = nFactor; } @@ -982,7 +995,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 = rInf.X(); SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); aLayoutModeModifier.SetAuto(); @@ -995,7 +1008,7 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) // adjust font sizes to fit into the rectangle pDropCapCache->CalcFontSize( this, rInf ); - const tools::Long nOldX = rInf.X(); + const SwTwips nOldX = rInf.X(); { SwDropSave aSave( rInf ); SwDropPortionPart* pCurrPart = m_pPart.get(); @@ -1020,7 +1033,7 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) Width(); // set values - pCurrPart->SetWidth( static_cast<sal_uInt16>(nTmpWidth) ); + pCurrPart->SetWidth(nTmpWidth); // Move rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() ); @@ -1029,7 +1042,7 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) } SetJoinBorderWithNext(false); SetJoinBorderWithPrev(false); - Width( static_cast<sal_uInt16>(rInf.X() - nOldX) ); + Width(rInf.X() - nOldX); } // reset my length @@ -1065,10 +1078,10 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) m_nDistance = 0; else { - const sal_uInt16 nWant = Width() + GetDistance(); - const sal_uInt16 nRest = static_cast<sal_uInt16>(rInf.Width() - rInf.X()); + const SwTwips nWant = Width() + GetDistance(); + const SwTwips nRest = rInf.Width() - rInf.X(); if( ( nWant > nRest ) || - lcl_IsDropFlyInter( rInf, Width() + GetDistance(), m_nDropHeight ) ) + lcl_IsDropFlyInter( rInf, nWant, m_nDropHeight ) ) m_nDistance = 0; Width( Width() + m_nDistance ); diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx index e150f7f489f1..13100a23ed77 100644 --- a/sw/source/core/text/txtfld.cxx +++ b/sw/source/core/text/txtfld.cxx @@ -55,244 +55,153 @@ #include <svl/grabbagitem.hxx> #include <svl/itemiter.hxx> #include <svl/whiter.hxx> +#include <editeng/cmapitem.hxx> #include <editeng/colritem.hxx> #include <editeng/udlnitem.hxx> #include <editeng/crossedoutitem.hxx> +#include <officecfg/Office/Writer.hxx> static bool lcl_IsInBody( SwFrame const *pFrame ) { if ( pFrame->IsInDocBody() ) return true; - 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 ); + SwDocStatField* pDocStatField = static_cast<SwDocStatField*>(pField); + sal_uInt16 nVirtPageCount = 0; + if (pDocStatField->GetSubType() == SwDocStatSubType::PageRange) + nVirtPageCount = m_pFrame->GetVirtPageCount(); + pDocStatField->ChangeExpansion(m_pFrame, nVirtPageCount); } - 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(); + auto pPageNumberField = static_cast<SwPageNumberField*>(pField); 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 == pPageNumberField->GetFormat()) + nNumFormat + = m_pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType(); + pPageNumberField->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 ) + if (!bName) { - SwDBField* pDBField = static_cast<SwDBField*>(pField); - pDBField->ChgBodyTextFlag( ::lcl_IsInBody( pFrame ) ); - } - { - 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() ) + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(*pFrame, - static_txtattr_cast<SwTextField const*>(pHint)); - } - { - 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(); + { + auto pJumpEditField = static_cast<SwJumpEditField*>(pField); + 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( + &pJumpEditField->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), pJumpEditField->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() == ReferencesSubtype::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 +213,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 +315,12 @@ SwLinePortion *SwTextFormatter::NewExtraPortion( SwTextFormatInfo &rInf ) } if( !pRet ) { - pRet = new SwFieldPortion( "" ); + auto pFieldPortion = new SwFieldPortion( u""_ustr ); + if (pHint->Which() == RES_TXTATR_CONTENTCONTROL) + { + pFieldPortion->SetContentControl(true); + } + pRet = pFieldPortion; rInf.SetLen(TextFrameIndex(1)); } return pRet; @@ -423,39 +338,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; @@ -466,18 +351,20 @@ static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextForm { // Insert attributes of referenced char format into current set const SwFormatCharFormat& rCharFormat = pCleanedSet->Get(RES_TXTATR_CHARFMT); - const SwAttrSet& rStyleAttrs = static_cast<const SwCharFormat *>(rCharFormat.GetRegisteredIn())->GetAttrSet(); + const SwAttrSet& rStyleAttrs = rCharFormat.GetCharFormat()->GetAttrSet(); SfxWhichIter aIter(rStyleAttrs); sal_uInt16 nWhich = aIter.FirstWhich(); while (nWhich) { + const SfxPoolItem* pItem = nullptr; if (!SwTextNode::IsIgnoredCharFormatForNumbering(nWhich, /*bIsCharStyle=*/true) && !pCleanedSet->HasItem(nWhich) - && !(pFormat && pFormat->HasItem(nWhich)) ) + && !(pFormat && pFormat->HasItem(nWhich)) + && rStyleAttrs.GetItemState(nWhich, true, &pItem) > SfxItemState::DEFAULT) { // Copy from parent sets only allowed items which will not overwrite // values explicitly defined in current set (pCleanedSet) or in pFormat - if (const SfxPoolItem* pItem = rStyleAttrs.GetItem(nWhich, true)) + if (pItem) pCleanedSet->Put(*pItem); } nWhich = aIter.NextWhich(); @@ -503,8 +390,8 @@ static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextForm if (pCleanedSet->HasItem(RES_CHRATR_GRABBAG)) { SfxGrabBagItem aGrabBag = pCleanedSet->Get(RES_CHRATR_GRABBAG, /*bSrchInParent=*/false); - std::map<OUString, css::uno::Any>& rMap = aGrabBag.GetGrabBag(); - auto aIterator = rMap.find("CharShadingMarker"); + const std::map<OUString, css::uno::Any>& rMap = aGrabBag.GetGrabBag(); + auto aIterator = rMap.find(u"CharShadingMarker"_ustr); if (aIterator != rMap.end()) aIterator->second >>= bShadingWasImported; } @@ -517,6 +404,13 @@ static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextForm pCleanedSet->ClearItem(pItem->Which()); } } + else if (pItem->Which() == RES_CHRATR_CASEMAP) + { + SvxCaseMap eCaseMap = static_cast<const SvxCaseMapItem*>(pItem)->GetCaseMap(); + // MS only knows about "all caps" and "small caps". Small caps is not set on numbering + if (eCaseMap == SvxCaseMap::SmallCaps) + pCleanedSet->ClearItem(pItem->Which()); + } pItem = aIter.NextItem(); }; @@ -530,63 +424,85 @@ 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 + bool bDisplayMovedTextInGreen = officecfg::Office::Writer::Comparison::DisplayMovedTextInGreen::get(); + if ( bDisplayMovedTextInGreen && 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); + SwModule::get()->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() ); + SwModule::get()->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 +573,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,17 +631,45 @@ 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 aTextNow( 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(); + // previous (outdated) text + OUString aOriginalText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::ORIGTEXT) ); + + if ( !aTextNow.isEmpty() || !aOriginalText.isEmpty() ) + { + + bool bDisplayChangedParagraphNumbering = officecfg::Office::Writer::Comparison::DisplayChangedParagraphNumbering::get(); + if (bDisplayChangedParagraphNumbering && aTextNow != aOriginalText && !aOriginalText.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.". + aTextNow = aTextNow + "[" + aOriginalText + "]" + + pTextNd->GetLabelFollowedBy().replaceAll("\t", " "); + } + else if (!aTextNow.isEmpty()) + aTextNow += pTextNd->GetLabelFollowedBy(); + } } + else if (pTextNd->getIDocumentSettingAccess()->get(DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY) + || !aTextNow.isEmpty()) + aTextNow += pTextNd->GetLabelFollowedBy(); // Not just an optimization ... // A number portion without text will be assigned a width of 0. // The succeeding text portion will flow into the BreakCut in the BreakLine, // although we have rInf.GetLast()->GetFlyPortion()! - if( !aText.isEmpty() ) + if( !aTextNow.isEmpty() ) { // Build a new numbering font basing on the current paragraph font: @@ -748,12 +692,13 @@ 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(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor()); // we do not allow a vertical font pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() ); - pRet = new SwNumberPortion( aText, std::move(pNumFnt), + pRet = new SwNumberPortion( aTextNow, std::move(pNumFnt), bLeft, bCenter, nMinDist, bLabelAlignmentPosAndSpaceModeActive ); } diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx index a5fb1f6b6731..9c47224cdfc5 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) @@ -175,13 +178,14 @@ SwRect SwContourCache::CalcBoundRect( const SwAnchoredObject* pAnchoredObj, } } + const SwFlyFrame* pFlyFrame = pAnchoredObj->DynCastFlyFrame(); + const SwFrame* pLower = pFlyFrame + ? static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower() : nullptr; if( bHandleContour && - ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr || - ( static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower() && - static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower()->IsNoTextFrame() ) ) ) + ( !pFlyFrame || ( pLower && pLower->IsNoTextFrame() ) ) ) { aRet = pAnchoredObj->GetObjRectWithSpaces(); - if( aRet.IsOver( rLine ) ) + if( aRet.Overlaps( rLine ) ) { if( !pContourCache ) pContourCache = new SwContourCache; @@ -216,7 +220,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,29 +228,28 @@ 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()), - pFormat->GetSurround().IsOutside(), false, pFrame->IsVertical() ) - }; + CacheItem item{ pObj, // due to #37347 the Object must be entered only after GetContour() + std::make_unique<TextRanger>( + aPolyPolygon, pPolyPolygon ? &*pPolyPolygon : nullptr, 20, + o3tl::narrowing<sal_uInt16>(rLRSpace.ResolveLeft({})), + o3tl::narrowing<sal_uInt16>(rLRSpace.ResolveRight({})), + pFormat->GetSurround().IsOutside(), false, pFrame->IsVertical()) }; mvItems.insert(mvItems.begin(), std::move(item)); mvItems[0].mxTextRanger->SetUpper( rULSpace.GetUpper() ); mvItems[0].mxTextRanger->SetLower( rULSpace.GetLower() ); @@ -371,7 +374,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 +415,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 +438,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 +494,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 +502,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 +528,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 +579,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,26 +591,27 @@ 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 // and <SwAlignRect(..)> fly frame area // #i47804# - consider transparent graphics // and OLE objects. + const SwFrame* pLower = pFly->Lower(); bool bClipFlyArea = ( ( css::text::WrapTextMode_THROUGH == rSur.GetSurround() ) // #i68520# ? (pAnchoredObjTmp->GetDrawObj()->GetLayer() != nHellId) : !rSur.IsContour() ) && !pFly->IsBackgroundTransparent() && - ( !pFly->Lower() || - !pFly->Lower()->IsNoTextFrame() || - !static_cast<const SwNoTextFrame*>(pFly->Lower())->IsTransparent() ); + ( !pLower || + !pLower->IsNoTextFrame() || + !static_cast<const SwNoTextFrame*>(pLower)->IsTransparent() ); if ( bClipFlyArea ) { // #i68520# @@ -648,8 +657,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 +666,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 +679,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 +712,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 +763,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 +771,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 +897,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 +929,7 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() !pAnchoredObj->ConsiderForTextWrap() || ( mbIgnoreObjsInHeaderFooter && !bFooterHeader && pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) || - ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat().GetFollowTextFlow().GetValue() ) + ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat()->GetFollowTextFlow().GetValue() ) ) { continue; @@ -928,13 +967,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 +990,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 +1008,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 +1029,71 @@ 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; + } + + const SwFormatSurround& rSurround = pAnchoredObj->GetFrameFormat()->GetSurround(); + if (rSurround.GetValue() == text::WrapTextMode_THROUGH) + { + // Wrap through has no influence on clearing breaks. + 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 +1118,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 +1132,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 +1169,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 +1197,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 +1224,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 +1277,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 +1315,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 +1343,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 +1417,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 acd1b1b4f5be..bea6d81e3b44 100644 --- a/sw/source/core/text/txtfrm.cxx +++ b/sw/source/core/text/txtfrm.cxx @@ -17,14 +17,17 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ +#include <config_wasm_strip.h> + #include <hintids.hxx> #include <hints.hxx> #include <svl/ctloptions.hxx> #include <editeng/lspcitem.hxx> #include <editeng/lrspitem.hxx> #include <editeng/brushitem.hxx> +#include <editeng/charhiddenitem.hxx> #include <editeng/pgrditem.hxx> -#include <unotools/configmgr.hxx> +#include <comphelper/configuration.hxx> #include <swmodule.hxx> #include <SwSmartTagMgr.hxx> #include <doc.hxx> @@ -66,11 +69,15 @@ #include <fmtflcnt.hxx> #include <fmtcntnt.hxx> #include <numrule.hxx> -#include <IGrammarContact.hxx> +#include <GrammarContact.hxx> #include <calbck.hxx> #include <ftnidx.hxx> #include <ftnfrm.hxx> +#include <wrtsh.hxx> +#include <view.hxx> +#include <edtwin.hxx> +#include <FrameControlsManager.hxx> namespace sw { @@ -281,15 +288,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 @@ -321,16 +328,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); } } @@ -338,19 +345,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))) @@ -379,8 +383,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); @@ -398,10 +401,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; @@ -709,31 +712,30 @@ 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 ); } -SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang ) : +SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang, + SvtCTLOptions::TextNumerals eCTLTextNumerals ) : 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(); - - if ( SvtCTLOptions::NUMERALS_HINDI == nTextNumerals ) + if ( SvtCTLOptions::NUMERALS_HINDI == eCTLTextNumerals ) eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; - else if ( SvtCTLOptions::NUMERALS_ARABIC == nTextNumerals ) + else if ( SvtCTLOptions::NUMERALS_ARABIC == eCTLTextNumerals ) eLang = LANGUAGE_ENGLISH; - else if ( SvtCTLOptions::NUMERALS_SYSTEM == nTextNumerals ) + else if ( SvtCTLOptions::NUMERALS_SYSTEM == eCTLTextNumerals ) eLang = ::GetAppLanguage(); } @@ -771,6 +773,8 @@ SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib, , mnHeightOfLastLine( 0 ) , mnAdditionalFirstLineOffset( 0 ) , mnOffset( 0 ) + , mnNoHyphOffset( COMPLETE_STRING ) + , mnNoHyphEndZone( 0 ) , mnCacheIndex( USHRT_MAX ) , mbLocked( false ) , mbWidow( false ) @@ -791,6 +795,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, @@ -809,13 +931,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 > 0 && rTextNode == (rFootnoteIdxs[ nPos ]->GetTextNode())) --nPos; - if (nPos || &rTextNode != &(rFootnoteIdxs[ nPos ]->GetTextNode())) + if (nPos || rTextNode != (rFootnoteIdxs[ nPos ]->GetTextNode())) ++nPos; } size_t iter(0); @@ -878,6 +1000,12 @@ void SwTextFrame::DestroyImpl() } } + if (!GetDoc().IsInDtor()) + { + if (SwView* pView = GetActiveView()) + pView->GetEditWin().GetFrameControlsManager().RemoveControls(this); + } + SwContentFrame::DestroyImpl(); } @@ -891,6 +1019,7 @@ namespace sw { // 1. if real insert => correct nStart/nEnd for full nLen // 2. if rl un-delete => do not correct nStart/nEnd but just include un-deleted static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged, + sw::ParagraphBreakMode const eMode, SwScriptInfo *const pScriptInfo, bool const isRealInsert, SwTextNode const& rNode, sal_Int32 const nIndex, sal_Int32 const nLen) { @@ -1002,13 +1131,6 @@ static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged, rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen); text.insert(nTFIndex, rNode.GetText().subView(nIndex, nLen)); nInserted = nLen; - if (rMerged.extents.size() == 1 // also if it was empty! - || rMerged.pParaPropsNode->GetIndex() < rNode.GetIndex()) - { // text inserted after current para-props node - rMerged.pParaPropsNode->RemoveFromListRLHidden(); - rMerged.pParaPropsNode = &const_cast<SwTextNode&>(rNode); - rMerged.pParaPropsNode->AddToListRLHidden(); - } // called from SwRangeRedline::InvalidateRange() if (rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden) { @@ -1016,12 +1138,24 @@ static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged, } } rMerged.mergedText = text.makeStringAndClear(); + if ((!bInserted && rMerged.extents.size() == 1) // also if it was empty! + || rNode.GetIndex() <= rMerged.pParaPropsNode->GetIndex()) + { // text inserted before current para-props node + SwTextNode *const pOldParaPropsNode{rMerged.pParaPropsNode}; + FindParaPropsNodeIgnoreHidden(rMerged, eMode, pScriptInfo); + if (rMerged.pParaPropsNode != pOldParaPropsNode) + { + pOldParaPropsNode->RemoveFromListRLHidden(); + rMerged.pParaPropsNode->AddToListRLHidden(); + } + } return TextFrameIndex(nInserted); } // 1. if real delete => correct nStart/nEnd for full nLen // 2. if rl delete => do not correct nStart/nEnd but just exclude deleted TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, + sw::ParagraphBreakMode const eMode, SwScriptInfo *const pScriptInfo, bool const isRealDelete, SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 const nLen) { @@ -1032,7 +1166,7 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, sal_Int32 nToDelete(nLen); sal_Int32 nDeleted(0); size_t nFoundNode(0); - size_t nErased(0); +// size_t nErased(0); auto it = rMerged.extents.begin(); for (; it != rMerged.extents.end(); ) { @@ -1068,7 +1202,7 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, bErase = nDeleteHere == it->nEnd - it->nStart; if (bErase) { - ++nErased; +// ++nErased; assert(it->nStart == nIndex); it = rMerged.extents.erase(it); } @@ -1134,21 +1268,23 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, // can't do: might be last one in node was erased assert(nLen == 0 || rMerged.empty() || (it-1)->nEnd <= nIndex); // note: if first node gets deleted then that must call DelFrames as // pFirstNode is never updated - if (nErased && nErased == nFoundNode) + rMerged.mergedText = text.makeStringAndClear(); +// could be all-hidden now so always check! if (nErased && nErased == nFoundNode) { // all visible text from node was erased #if 1 if (rMerged.pParaPropsNode == &rNode) { - rMerged.pParaPropsNode->RemoveFromListRLHidden(); - rMerged.pParaPropsNode = rMerged.extents.empty() - ? const_cast<SwTextNode*>(rMerged.pLastNode) - : rMerged.extents.front().pNode; - rMerged.pParaPropsNode->AddToListRLHidden(); + SwTextNode *const pOldParaPropsNode{rMerged.pParaPropsNode}; + FindParaPropsNodeIgnoreHidden(rMerged, eMode, pScriptInfo); + if (rMerged.pParaPropsNode != pOldParaPropsNode) + { + pOldParaPropsNode->RemoveFromListRLHidden(); + rMerged.pParaPropsNode->AddToListRLHidden(); + } } #endif // NOPE must listen on all non-hidden nodes; particularly on pLastNode rMerged.listener.EndListening(&const_cast<SwTextNode&>(rNode)); } - rMerged.mergedText = text.makeStringAndClear(); return TextFrameIndex(nDeleted); } @@ -1251,15 +1387,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); } @@ -1275,7 +1410,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 @@ -1333,11 +1468,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() @@ -1349,6 +1484,19 @@ void SwTextFrame::ResetPreps() } } +static auto FindCellFrame(SwFrame const* pLower) -> SwLayoutFrame const* +{ + while (pLower) + { + if (pLower->IsCellFrame()) + { + return static_cast<SwLayoutFrame const*>(pLower); + } + pLower = pLower->GetUpper(); + } + return nullptr; +} + bool SwTextFrame::IsHiddenNow() const { SwFrameSwapper aSwapper( this, true ); @@ -1359,26 +1507,40 @@ bool SwTextFrame::IsHiddenNow() const return true; } + // TODO: what is the above check good for and can it be removed? + return IsHiddenNowImpl(); +} + +bool SwTextFrame::IsHiddenNowImpl() const +{ + if (SwContentFrame::IsHiddenNow()) + return true; + bool bHiddenCharsHidePara(false); bool bHiddenParaField(false); if (m_pMergedPara) { TextFrameIndex nHiddenStart(COMPLETE_STRING); TextFrameIndex nHiddenEnd(0); + bool hasHidden{false}; if (auto const pScriptInfo = GetScriptInfo()) { - pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0), + hasHidden = pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0), nHiddenStart, nHiddenEnd); } else // ParaPortion is created in Format, but this is called earlier { SwScriptInfo aInfo; - aInfo.InitScriptInfo(*m_pMergedPara->pFirstNode, m_pMergedPara.get(), IsRightToLeft()); - aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0), + aInfo.InitScriptInfoHidden(*m_pMergedPara->pFirstNode, m_pMergedPara.get()); + hasHidden = aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0), nHiddenStart, nHiddenEnd); } - if (TextFrameIndex(0) == nHiddenStart && - TextFrameIndex(GetText().getLength()) <= nHiddenEnd) + if ((TextFrameIndex(0) == nHiddenStart + && TextFrameIndex(GetText().getLength()) <= nHiddenEnd) + // special case: GetBoundsOfHiddenRange doesn't assign! + // but it does return that there *is* something hidden, in case + // the frame is empty then the whole thing must be hidden + || (hasHidden && m_pMergedPara->mergedText.isEmpty())) { bHiddenCharsHidePara = true; } @@ -1412,6 +1574,46 @@ bool SwTextFrame::IsHiddenNow() const bHiddenCharsHidePara = static_cast<SwTextNode const*>(SwFrame::GetDep())->HasHiddenCharAttribute( true ); bHiddenParaField = static_cast<SwTextNode const*>(SwFrame::GetDep())->IsHiddenByParaField(); } + if (bHiddenCharsHidePara && GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH)) + { + // apparently in Word it's always the last para marker that determines hidden? + // even in case when they are merged by delete redline (it's obvious when they are merged by hidden-attribute + SwTextNode const*const pNode{ m_pMergedPara + ? m_pMergedPara->pLastNode + : static_cast<SwTextNode const*>(SwFrame::GetDep()) }; + // Word ignores hidden formatting on the cell end marker + bool isLastInCell{false}; + if (SwLayoutFrame const*const pCellFrame{FindCellFrame(this)}) + { + SwContentFrame const* pNext{GetNextContentFrame()}; + // skip frame in hidden section ("this" is *not* in hidden section!) + while (pNext && pNext->SwContentFrame::IsHiddenNow()) + { + pNext = pNext->GetNextContentFrame(); + } + isLastInCell = pNext == nullptr || !pCellFrame->IsAnLower(pNext); + } + if (!isLastInCell) + { + SwFormatAutoFormat const& rListAutoFormat{pNode->GetAttr(RES_PARATR_LIST_AUTOFMT)}; + std::shared_ptr<SfxItemSet> const pSet{rListAutoFormat.GetStyleHandle()}; + SvxCharHiddenItem const* pItem{pSet ? pSet->GetItemIfSet(RES_CHRATR_HIDDEN) : nullptr}; + if (!pItem) + { + // don't use node's mpAttrSet, it doesn't apply to para marker + SwFormatColl const*const pStyle{pNode->GetFormatColl()}; + if (pStyle) + { + pItem = &pStyle->GetFormatAttr(RES_CHRATR_HIDDEN); + } + } + if (!pItem || !pItem->GetValue()) + { + bHiddenCharsHidePara = false; + } + } + } const SwViewShell* pVsh = getRootFrame()->GetCurrShell(); if ( pVsh && ( bHiddenCharsHidePara || bHiddenParaField ) ) @@ -1424,6 +1626,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; } } @@ -1477,7 +1692,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 ); @@ -1491,9 +1706,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; @@ -1552,7 +1767,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 ); @@ -1586,13 +1801,18 @@ void SwTextFrame::HideAndShowObjects() { sal_Int32 nHiddenStart; sal_Int32 nHiddenEnd; - const SwPosition& rAnchor = pContact->GetContentAnchor(); + const SwFormatAnchor& rAnchorFormat = pContact->GetAnchorFormat(); + const SwNode* pNode = rAnchorFormat.GetAnchorNode(); + // When the object was already removed from text, but the layout hasn't been + // updated yet, this can be nullptr: + if (!pNode) + continue; SwScriptInfo::GetBoundsOfHiddenRange( - *rAnchor.nNode.GetNode().GetTextNode(), - rAnchor.nContent.GetIndex(), nHiddenStart, nHiddenEnd); + *pNode->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 ); } @@ -1616,21 +1836,33 @@ void SwTextFrame::HideAndShowObjects() } } +void SwLayoutFrame::HideAndShowObjects() +{ + for (SwFrame * pLower = Lower(); pLower; pLower = pLower->GetNext()) + { + pLower->HideAndShowObjects(); + } +} + +void SwFrame::HideAndShowObjects() +{ +} + /** * Returns the first possible break point in the current line. * This method is used in SwTextFrame::Format() to decide whether the previous * line has to be formatted as well. * nFound is <= nEndLine. */ -TextFrameIndex SwTextFrame::FindBrk(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++; } @@ -1639,7 +1871,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++; } @@ -1649,6 +1881,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 @@ -1699,7 +1933,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(); @@ -1725,7 +1959,7 @@ void SwTextFrame::CalcLineSpace() return; if( GetDrawObjs() || - GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace().IsAutoFirst()) + GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent().IsAutoFirst()) { Init(); return; @@ -1792,48 +2026,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(); @@ -1920,13 +2154,14 @@ void UpdateMergedParaForMove(sw::MergedPara & rMerged, for (auto const& it : deleted) { sal_Int32 const nStart(it.first - nSourceStart + nDestStart); - TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, false, - rDestNode, nStart, it.second - it.first); + TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, + rTextFrame.getRootFrame()->GetParagraphBreakMode(), rTextFrame.GetScriptInfo(), + false, rDestNode, nStart, it.second - it.first); //FIXME asserts valid for join - but if called from split, the new node isn't there yet and it will be added later... assert(nDeleted); // assert(nDeleted == it.second - it.first); if(nDeleted) { - // InvalidateRange/lcl_SetScriptInval was called sufficiently for 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>); @@ -1940,6 +2175,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; @@ -1953,6 +2189,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 @@ -1965,27 +2202,81 @@ 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); + SwFormatChangeHint const * pFormatChangedHint(nullptr); + sw::AttrSetChangeHint const* pAttrSetChangeHint(nullptr); + sw::UpdateAttrHint const* pUpdateAttrHint(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 (auto const pHt = dynamic_cast<sw::MoveText const*>(&rHint)) + else if (rHint.GetId() == SfxHintId::SwUpdateAttr) { - pMoveText = pHt; + pUpdateAttrHint = static_cast<const sw::UpdateAttrHint*>(&rHint); } - else if (auto const pHynt = dynamic_cast<sw::RedlineDelText const*>(&rHint)) + else if (rHint.GetId() == SfxHintId::SwInsertText) { - pRedlineDelText = pHynt; + pInsertText = static_cast<const sw::InsertText*>(&rHint); } - else if (auto const pHnt = dynamic_cast<sw::RedlineUnDelText const*>(&rHint)) + else if (rHint.GetId() == SfxHintId::SwDeleteText) { - pRedlineUnDelText = pHnt; + 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 (rHint.GetId() == SfxHintId::SwMoveText) + { + pMoveText = static_cast<sw::MoveText const*>(&rHint); + } + else if (rHint.GetId() == SfxHintId::SwRedlineDelText) + { + pRedlineDelText = static_cast<sw::RedlineDelText const*>(&rHint); + } + else if (rHint.GetId() == SfxHintId::SwRedlineUnDelText) + { + pRedlineUnDelText = static_cast<sw::RedlineUnDelText const*>(&rHint); + } + else if (rHint.GetId() == SfxHintId::SwFormatChange) + { + pFormatChangedHint = static_cast<const SwFormatChangeHint*>(&rHint); + } + else if (rHint.GetId() == SfxHintId::SwAttrSetChange) + { + pAttrSetChangeHint = static_cast<const sw::AttrSetChangeHint*>(&rHint); } else { @@ -2000,7 +2291,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) SwTextNode const& rNode(static_cast<SwTextNode const&>(rModify)); // modifications concerning frame attributes are processed by the base class - if( IsInRange( aFrameFormatSetRange, nWhich ) || RES_FMT_CHG == nWhich ) + if( IsInRange( aFrameFormatSetRange, nWhich ) || pFormatChangedHint ) { if (m_pMergedPara) { // ignore item set changes that don't apply @@ -2013,8 +2304,8 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) return; } } - SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew)); - if( nWhich == RES_FMT_CHG && getRootFrame()->GetCurrShell() ) + SwContentFrame::SwClientNotify(rModify, rHint); + if( pFormatChangedHint && getRootFrame()->GetCurrShell() ) { // collection has changed Prepare(); @@ -2058,7 +2349,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) sal_Int32 const nNLen = pRedlineDelText->nLen; nPos = MapModelToView(&rNode, nNPos); // update merged before doing anything else - nLen = UpdateMergedParaForDelete(*m_pMergedPara, false, rNode, nNPos, nNLen); + nLen = UpdateMergedParaForDelete(*m_pMergedPara, + getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(), + false, rNode, nNPos, nNLen); const sal_Int32 m = -nNLen; if (nLen && IsIdxInside(nPos, nLen)) { @@ -2080,7 +2373,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) sal_Int32 const nNPos = pRedlineUnDelText->nStart; sal_Int32 const nNLen = pRedlineUnDelText->nLen; nPos = MapModelToView(&rNode, nNPos); - nLen = UpdateMergedParaForInsert(*m_pMergedPara, false, rNode, nNPos, nNLen); + nLen = UpdateMergedParaForInsert(*m_pMergedPara, + getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(), + false, rNode, nNPos, nNLen); if (IsIdxInside(nPos, nLen)) { if (!nLen) @@ -2106,7 +2401,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, @@ -2121,23 +2416,30 @@ 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, + getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(), + true, rNode, pInsertText->nPos, pInsertText->nLen); } if( IsIdxInside( nPos, nLen ) ) { @@ -2150,107 +2452,351 @@ 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, + getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(), + 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); - } + nLen = TextFrameIndex(pDeleteText->nLen); + } + const sal_Int32 m = -pDeleteText->nLen; + if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen)) + { + if( !nLen ) + InvalidateSize(); else + InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m ); + } + lcl_SetWrong( *this, rNode, pDeleteText->nStart, m, true ); + if (nLen) + { + lcl_SetScriptInval( *this, nPos ); + bSetFieldsDirty = bRecalcFootnoteFlag = true; + lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); + } + } + else if (pDeleteChar) + { + nPos = MapModelToView(&rNode, pDeleteChar->m_nPos); + if (m_pMergedPara) + { + nLen = UpdateMergedParaForDelete(*m_pMergedPara, + getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(), + 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 if (pAttrSetChangeHint) + { + InvalidateLineNum(); + + const SwAttrSet& rNewSet = *pAttrSetChangeHint->m_pNew->GetChgSet(); + int nClear = 0; + sal_uInt16 nCount = rNewSet.Count(); + + if( const SwFormatFootnote* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FTN, false ) ) + { + nPos = MapModelToView(&rNode, pItem->GetTextFootnote()->GetStart()); + if (IsIdxInside(nPos, TextFrameIndex(1))) + Prepare( PrepareHint::FootnoteInvalidation, pAttrSetChangeHint->m_pNew ); + nClear = 0x01; + --nCount; + } + + if( const SwFormatField* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FIELD, false ) ) + { + nPos = MapModelToView(&rNode, pItem->GetTextField()->GetStart()); + if (IsIdxInside(nPos, TextFrameIndex(1))) { - 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>); + const SwFormatField* pOldItem = pAttrSetChangeHint->m_pOld ? + &(pAttrSetChangeHint->m_pOld->GetChgSet()->Get(RES_TXTATR_FIELD)) : nullptr; + if (SfxPoolItem::areSame( pItem, pOldItem )) + { + InvalidatePage(); + SetCompletePaint(); + } + else + InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1))); } + nClear |= 0x02; + --nCount; } - break; - case RES_DEL_TXT: + bool bLineSpace = SfxItemState::SET == rNewSet.GetItemState( + RES_PARATR_LINESPACING, false ), + bRegister = SfxItemState::SET == rNewSet.GetItemState( + RES_PARATR_REGISTER, false ); + if ( bLineSpace || bRegister ) { - 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 (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + { + Prepare( bRegister ? PrepareHint::Register : PrepareHint::AdjustSizeWithoutFormatting ); + CalcLineSpace(); + InvalidateSize(); + InvalidatePrt_(); + + // i#11859 + // (1) Also invalidate next frame on next page/column. + // (2) Skip empty sections and hidden paragraphs + // Thus, use method <InvalidateNextPrtArea()> + InvalidateNextPrtArea(); + + SetCompletePaint(); } - else + nClear |= 0x04; + if ( bLineSpace ) { - nLen = TextFrameIndex(nNLen); + --nCount; + if ((!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + && IsInSct() && !GetPrev()) + { + SwSectionFrame *pSect = FindSctFrame(); + if( pSect->ContainsAny() == this ) + pSect->InvalidatePrt(); + } } - const sal_Int32 m = -nNLen; - if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen)) + if ( bRegister ) + --nCount; + } + if ( SfxItemState::SET == rNewSet.GetItemState( RES_PARATR_SPLIT, + false )) + { + if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) { - if( !nLen ) - InvalidateSize(); - else - InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m ); + if (GetPrev()) + CheckKeep(); + Prepare(); + InvalidateSize(); } - lcl_SetWrong( *this, rNode, nNPos, m, true ); - if (nLen) + nClear |= 0x08; + --nCount; + } + + if( SfxItemState::SET == rNewSet.GetItemState( RES_BACKGROUND, false) + && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + && !IsFollow() && GetDrawObjs() ) + { + SwSortedObjs *pObjs = GetDrawObjs(); + for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i ) { - lcl_SetScriptInval( *this, nPos ); - bSetFieldsDirty = bRecalcFootnoteFlag = true; - lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if( !pFly->IsFlyInContentFrame() ) + { + const SvxBrushItem &rBack = + pFly->GetAttrSet()->GetBackground(); + // #GetTransChg# + // following condition determines, if the fly frame + // "inherites" the background color of text frame. + // This is the case, if fly frame background + // color is "no fill"/"auto fill" and if the fly frame + // has no background graphic. + // Thus, check complete fly frame background + // color and *not* only its transparency value + if ( (rBack.GetColor() == COL_TRANSPARENT) && + rBack.GetGraphicPos() == GPOS_NONE ) + { + pFly->SetCompletePaint(); + pFly->InvalidatePage(); + } + } + } } } - break; - case RES_UPDATE_ATTR: + + if ( SfxItemState::SET == + rNewSet.GetItemState( RES_TXTATR_CHARFMT, false ) ) + { + lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); + lcl_SetScriptInval( *this, TextFrameIndex(0) ); + } + else if ( SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_LANGUAGE, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CJK_LANGUAGE, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CTL_LANGUAGE, false ) ) + lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); + else if ( SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_FONT, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CJK_FONT, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CTL_FONT, false ) ) + lcl_SetScriptInval( *this, TextFrameIndex(0) ); + else if ( SfxItemState::SET == + rNewSet.GetItemState( RES_FRAMEDIR, false ) + && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)) { - const SwUpdateAttr* pNewUpdate = static_cast<const SwUpdateAttr*>(pNew); + SetDerivedR2L( false ); + CheckDirChange(); + // Force complete paint due to existing indents. + SetCompletePaint(); + } - sal_Int32 const nNPos = pNewUpdate->getStart(); - sal_Int32 const nNLen = pNewUpdate->getEnd() - nNPos; - nPos = MapModelToView(&rNode, nNPos); - nLen = MapModelToView(&rNode, nNPos + nNLen) - nPos; - if( IsIdxInside( nPos, nLen ) ) + if( nCount ) + { + if( getRootFrame()->GetCurrShell() ) { - // We need to reformat anyways, even if the invalidated - // range is empty. - // E.g.: empty line, set 14 pt! + Prepare(); + InvalidatePrt_(); + } - // FootnoteNumbers need to be formatted - if( !nLen ) - nLen = TextFrameIndex(1); + if (nClear || (m_pMergedPara && + (m_pMergedPara->pParaPropsNode != &rModify || + m_pMergedPara->pFirstNode != &rModify))) + { + assert(pAttrSetChangeHint->m_pOld); + SwAttrSetChg aOldSet( *pAttrSetChangeHint->m_pOld ); + SwAttrSetChg aNewSet( *pAttrSetChangeHint->m_pNew ); - InvalidateRange_( SwCharRange( nPos, nLen) ); - const sal_uInt16 nTmp = pNewUpdate->getWhichAttr(); + if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify) + { + for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i) + { + if (i != RES_BREAK && i != RES_PAGEDESC) + { + aOldSet.ClearItem(i); + aNewSet.ClearItem(i); + } + } + for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i) + { + aOldSet.ClearItem(i); + aNewSet.ClearItem(i); + } + } + if (m_pMergedPara && m_pMergedPara->pFirstNode != &rModify) + { + aOldSet.ClearItem(RES_BREAK); + aNewSet.ClearItem(RES_BREAK); + aOldSet.ClearItem(RES_PAGEDESC); + aNewSet.ClearItem(RES_PAGEDESC); + } - if( ! nTmp || RES_TXTATR_CHARFMT == nTmp || RES_TXTATR_INETFMT == nTmp || RES_TXTATR_AUTOFMT == nTmp || - RES_FMT_CHG == nTmp || RES_ATTRSET_CHG == nTmp ) + if( 0x01 & nClear ) + { + aOldSet.ClearItem( RES_TXTATR_FTN ); + aNewSet.ClearItem( RES_TXTATR_FTN ); + } + if( 0x02 & nClear ) + { + aOldSet.ClearItem( RES_TXTATR_FIELD ); + aNewSet.ClearItem( RES_TXTATR_FIELD ); + } + if ( 0x04 & nClear ) + { + if ( bLineSpace ) + { + aOldSet.ClearItem( RES_PARATR_LINESPACING ); + aNewSet.ClearItem( RES_PARATR_LINESPACING ); + } + if ( bRegister ) + { + aOldSet.ClearItem( RES_PARATR_REGISTER ); + aNewSet.ClearItem( RES_PARATR_REGISTER ); + } + } + if ( 0x08 & nClear ) + { + aOldSet.ClearItem( RES_PARATR_SPLIT ); + aNewSet.ClearItem( RES_PARATR_SPLIT ); + } + if (aOldSet.Count() || aNewSet.Count()) { - lcl_SetWrong( *this, rNode, nNPos, nNPos + nNLen, false ); - lcl_SetScriptInval( *this, nPos ); + SwContentFrame::SwClientNotify(rModify, sw::AttrSetChangeHint(&aOldSet, &aNewSet)); } } + else + SwContentFrame::SwClientNotify(rModify, rHint); + } - if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) && - hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) ) +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (isA11yRelevantAttribute(nWhich)) + { + SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if ( pViewSh ) { - SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; - if ( pViewSh ) - { - pViewSh->InvalidateAccessibleParaAttrs( *this ); - } + pViewSh->InvalidateAccessibleParaAttrs( *this ); } } - break; - case RES_OBJECTDYING: +#endif + } + else if (rHint.GetId() == SfxHintId::SwObjectDying) + ; // do nothing + else if (pUpdateAttrHint) + { + const SwUpdateAttr* pNewUpdate = pUpdateAttrHint->m_pNew; + + sal_Int32 const nNPos = pNewUpdate->getStart(); + sal_Int32 const nNLen = pNewUpdate->getEnd() - nNPos; + nPos = MapModelToView(&rNode, nNPos); + nLen = MapModelToView(&rNode, nNPos + nNLen) - nPos; + if( IsIdxInside( nPos, nLen ) ) + { + // We need to reformat anyways, even if the invalidated + // range is empty. + // E.g.: empty line, set 14 pt! + + // FootnoteNumbers need to be formatted + if( !nLen ) + nLen = TextFrameIndex(1); + + InvalidateRange_( SwCharRange( nPos, nLen) ); + const sal_uInt16 nTmp = pNewUpdate->getWhichAttr(); + + if( ! nTmp || RES_TXTATR_CHARFMT == nTmp || RES_TXTATR_INETFMT == nTmp || RES_TXTATR_AUTOFMT == nTmp || + RES_UPDATEATTR_FMT_CHG == nTmp || RES_UPDATEATTR_ATTRSET_CHG == nTmp ) + { + lcl_SetWrong( *this, rNode, nNPos, nNPos + nNLen, false ); + lcl_SetScriptInval( *this, nPos ); + } + } + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) && + hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) ) + { + SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if ( pViewSh ) + { + pViewSh->InvalidateAccessibleParaAttrs( *this ); + } + } +#endif + } + else switch (nWhich) + { + case RES_LINENUMBER: + { + assert(false); // should have been forwarded to SwContentFrame + InvalidateLineNum(); + } break; case RES_PARATR_LINESPACING: @@ -2282,7 +2828,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? @@ -2305,7 +2851,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()); } @@ -2317,261 +2863,6 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) break; } - case RES_ATTRSET_CHG: - { - 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 )) - { - nPos = MapModelToView(&rNode, - static_cast<const SwFormatFootnote*>(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 )) - { - nPos = MapModelToView(&rNode, - static_cast<const SwFormatField*>(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 ) - { - InvalidatePage(); - SetCompletePaint(); - } - else - InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1))); - } - nClear |= 0x02; - --nCount; - } - bool bLineSpace = SfxItemState::SET == rNewSet.GetItemState( - RES_PARATR_LINESPACING, false ), - bRegister = SfxItemState::SET == rNewSet.GetItemState( - RES_PARATR_REGISTER, false ); - if ( bLineSpace || bRegister ) - { - if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) - { - Prepare( bRegister ? PrepareHint::Register : PrepareHint::AdjustSizeWithoutFormatting ); - CalcLineSpace(); - InvalidateSize(); - InvalidatePrt_(); - - // i#11859 - // (1) Also invalidate next frame on next page/column. - // (2) Skip empty sections and hidden paragraphs - // Thus, use method <InvalidateNextPrtArea()> - InvalidateNextPrtArea(); - - SetCompletePaint(); - } - nClear |= 0x04; - if ( bLineSpace ) - { - --nCount; - if ((!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) - && IsInSct() && !GetPrev()) - { - SwSectionFrame *pSect = FindSctFrame(); - if( pSect->ContainsAny() == this ) - pSect->InvalidatePrt(); - } - } - if ( bRegister ) - --nCount; - } - if ( SfxItemState::SET == rNewSet.GetItemState( RES_PARATR_SPLIT, - false )) - { - if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) - { - if (GetPrev()) - CheckKeep(); - Prepare(); - InvalidateSize(); - } - nClear |= 0x08; - --nCount; - } - - if( SfxItemState::SET == rNewSet.GetItemState( RES_BACKGROUND, false) - && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) - && !IsFollow() && GetDrawObjs() ) - { - SwSortedObjs *pObjs = GetDrawObjs(); - for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i ) - { - SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; - if ( auto pFly = dynamic_cast<SwFlyFrame *>( pAnchoredObj ) ) - { - if( !pFly->IsFlyInContentFrame() ) - { - const SvxBrushItem &rBack = - pFly->GetAttrSet()->GetBackground(); - // #GetTransChg# - // following condition determines, if the fly frame - // "inherites" the background color of text frame. - // This is the case, if fly frame background - // color is "no fill"/"auto fill" and if the fly frame - // has no background graphic. - // Thus, check complete fly frame background - // color and *not* only its transparency value - if ( (rBack.GetColor() == COL_TRANSPARENT) && - rBack.GetGraphicPos() == GPOS_NONE ) - { - pFly->SetCompletePaint(); - pFly->InvalidatePage(); - } - } - } - } - } - - if ( SfxItemState::SET == - rNewSet.GetItemState( RES_TXTATR_CHARFMT, false ) ) - { - lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); - lcl_SetScriptInval( *this, TextFrameIndex(0) ); - } - else if ( SfxItemState::SET == - rNewSet.GetItemState( RES_CHRATR_LANGUAGE, false ) || - SfxItemState::SET == - rNewSet.GetItemState( RES_CHRATR_CJK_LANGUAGE, false ) || - SfxItemState::SET == - rNewSet.GetItemState( RES_CHRATR_CTL_LANGUAGE, false ) ) - lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); - else if ( SfxItemState::SET == - rNewSet.GetItemState( RES_CHRATR_FONT, false ) || - SfxItemState::SET == - rNewSet.GetItemState( RES_CHRATR_CJK_FONT, false ) || - SfxItemState::SET == - rNewSet.GetItemState( RES_CHRATR_CTL_FONT, false ) ) - lcl_SetScriptInval( *this, TextFrameIndex(0) ); - else if ( SfxItemState::SET == - rNewSet.GetItemState( RES_FRAMEDIR, false ) - && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)) - { - SetDerivedR2L( false ); - CheckDirChange(); - // Force complete paint due to existing indents. - SetCompletePaint(); - } - - if( nCount ) - { - if( getRootFrame()->GetCurrShell() ) - { - Prepare(); - InvalidatePrt_(); - } - - if (nClear || (m_pMergedPara && - (m_pMergedPara->pParaPropsNode != &rModify || - m_pMergedPara->pFirstNode != &rModify))) - { - assert(pOld); - SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); - SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); - - if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify) - { - for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i) - { - if (i != RES_BREAK && i != RES_PAGEDESC) - { - aOldSet.ClearItem(i); - aNewSet.ClearItem(i); - } - } - for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i) - { - aOldSet.ClearItem(i); - aNewSet.ClearItem(i); - } - } - if (m_pMergedPara && m_pMergedPara->pFirstNode != &rModify) - { - aOldSet.ClearItem(RES_BREAK); - aNewSet.ClearItem(RES_BREAK); - aOldSet.ClearItem(RES_PAGEDESC); - aNewSet.ClearItem(RES_PAGEDESC); - } - - if( 0x01 & nClear ) - { - aOldSet.ClearItem( RES_TXTATR_FTN ); - aNewSet.ClearItem( RES_TXTATR_FTN ); - } - if( 0x02 & nClear ) - { - aOldSet.ClearItem( RES_TXTATR_FIELD ); - aNewSet.ClearItem( RES_TXTATR_FIELD ); - } - if ( 0x04 & nClear ) - { - if ( bLineSpace ) - { - aOldSet.ClearItem( RES_PARATR_LINESPACING ); - aNewSet.ClearItem( RES_PARATR_LINESPACING ); - } - if ( bRegister ) - { - aOldSet.ClearItem( RES_PARATR_REGISTER ); - aNewSet.ClearItem( RES_PARATR_REGISTER ); - } - } - if ( 0x08 & nClear ) - { - aOldSet.ClearItem( RES_PARATR_SPLIT ); - aNewSet.ClearItem( RES_PARATR_SPLIT ); - } - if (aOldSet.Count() || aNewSet.Count()) - { - SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(&aOldSet, &aNewSet)); - } - } - else - SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew)); - } - - if (isA11yRelevantAttribute(nWhich)) - { - SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; - if ( pViewSh ) - { - pViewSh->InvalidateAccessibleParaAttrs( *this ); - } - } - } - 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(); @@ -2598,38 +2889,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"); @@ -2765,14 +3030,24 @@ 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" ); - if ( bNotify ) - InvalidateSize(); - else - InvalidateSize_(); + // check while ignoring frame width (testParagraphMarkInCell) + // because it's called from InvalidateAllContent() + if (!IsHiddenNowImpl()) + { + SetInvalidVert( true ); // Test + if ( bNotify ) + InvalidateSize(); + else + InvalidateSize_(); + } return bParaPossiblyInvalid; } @@ -2943,7 +3218,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; @@ -2958,7 +3233,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(); + } } } } @@ -3200,7 +3481,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); } /** @@ -3215,7 +3496,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" ); @@ -3298,7 +3579,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 @@ -3312,7 +3593,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" ); @@ -3320,11 +3601,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()); + SwTwips nRet = getFramePrintArea().SSize().Height(); if( IsUndersized() ) { if( IsEmpty() || GetText().isEmpty() ) - nRet = static_cast<sal_uInt16>(EmptyHeight()); + nRet = EmptyHeight(); else ++nRet; } @@ -3333,7 +3614,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 @@ -3442,7 +3723,26 @@ SwTwips SwTextFrame::CalcFitToContent() SetPara( pOldPara ); - return nMax; + // tdf#164932 handle numbering list offset + const SwTextNode* pTextNode( GetTextNodeForParaProps() ); + SwTwips nNumOffset = 0; + if ( pTextNode->IsNumbered(getRootFrame()) && + pTextNode->IsCountedInList() && pTextNode->GetNumRule() ) + { + sal_uInt16 nListLevel = std::clamp(pTextNode->GetActualListLevel(), 0, MAXLEVEL - 1); + const SwNumFormat& rNumFormat = pTextNode->GetNumRule()->Get(nListLevel); + if ( rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + const SwAttrSet& rSet = pTextNode->GetSwAttrSet(); + ::sw::ListLevelIndents const indents(pTextNode->AreListLevelIndentsApplicable()); + SvxTextLeftMarginItem leftMargin(rSet.GetTextLeftMargin()); + if (indents & ::sw::ListLevelIndents::LeftMargin) + leftMargin.SetTextLeft(SvxIndentValue::twips(rNumFormat.GetAbsLSpace())); + nNumOffset = leftMargin.ResolveTextLeft(/*metrics*/ {}); + } + } + + return nMax + nNumOffset; } /** @@ -3466,16 +3766,8 @@ void SwTextFrame::CalcAdditionalFirstLineOffset() pTextNode->IsCountedInList() && pTextNode->GetNumRule())) return; - int nListLevel = pTextNode->GetActualListLevel(); - - if (nListLevel < 0) - nListLevel = 0; - - if (nListLevel >= MAXLEVEL) - nListLevel = MAXLEVEL - 1; - - const SwNumFormat& rNumFormat = - pTextNode->GetNumRule()->Get( static_cast<sal_uInt16>(nListLevel) ); + sal_uInt16 nListLevel = std::clamp(pTextNode->GetActualListLevel(), 0, MAXLEVEL - 1); + const SwNumFormat& rNumFormat = pTextNode->GetNumRule()->Get(nListLevel); if ( rNumFormat.GetPositionAndSpaceMode() != SvxNumberFormat::LABEL_ALIGNMENT ) return; @@ -3717,24 +4009,38 @@ tools::Long SwTextFrame::GetLineSpace( const bool _bNoPropLineSpace ) const return nRet; } -sal_uInt16 SwTextFrame::FirstLineHeight() const +SwTwips SwTextFrame::FirstLineHeight() const { if ( !HasPara() ) { if( IsEmpty() && isFrameAreaDefinitionValid() ) - return IsVertical() ? static_cast<sal_uInt16>(getFramePrintArea().Width()) : static_cast<sal_uInt16>(getFramePrintArea().Height()); - return USHRT_MAX; + return IsVertical() ? getFramePrintArea().Width() : getFramePrintArea().Height(); + return std::numeric_limits<SwTwips>::max(); } const SwParaPortion *pPara = GetPara(); if ( !pPara ) - return USHRT_MAX; - - return pPara->Height(); + return std::numeric_limits<SwTwips>::max(); + + // tdf#146500 Lines with only fly overlap cannot be "moved", so the idea + // here is to continue until there's some text. + // FIXME ideally we want to count a fly to the line in which it is anchored + // - it may even be anchored in some other paragraph! SwFlyPortion doesn't + // have a pointer sadly so no way to find out. + SwTwips 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 { @@ -3756,7 +4062,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() ) { @@ -3816,9 +4122,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() ) @@ -3882,7 +4188,7 @@ void SwTextFrame::VisitPortions( SwPortionHandler& rPH ) const pPor = pPor->GetNextPortion(); } - rPH.LineBreak(pLine->Width()); + rPH.LineBreak(); pLine = pLine->GetNext(); } } @@ -3896,6 +4202,12 @@ const SwScriptInfo* SwTextFrame::GetScriptInfo() const return pPara ? &pPara->GetScriptInfo() : nullptr; } +SwScriptInfo* SwTextFrame::GetScriptInfo() +{ + SwParaPortion* pPara = GetPara(); + return pPara ? &pPara->GetScriptInfo() : nullptr; +} + /** * Helper function for SwTextFrame::CalcBasePosForFly() */ @@ -4024,4 +4336,15 @@ void SwTextFrame::repaintTextFrames( const SwTextNode& rNode ) } } +void SwTextFrame::UpdateOutlineContentVisibilityButton(SwWrtShell* pWrtSh) const +{ + if (pWrtSh && pWrtSh->GetViewOptions()->IsShowOutlineContentVisibilityButton() && + GetTextNodeFirst()->IsOutline()) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + rMngr.SetOutlineContentVisibilityButton(this); + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtftn.cxx b/sw/source/core/text/txtftn.cxx index 5b4b9f7492e0..cea62efffb6a 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> @@ -31,6 +32,7 @@ #include <txtftn.hxx> #include <flyfrm.hxx> #include <fmtftn.hxx> +#include <fmtsrnd.hxx> #include <ftninfo.hxx> #include <charfmt.hxx> #include <rowfrm.hxx> @@ -53,6 +55,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> @@ -232,15 +241,21 @@ static SwTwips lcl_GetFootnoteLower( const SwTextFrame* pFrame, SwTwips nLower ) SwTwips nFlyLower = aRectFnSet.IsVert() ? LONG_MAX : 0; while ( pStartFrame != pFrame ) { - OSL_ENSURE( pStartFrame, "Frame chain is broken" ); + assert(pStartFrame && "Frame chain is broken"); if ( pStartFrame->GetDrawObjs() ) { const SwSortedObjs &rObjs = *pStartFrame->GetDrawObjs(); for (SwAnchoredObject* pAnchoredObj : rObjs) { + if (pAnchoredObj->GetFrameFormat()->GetSurround().GetSurround() + == text::WrapTextMode_THROUGH) + { + continue; // tdf#161718 no effect on text flow, skip + } + SwRect aRect( pAnchoredObj->GetObjRect() ); - auto pFlyFrame = dynamic_cast<SwFlyFrame*>( pAnchoredObj ); + auto pFlyFrame = pAnchoredObj->DynCastFlyFrame(); if ( !pFlyFrame || pFlyFrame->isFrameAreaDefinitionValid() ) { @@ -288,7 +303,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 ); } @@ -336,8 +351,9 @@ SwTwips SwTextFrame::GetFootnoteFrameHeight_() const const SwLayoutFrame* pTmp = GetUpper(); while( !bInvalidPos && pTmp ) { + const SwFrame* pLower = pTmp->Lower(); bInvalidPos = !pTmp->isFrameAreaPositionValid() || - !pTmp->Lower()->isFrameAreaPositionValid(); + !pLower || !pLower->isFrameAreaPositionValid(); if( pTmp == pCont ) break; pTmp = pTmp->GetUpper(); @@ -522,7 +538,7 @@ void SwTextFrame::RemoveFootnote(TextFrameIndex const nStart, TextFrameIndex con else { if (!bEndDoc || ( bEndn && pEndBoss->IsInSct() && - !SwLayouter::Collecting( &GetDoc(), + !SwLayouter::Collecting( GetDoc(), pEndBoss->FindSctFrame(), nullptr ) )) { if( bEndn ) @@ -590,8 +606,6 @@ void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDea mbFootnote = true; mbInFootnoteConnect = true; // Just reset! // See if pFootnote is an endnote on a separate endnote page. - const IDocumentSettingAccess& rSettings = GetDoc().getIDocumentSettingAccess(); - const bool bContinuousEndnotes = rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES); const bool bEnd = pFootnote->GetFootnote().IsEndNote(); // We want to store this value, because it is needed as a fallback @@ -622,15 +636,11 @@ void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDea if( bDocEnd ) { - if ((pSect || bContinuousEndnotes) && pSrcFrame) + if (pSect && pSrcFrame) { SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ); - if (pFootnoteFrame && (pFootnoteFrame->IsInSct() || bContinuousEndnotes)) + if (pFootnoteFrame && pFootnoteFrame->IsInSct()) { - // We either have a foot/endnote that goes to the end of the section or are in Word - // compatibility mode where endnotes go to the end of the document. Handle both - // cases by removing the footnote here, then later appending them to the correct - // last page of the document or section. pBoss->RemoveFootnote( pSrcFrame, pFootnote ); pSrcFrame = nullptr; } @@ -641,22 +651,22 @@ void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDea SwFootnoteFrame *pFootnoteFrame = pSrcFrame ? SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ) : nullptr; if( pFootnoteFrame && !pFootnoteFrame->GetUpper() ) pFootnoteFrame = nullptr; - SwDoc *const pDoc = &GetDoc(); - if( SwLayouter::Collecting( pDoc, pSect, pFootnoteFrame ) ) + SwDoc& rDoc = GetDoc(); + if( SwLayouter::Collecting( rDoc, pSect, pFootnoteFrame ) ) { if( !pSrcFrame ) { - SwFootnoteFrame *pNew = new SwFootnoteFrame(pDoc->GetDfltFrameFormat(),this,this,pFootnote); + SwFootnoteFrame *pNew = new SwFootnoteFrame(rDoc.GetDfltFrameFormat(),this,this,pFootnote); SwNodeIndex aIdx( *pFootnote->GetStartNode(), 1 ); - ::InsertCnt_( pNew, pDoc, aIdx.GetIndex() ); - pDoc->getIDocumentLayoutAccess().GetLayouter()->CollectEndnote( pNew ); + ::InsertCnt_( pNew, rDoc, aIdx.GetIndex() ); + rDoc.getIDocumentLayoutAccess().GetLayouter()->CollectEndnote( pNew ); } else if( pSrcFrame != this ) SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this ); mbInFootnoteConnect = false; return; } - else if( pSrcFrame ) + else if (pSrcFrame && pFootnoteFrame) { SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame(); if( !pFootnoteBoss->IsInSct() || @@ -788,10 +798,10 @@ SwFootnotePortion *SwTextFormatter::NewFootnotePortion( SwTextFormatInfo &rInf, OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), "NewFootnotePortion with unswapped frame" ); - SwTextFootnote *pFootnote = static_cast<SwTextFootnote*>(pHint); + if (!m_pFrame->IsFootnoteAllowed()) + return new SwFootnotePortion(u""_ustr, nullptr); - if( !m_pFrame->IsFootnoteAllowed() ) - return new SwFootnotePortion("", pFootnote); + SwTextFootnote *pFootnote = static_cast<SwTextFootnote*>(pHint); const SwFormatFootnote& rFootnote = pFootnote->GetFootnote(); SwDoc *const pDoc = &m_pFrame->GetDoc(); @@ -801,11 +811,11 @@ SwFootnotePortion *SwTextFormatter::NewFootnotePortion( SwTextFormatInfo &rInf, SwSwapIfSwapped swap(m_pFrame); - sal_uInt16 nReal; + SwTwips nReal; { - sal_uInt16 nOldReal = m_pCurr->GetRealHeight(); - sal_uInt16 nOldAscent = m_pCurr->GetAscent(); - sal_uInt16 nOldHeight = m_pCurr->Height(); + SwTwips nOldReal = m_pCurr->GetRealHeight(); + SwTwips nOldAscent = m_pCurr->GetAscent(); + SwTwips nOldHeight = m_pCurr->Height(); CalcRealHeight(); nReal = m_pCurr->GetRealHeight(); if( nReal < nOldReal ) @@ -964,16 +974,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(u"CharFontCharSet"_ustr); sal_Int16 eCharSet; if ((aAny >>= eCharSet) && eCharSet == awt::CharSet::SYMBOL) { OUString aFontName; - aAny = xAnchorProps->getPropertyValue("CharFontName"); + aAny = xAnchor->getPropertyValue(u"CharFontName"_ustr); if (aAny >>= aFontName) { pNumFnt->SetName(aFontName, SwFontScript::Latin); @@ -990,6 +999,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() ) + SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet); + else + SwModule::get()->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; @@ -997,7 +1036,7 @@ SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( SwTextFormatInfo const static OUString lcl_GetPageNumber( const SwPageFrame* pPage ) { - OSL_ENSURE( pPage, "GetPageNumber: Homeless TextFrame" ); + assert(pPage && "GetPageNumber: Homeless TextFrame"); const sal_uInt16 nVirtNum = pPage->GetVirtPageNum(); const SvxNumberType& rNum = pPage->GetPageDesc()->GetNumType(); return rNum.GetNumStr( nVirtNum ); @@ -1083,7 +1122,7 @@ TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) // position we want to insert the Quovadis text // Let's see if it is that bad indeed: SwLinePortion *pPor = m_pCurr->GetFirstPortion(); - sal_uInt16 nLastLeft = 0; + SwTwips nLastLeft = 0; while( pPor ) { if ( pPor->IsFlyPortion() ) @@ -1095,7 +1134,7 @@ TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) // The old game all over again: we want the Line to wrap around // at a certain point, so we adjust the width. // nLastLeft is now basically the right margin - const sal_uInt16 nOldRealWidth = rInf.RealWidth(); + const SwTwips nOldRealWidth = rInf.RealWidth(); rInf.RealWidth( nOldRealWidth - nLastLeft ); OUString aErgo = lcl_GetPageNumber( pErgoFrame->FindPageFrame() ); @@ -1103,7 +1142,7 @@ TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) pQuo->SetAscent( rInf.GetAscent() ); pQuo->Height( rInf.GetTextHeight() ); pQuo->Format( rInf ); - sal_uInt16 nQuoWidth = pQuo->Width(); + SwTwips nQuoWidth = pQuo->Width(); SwLinePortion* pCurrPor = pQuo; while ( rInf.GetRest() ) @@ -1181,12 +1220,12 @@ TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) if( nDiff < 0 ) { nLastLeft = pQuo->GetAscent(); - nQuoWidth = static_cast<sal_uInt16>(-nDiff + nLastLeft); + nQuoWidth = -nDiff + nLastLeft; } else { nQuoWidth = 0; - nLastLeft = sal_uInt16(( pQuo->GetAscent() + nDiff ) / 2); + nLastLeft = (pQuo->GetAscent() + nDiff) / 2; } break; } @@ -1237,7 +1276,7 @@ TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) */ void SwTextFormatter::MakeDummyLine() { - sal_uInt16 nRstHeight = GetFrameRstHeight(); + SwTwips nRstHeight = GetFrameRstHeight(); if( m_pCurr && nRstHeight > m_pCurr->Height() ) { SwLineLayout *pLay = new SwLineLayout; @@ -1313,17 +1352,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; @@ -1341,8 +1377,7 @@ SwFootnoteSave::~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE } } -SwFootnotePortion::SwFootnotePortion( const OUString &rExpand, - SwTextFootnote *pFootn, sal_uInt16 nReal ) +SwFootnotePortion::SwFootnotePortion(const OUString& rExpand, SwTextFootnote* pFootn, SwTwips nReal) : SwFieldPortion( rExpand, nullptr ) , m_pFootnote(pFootn) , m_nOrigHeight( nReal ) @@ -1374,7 +1409,7 @@ bool SwFootnotePortion::Format( SwTextFormatInfo &rInf ) SetAscent( rInf.GetAscent() ); Height( rInf.GetTextHeight() ); rInf.SetFootnoteDone( !bFull ); - if( !bFull ) + if (!bFull && m_pFootnote) rInf.SetParaFootnote(); return bFull; } @@ -1388,7 +1423,7 @@ void SwFootnotePortion::Paint( const SwTextPaintInfo &rInf ) const SwExpandPortion::Paint( rInf ); } -SwPosSize SwFootnotePortion::GetTextSize( const SwTextSizeInfo &rInfo ) const +SwPositiveSize SwFootnotePortion::GetTextSize( const SwTextSizeInfo &rInfo ) const { // #i98418# // SwFootnoteSave aFootnoteSave( rInfo, pFootnote ); @@ -1408,8 +1443,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 ); @@ -1430,7 +1465,7 @@ bool SwQuoVadisPortion::Format( SwTextFormatInfo &rInf ) SetLen(TextFrameIndex(0)); if( bFull ) // Third try; we're done: we crush - Width( sal_uInt16(rInf.Width() - rInf.X()) ); + Width(rInf.Width() - rInf.X()); // No multiline Fields for QuoVadis and ErgoSum if( rInf.GetRest() ) @@ -1486,7 +1521,7 @@ SwErgoSumPortion::SwErgoSumPortion(const OUString &rExp, std::u16string_view rSt SetWhichPor( PortionType::ErgoSum ); } -TextFrameIndex SwErgoSumPortion::GetModelPositionForViewPoint(const sal_uInt16) const +TextFrameIndex SwErgoSumPortion::GetModelPositionForViewPoint(const SwTwips) const { return TextFrameIndex(0); } diff --git a/sw/source/core/text/txthyph.cxx b/sw/source/core/text/txthyph.cxx index 7f9f6f6cd611..510654a74f82 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; @@ -289,7 +288,7 @@ bool SwTextPortion::CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGu // length of pHyphPor is adjusted pHyphPor->SetLen( TextFrameIndex(aAltText.getLength() + 1) ); - static_cast<SwPosSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf ); + static_cast<SwPositiveSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf ); pHyphPor->SetLen( TextFrameIndex(aAltSpell.nChangedLength + nTmpLen) ); } else @@ -299,13 +298,13 @@ bool SwTextPortion::CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGu pHyphPor->SetLen(TextFrameIndex(1)); static const void* nLastFontCacheId = nullptr; - static sal_uInt16 aMiniCacheH = 0, aMiniCacheW = 0; + static SwTwips aMiniCacheH = 0, aMiniCacheW = 0; const void* nTmpFontCacheId; sal_uInt16 nFntIdx; rInf.GetFont()->GetFontCacheId( nTmpFontCacheId, nFntIdx, rInf.GetFont()->GetActual() ); if( !nLastFontCacheId || nLastFontCacheId != nTmpFontCacheId ) { nLastFontCacheId = nTmpFontCacheId; - static_cast<SwPosSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf ); + static_cast<SwPositiveSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf ); aMiniCacheH = pHyphPor->Height(); aMiniCacheW = pHyphPor->Width(); } else { @@ -325,8 +324,15 @@ bool SwTextPortion::CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGu ( nPorEnd == rInf.GetIdx() && rInf.GetLineStart() != rInf.GetIdx() ) ) { aInf.SetLen( nPorEnd - rInf.GetIdx() ); + if (auto stClampedContext = aInf.GetLayoutContext(); stClampedContext.has_value()) + { + stClampedContext->m_nEnd = nPorEnd.get(); + aInf.SetLayoutContext(stClampedContext); + } + pHyphPor->SetAscent( GetAscent() ); SetLen( aInf.GetLen() ); + SetLayoutContext(aInf.GetLayoutContext()); CalcTextSize( aInf ); Insert( pHyphPor.release() ); @@ -355,6 +361,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(); @@ -395,7 +411,7 @@ SwSoftHyphPortion::SwSoftHyphPortion() : SetWhichPor( PortionType::SoftHyphen ); } -sal_uInt16 SwSoftHyphPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +SwTwips SwSoftHyphPortion::GetViewWidth(const SwTextSizeInfo& rInf) const { // Although we're in the const, nViewWidth should be calculated at // the last possible moment @@ -422,10 +438,27 @@ sal_uInt16 SwSoftHyphPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const */ void SwSoftHyphPortion::Paint( const SwTextPaintInfo &rInf ) const { - if( Width() ) + if ( Width() ) { rInf.DrawViewOpt( *this, PortionType::SoftHyphen ); SwExpandPortion::Paint( rInf ); + + if (rInf.GetOpt().IsViewMetaChars() && !rInf.GetOpt().IsPrinting() + && !rInf.GetOpt().IsPDFExport()) + { + OUString aMarker = u"-"_ustr; + SwTextPaintInfo aInf(rInf, &aMarker); + SwTextPortion aMarkerPor; + SwPositiveSize aMarkerSize(rInf.GetTextSize(aMarker)); + aMarkerPor.Width(aMarkerSize.Width()); + aMarkerPor.Height(aMarkerSize.Height()); + aMarkerPor.SetAscent(GetAscent()); + + Color colorBackup = aInf.GetFont()->GetColor(); + aInf.GetFont()->SetColor(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor()); + aInf.DrawText(aMarkerPor, TextFrameIndex(aMarker.getLength()), true); + aInf.GetFont()->SetColor(colorBackup); + } } } 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/txtpaint.cxx b/sw/source/core/text/txtpaint.cxx index ccd9647bd99c..5b05b413fab9 100644 --- a/sw/source/core/text/txtpaint.cxx +++ b/sw/source/core/text/txtpaint.cxx @@ -41,7 +41,6 @@ SwSaveClip::~SwSaveClip() } void SwSaveClip::ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame, - bool bEnlargeRect, sal_Int32 nEnlargeTop, sal_Int32 nEnlargeBottom ) { @@ -74,12 +73,6 @@ void SwSaveClip::ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame, { tools::Rectangle aRect( rRect.SVRect() ); - // Having underscores in our line, we enlarged the repaint area - // (see frmform.cxx) because for some fonts it could be too small. - // Consequently, we have to enlarge the clipping rectangle as well. - if ( bEnlargeRect && ! bVertical ) - aRect.AdjustBottom(40 ); - // enlarge clip for paragraph margins at small fixed line height if ( nEnlargeTop > 0 ) aRect.AdjustTop( -nEnlargeTop ); diff --git a/sw/source/core/text/txtpaint.hxx b/sw/source/core/text/txtpaint.hxx index 1f9700af4c96..3554a2421415 100644 --- a/sw/source/core/text/txtpaint.hxx +++ b/sw/source/core/text/txtpaint.hxx @@ -32,7 +32,6 @@ class SwSaveClip final VclPtr<OutputDevice> m_pOut; void ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame, - bool bEnlargeRect, sal_Int32 nEnlargeTop, sal_Int32 nEnlargeBottom ); public: @@ -44,12 +43,14 @@ public: } ~SwSaveClip(); - void ChgClip( const SwRect &rRect, const SwTextFrame* pFrame = nullptr, - bool bEnlargeRect = false, - sal_Int32 nEnlargeTop = 0, - sal_Int32 nEnlargeBottom = 0) - { if( m_pOut ) ChgClip_( rRect, pFrame, - bEnlargeRect, nEnlargeTop, nEnlargeBottom ); } + void ChgClip(const SwRect& rRect, const SwTextFrame* pFrame = nullptr, + sal_Int32 nEnlargeTop = 0, sal_Int32 nEnlargeBottom = 0) + { + if (m_pOut) + { + ChgClip_(rRect, pFrame, nEnlargeTop, nEnlargeBottom); + } + } bool IsOn() const { return m_bOn; } bool IsChg() const { return m_bChg; } }; diff --git a/sw/source/core/text/txttab.cxx b/sw/source/core/text/txttab.cxx index 8e8203c72270..14d8b9072693 100644 --- a/sw/source/core/text/txttab.cxx +++ b/sw/source/core/text/txttab.cxx @@ -40,27 +40,40 @@ * 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 bTabOverMargin = rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN); + 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())) @@ -70,7 +83,7 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto sal_Unicode cDec = 0; SvxTabAdjust eAdj; - sal_uInt16 nNewTabPos; + SwTwips nNewTabPos; bool bAutoTabStop = true; { const bool bRTL = m_pFrame->IsRightToLeft(); @@ -78,8 +91,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 +132,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 +144,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 || bTabOverMargin) + 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,17 +162,37 @@ 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; + // TODO: unclear if old Word has an upper limit for center/right + // tabs, UI allows setting 55.87cm max which is still one line + if (!bTabOverMargin || o3tl::toTwips(558, o3tl::Length::mm) < nNextPos) + { + nNextPos = rInf.Width(); + } + } bAutoTabStop = false; } else { - sal_uInt16 nDefTabDist = m_aLineInf.GetDefTabStop(); - if( USHRT_MAX == nDefTabDist ) + SwTwips nDefTabDist = m_aLineInf.GetDefTabStop(); + if (std::numeric_limits<SwTwips>::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 = rTab[0].GetTabPos(); else nDefTabDist = SVX_TAB_DEFDIST; m_aLineInf.SetDefTabStop( nDefTabDist ); @@ -247,9 +285,10 @@ 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); + nNewTabPos = nNextPos; } SwTabPortion *pTabPor = nullptr; @@ -287,6 +326,8 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto } } } + if (pTabPor) + rInf.UpdateTabSeen(pTabPor->GetWhichPor()); return pTabPor; } @@ -294,12 +335,12 @@ 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 ) +SwTabPortion::SwTabPortion( const SwTwips nTabPosition, const sal_Unicode cFillChar, const bool bAutoTab ) + : m_nTabPos(nTabPosition), m_cFill(cFillChar), m_bAutoTabStop( bAutoTab ) { mnLineLength = TextFrameIndex(1); OSL_ENSURE(!IsFilled() || ' ' != m_cFill, "SwTabPortion::CTOR: blanks ?!"); - SetWhichPor( PortionType::Table ); + SetWhichPor( PortionType::Tab ); } bool SwTabPortion::Format( SwTextFormatInfo &rInf ) @@ -309,7 +350,7 @@ bool SwTabPortion::Format( SwTextFormatInfo &rInf ) return PostFormat( rInf ); if( pLastTab ) pLastTab->PostFormat( rInf ); - return PreFormat( rInf ); + return PreFormat(rInf, pLastTab); } void SwTabPortion::FormatEOL( SwTextFormatInfo &rInf ) @@ -318,35 +359,37 @@ void SwTabPortion::FormatEOL( SwTextFormatInfo &rInf ) PostFormat( rInf ); } -bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) +bool SwTabPortion::PreFormat(SwTextFormatInfo &rInf, SwTabPortion const*const pLastTab) { OSL_ENSURE( rInf.X() <= GetTabPos(), "SwTabPortion::PreFormat: rush hour" ); // Here we settle down ... - SetFix( static_cast<sal_uInt16>(rInf.X()) ); + SetFix(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 tools::Long nTextFrameWidth = rInf.GetTextFrame()->getFrameArea().Width(); // The minimal width of a tab is one blank at least. // #i37686# In compatibility mode, the minimum width // should be 1, even for non-left tab stops. - sal_uInt16 nMinimumTabWidth = 1; + SwTwips nMinimumTabWidth = 1; if ( !bTabCompat ) { // #i89179# // tab portion representing the list tab of a list label gets the // same font as the corresponding number portion - std::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 ); @@ -358,7 +401,8 @@ bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) // 1. Minimal width does not fit to line anymore. // 2. An underflow event was called for the tab portion. bool bFull = ( bTabCompat && rInf.IsUnderflow() ) || - ( rInf.Width() <= rInf.X() + PrtWidth() && rInf.X() <= rInf.Width() ) ; + (rInf.Width() <= rInf.X() + PrtWidth() && rInf.X() <= rInf.Width() + && (!bTabOverMargin || !pLastTab)); // #95477# Rotated tab stops get the width of one blank const Degree10 nDir = rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ); @@ -381,13 +425,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 || rInf.Width() <= rInf.X())) { - 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(GetTabPos() - rInf.X()); bFull = rInf.Width() <= rInf.X() + PrtWidth(); // In tabulator compatibility mode, we reset the bFull flag @@ -396,7 +450,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 +471,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(rInf.Width() - rInf.X()); SetFixWidth( PrtWidth() ); } else @@ -443,16 +497,22 @@ 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 SwTwips nRight + = bTabOverMargin + ? GetTabPos() + : bTabOverSpacing + ? std::min(GetTabPos(), rInf.GetTextFrame()->getFrameArea().Right()) + : std::min(GetTabPos(), rInf.Width()); const SwLinePortion *pPor = GetNextPortion(); - sal_uInt16 nPorWidth = 0; + SwTwips nPorWidth = 0; while( pPor ) { nPorWidth = nPorWidth + pPor->Width(); @@ -462,7 +522,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; } @@ -470,10 +530,10 @@ bool SwTabPortion::PostFormat( SwTextFormatInfo &rInf ) // #127428# Abandon dec. tab position if line is full if ( bTabCompat && PortionType::TabDecimal == nWhich ) { - sal_uInt16 nPrePorWidth = static_cast<const SwTabDecimalPortion*>(this)->GetWidthOfPortionsUpToDecimalPosition(); + SwTwips nPrePorWidth = static_cast<const SwTabDecimalPortion*>(this)->GetWidthOfPortionsUpToDecimalPosition(); // no value was set => no decimal character was found - if ( USHRT_MAX != nPrePorWidth ) + if (std::numeric_limits<SwTwips>::max() != nPrePorWidth) { if ( !bTabOverMargin && nPrePorWidth && nPorWidth - nPrePorWidth > rInf.Width() - nRight ) { @@ -488,18 +548,18 @@ 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 ) + SwTwips nNewWidth = nPorWidth / 2; + if (!bTabOverMargin && !bTabOverSpacing && nNewWidth > rInf.Width() - nRight) nNewWidth = nPorWidth - (rInf.Width() - nRight); nPorWidth = nNewWidth; } - const sal_uInt16 nDiffWidth = nRight - GetFix(); + const SwTwips nDiffWidth = nRight - GetFix(); if( nDiffWidth > nPorWidth ) { - const sal_uInt16 nOldWidth = GetFixWidth(); - const sal_uInt16 nAdjDiff = nDiffWidth - nPorWidth; + const SwTwips nOldWidth = GetFixWidth(); + const SwTwips nAdjDiff = nDiffWidth - nPorWidth; if( nAdjDiff > GetFixWidth() ) PrtWidth( nAdjDiff ); // Don't be afraid: we have to move rInf further. @@ -524,7 +584,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 +596,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 +613,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 ); } @@ -562,14 +622,14 @@ void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const if( rInf.GetFont()->IsPaintBlank() ) { // Tabs with filling/filled tabs - const sal_uInt16 nCharWidth = rInf.GetTextSize(OUString(' ')).Width(); + const SwTwips nCharWidth = rInf.GetTextSize(OUString(' ')).Width(); // Robust: if( nCharWidth ) { // Always with kerning, also on printer! - sal_uInt16 nChar = Width() / nCharWidth; - OUStringBuffer aBuf; + sal_Int32 nChar = Width() / nCharWidth; + OUStringBuffer aBuf(nChar); comphelper::string::padToLength(aBuf, nChar, ' '); rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0), TextFrameIndex(nChar), true); @@ -581,17 +641,17 @@ void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const return; // Tabs with filling/filled tabs - const sal_uInt16 nCharWidth = rInf.GetTextSize(OUString(m_cFill)).Width(); + const SwTwips nCharWidth = rInf.GetTextSize(OUString(m_cFill)).Width(); OSL_ENSURE( nCharWidth, "!SwTabPortion::Paint: sophisticated tabchar" ); // Robust: if( nCharWidth ) { // Always with kerning, also on printer! - sal_uInt16 nChar = Width() / nCharWidth; + sal_Int32 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 +664,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..5379954d2a7f 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 @@ -63,7 +69,8 @@ SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, const SwTwips nRst ) if( !m_bKeep && m_pFrame->IsInSct() ) { const SwSectionFrame* const pSct = m_pFrame->FindSctFrame(); - m_bKeep = pSct->Lower()->IsColumnFrame() && !pSct->MoveAllowed( m_pFrame ); + if (const SwFrame* pLower = pSct->Lower()) + m_bKeep = pLower->IsColumnFrame() && !pSct->MoveAllowed( m_pFrame ); } m_bKeep = m_bKeep || !m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetSplit().GetValue() || m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep().GetValue(); @@ -99,7 +106,7 @@ SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, const SwTwips nRst ) * be done until the Follow is formatted. Unfortunately this is crucial * to decide if the whole paragraph goes to the next page or not. */ -bool SwTextFrameBreak::IsInside( SwTextMargin const &rLine ) const +bool SwTextFrameBreak::IsInside(SwTextMargin const& rLine, SwResizeLimitReason& reason) const { bool bFit = false; @@ -127,15 +134,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()) { @@ -161,7 +207,7 @@ bool SwTextFrameBreak::IsInside( SwTextMargin const &rLine ) const // The LineHeight exceeds the current Frame height. // Call a test Grow to detect if the Frame could // grow the requested area. - nHeight += m_pFrame->GrowTst( LONG_MAX ); + nHeight += m_pFrame->GrowTst(LONG_MAX, reason); // The Grow() returns the height by which the Upper of the TextFrame // would let the TextFrame grow. @@ -176,10 +222,11 @@ bool SwTextFrameBreak::IsInside( SwTextMargin const &rLine ) const bool SwTextFrameBreak::IsBreakNow( SwTextMargin &rLine ) { SwSwapIfSwapped swap(m_pFrame); + SwResizeLimitReason reason = SwResizeLimitReason::Unspecified; // bKeep is stronger than IsBreakNow() // Is there enough space ? - if( m_bKeep || IsInside( rLine ) ) + if (m_bKeep || IsInside(rLine, reason)) m_bBreak = false; else { @@ -194,6 +241,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() ) ) { @@ -206,6 +261,11 @@ bool SwTextFrameBreak::IsBreakNow( SwTextMargin &rLine ) if( !pTmp || !pTmp->Lower() ) m_bBreak = false; } + else if (reason == SwResizeLimitReason::FixedSizeFrame) + { + // The content is in a clipping frame - no need to break at all + m_bBreak = false; + } } return m_bBreak; @@ -267,7 +327,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: @@ -364,7 +434,7 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) OSL_ENSURE( ! pFrame->IsVertical() || ! pFrame->IsSwapped(), "WidowsAndOrphans::FindWidows with swapped frame" ); - if( !m_nWidLines || !pFrame->IsFollow() ) + if( !pFrame->IsFollow() ) return false; rLine.Bottom(); @@ -397,15 +467,156 @@ 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) + // hyphenation-keep-line: disable hyphenation in the last line instead of truncating it + // hyphenation-zone-always/page/column/spread: modify hyphenation in the last line (end zone) + int nExtraWidLines = 0; + if( rLine.GetLineNr() >= m_nWidLines && pMaster->HasPara() ) + { + SwParaPortion *pMasterPara = pMaster->GetPara(); + const SwAttrSet& rSet = pFrame->GetTextNodeForParaProps()->GetSwAttrSet(); + const SvxHyphenZoneItem &rAttr = rSet.GetHyphenZone(); + + bool bKeep = rAttr.IsHyphen() && rAttr.IsKeep() && rAttr.GetKeepType(); + bool bKeepLine = bKeep && rAttr.IsKeepLine(); + + // last line of a column inside a page + auto pMasterPage = pMaster->FindPageFrame(); + auto pPage = pFrame->FindPageFrame(); + // across column, but not page or spread, when both parts are there on the same page + bool bAcrossColumnNotPage = pMasterPage == pPage; + // across page, but not spread, when the parts are there not on the same page + bool bAcrossPageNotSpread = !bAcrossColumnNotPage && + !pMasterPage->OnRightPage() && pPage->OnRightPage() && + // linked text frames can be on a different spread, so check + // that the master is there on the previous page + pMasterPage == pPage->GetPrev(); + + // calculate end zones, based on their inheritance (0 means inheritance) + sal_Int16 nEndZoneParagraph = rAttr.GetTextHyphenZoneAlways() > 0 + ? rAttr.GetTextHyphenZoneAlways() + : rAttr.GetTextHyphenZone(); + sal_Int16 nEndZoneColumn = rAttr.GetTextHyphenZoneColumn() > 0 + ? rAttr.GetTextHyphenZoneColumn() + : nEndZoneParagraph; + sal_Int16 nEndZonePage = rAttr.GetTextHyphenZonePage() > 0 + ? rAttr.GetTextHyphenZonePage() + : nEndZoneColumn; + sal_Int16 nEndZoneSpread = rAttr.GetTextHyphenZoneSpread() > 0 + ? rAttr.GetTextHyphenZoneSpread() + : nEndZonePage; + + // set end zone + sal_Int16 nNoHyphEndZone = bAcrossColumnNotPage + ? nEndZoneColumn + : bAcrossPageNotSpread + ? nEndZonePage + : nEndZoneSpread; + + // if PAGE or SPREAD, allow hyphenation in the not last column or in the + // not last linked frame on the same page + if( bKeep && bAcrossColumnNotPage && ( + rAttr.GetKeepType() == css::text::ParagraphHyphenationKeepType::SPREAD || + rAttr.GetKeepType() == css::text::ParagraphHyphenationKeepType::PAGE ) ) + { + bKeep = false; + } + + // if SPREAD, allow hyphenation at bottom of left page on the same spread + if ( bKeep && rAttr.GetKeepType() == css::text::ParagraphHyphenationKeepType::SPREAD && + bAcrossPageNotSpread ) + { + bKeep = false; + } + + // remove remaining NoHyphOffset after enabling Hyphenate Across Spread (!bKeep) or + // enabling Move Line (!bKeepLine), or setting End Zone to no-limit, + // and invalidate the master to update the last line + if ( (!bKeep || !bKeepLine) && + pMaster->GetNoHyphOffset() != TextFrameIndex(COMPLETE_STRING) ) + { + pMaster->SetNoHyphOffset(TextFrameIndex(COMPLETE_STRING)); + pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pMaster->InvalidateSize_(); + } + + // applied end zone, i.e. hyphenation is not disabled completely, + // end zone is not zero and different from the hyphenation zone + bool bApplyEndZone = !bKeep && nNoHyphEndZone > 0 && + nNoHyphEndZone != rAttr.GetTextHyphenZone(); + if ( ( bKeep || bApplyEndZone ) && pMasterPara && pMasterPara->GetNext() ) + { + // calculate the beginning of last hyphenated line + TextFrameIndex nIdx(pMasterPara->GetLen()); + SwLineLayout * pNext = pMasterPara->GetNext(); + nIdx += pNext->GetLen(); + SwLineLayout * pCurr = pNext; + SwLineLayout * pPrev = pNext; + while ( pNext->GetNext() ) + { + pPrev = pCurr; + pCurr = pNext; + pNext = pNext->GetNext(); + nIdx += pNext->GetLen(); + } + nIdx -= pNext->GetLen(); + // hyphenated line, but not the last remaining one + // in the case of shifting full line (bKeepLine = false) + if ( pNext->IsEndHyph() && ( bKeepLine || !pNext->IsLastHyph() || bApplyEndZone ) ) + { + nExtraWidLines = rLine.GetLineNr() - m_nWidLines + 1; + // shift only a word: disable hyphenation in the line, if needed + if ( ( bKeepLine || bApplyEndZone ) && nExtraWidLines ) + { + pMaster->SetNoHyphOffset(nIdx); + pMaster->SetNoHyphEndZone(bApplyEndZone ? nNoHyphEndZone : -1); + // update also columns and frames + pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pMaster->InvalidateSize_(); + nExtraWidLines = 0; // no need to shift the full line + } + // shift full line: + // set remaining line to "last remaining hyphenated line" + // to avoid truncating multiple hyphenated lines instead + // of a single one + else if ( bKeep && !bKeepLine && 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 ); + } + + // update the old line with disabled hyphenation, i.e. when there is a line + // with disabled hyphenation, but it is not the last line any more + TextFrameIndex nNoHyphIdx = pMaster->GetNoHyphOffset(); + if ( nNoHyphIdx != TextFrameIndex(COMPLETE_STRING) && nNoHyphIdx != nIdx ) + { + // enable hyphenation for all the lines in the TextFrame again + pMaster->SetNoHyphOffset(TextFrameIndex(COMPLETE_STRING)); + // update all the previous lines before the previous offset, e.g. + // when deleting all the lines before the last line with disabled hyphenation + // results a starting line with disabled hyphenation -> repaint it with enabled + // hyphenation again + pMaster->InvalidateRange_(SwCharRange(TextFrameIndex(0), nNoHyphIdx)); + } + } + } + + // no widow (e.g. in tables) and no hyphenation-keep + if( !m_nWidLines && !nExtraWidLines ) + return 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 +626,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 +648,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 +672,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 +688,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 +759,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..bf3051df2298 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: @@ -40,6 +40,7 @@ public: void SetKeep( const bool bNew ) { m_bKeep = bNew; } bool IsInside( SwTextMargin const &rLine ) const; + bool IsInside(SwTextMargin const& rLine, SwResizeLimitReason&) const; // In order to be able to handle special cases with Footnote. // SetRstHeight sets the rest height for SwTextFrameBreak. This is needed @@ -63,7 +64,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 +80,15 @@ public: } }; +inline bool SwTextFrameBreak::IsInside(SwTextMargin const& rLine) const +{ + return IsInside(rLine, o3tl::temporary(SwResizeLimitReason())); +} + +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..5a0ac3da9d93 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 ); } } @@ -848,6 +849,7 @@ sal_uInt16 WrongListIteratorCounter::GetElementCount() InCurrentNode = 0; pNode = rExtent.pNode; } + assert(rExtent.pNode); SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)()); for (; pWrongList && InCurrentNode < pWrongList->Count(); ++InCurrentNode) { @@ -892,6 +894,7 @@ WrongListIteratorCounter::GetElementAt(sal_uInt16 nIndex) InCurrentNode = 0; pNode = rExtent.pNode; } + assert(rExtent.pNode); SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)()); for (; pWrongList && InCurrentNode < pWrongList->Count(); ++InCurrentNode) { 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: */ |