diff options
Diffstat (limited to 'sw/source/core/text/porlay.cxx')
-rw-r--r-- | sw/source/core/text/porlay.cxx | 757 |
1 files changed, 494 insertions, 263 deletions
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx index 3d26e990c50c..50aaa43b7745 100644 --- a/sw/source/core/text/porlay.cxx +++ b/sw/source/core/text/porlay.cxx @@ -33,7 +33,7 @@ #include <unicode/uchar.h> #include <com/sun/star/i18n/ScriptType.hpp> #include <com/sun/star/i18n/CharacterIteratorMode.hpp> -#include <com/sun/star/i18n/CTLScriptType.hpp> +#include <com/sun/star/i18n/UnicodeType.hpp> #include <com/sun/star/i18n/WordType.hpp> #include <com/sun/star/i18n/XBreakIterator.hpp> #include <paratr.hxx> @@ -41,58 +41,93 @@ #include <optional> #include <editeng/adjustitem.hxx> #include <editeng/charhiddenitem.hxx> +#include <editeng/escapementitem.hxx> #include <svl/asiancfg.hxx> #include <svl/languageoptions.hxx> #include <tools/multisel.hxx> #include <unotools/charclass.hxx> #include <charfmt.hxx> #include <docary.hxx> +#include <fmtanchr.hxx> #include <redline.hxx> #include <calbck.hxx> #include <doc.hxx> #include <swscanner.hxx> #include <txatbase.hxx> -#include <calc.hxx> #include <IDocumentRedlineAccess.hxx> #include <IDocumentSettingAccess.hxx> #include <IDocumentContentOperations.hxx> -#include <IDocumentFieldsAccess.hxx> #include <IMark.hxx> #include <sortedobjs.hxx> -#include <dcontact.hxx> - -using namespace ::com::sun::star; -using namespace i18n::ScriptType; +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/text/XBookmarksSupplier.hpp> +#include <officecfg/Office/Common.hxx> +#include <comphelper/processfactory.hxx> +#include <docsh.hxx> +#include <unobookmark.hxx> +#include <unocrsrhelper.hxx> +#include <frmatr.hxx> +#include <vcl/kernarray.hxx> +#include <editeng/ulspitem.hxx> +#include <com/sun/star/rdf/Statement.hpp> +#include <com/sun/star/rdf/URI.hpp> +#include <com/sun/star/rdf/URIs.hpp> +#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp> +#include <com/sun/star/rdf/XLiteral.hpp> +#include <com/sun/star/text/XTextContent.hpp> #include <unicode/ubidi.h> #include <i18nutil/scripttypedetector.hxx> #include <i18nutil/unicode.hxx> +using namespace ::com::sun::star; +using namespace i18n::ScriptType; + +/* + https://www.khtt.net/en/page/1821/the-big-kashida-secret + + the rules of priorities that govern the addition of kashidas in Arabic text + made ... for ... Explorer 5.5 browser. + + The kashida justification is based on a connection priority scheme that + decides where kashidas are put automatically. + + This is how the software decides on kashida-inserting priorities: + 1. First it looks for characters with the highest priority in each word, + which means kashida-extensions will only been used in one position in each + word. Not more. + 2. The kashida will be connected to the character with the highest priority. + 3. If kashida connection opportunities are found with an equal level of + priority in one word, the kashida will be placed towards the end of the + word. + + The priority list of characters and the positioning is as follows: + 1. after a kashida that is manually placed in the text by the user, + 2. after a Seen or Sad (initial and medial form), + 3. before the final form of Taa Marbutah, Haa, Dal, + 4. before the final form of Alef, Tah Lam, Kaf and Gaf, + 5. before the preceding medial Baa of Ra, Ya and Alef Maqsurah, + 6. before the final form of Waw, Ain, Qaf and Fa, + 7. before the final form of other characters that can be connected. +*/ + #define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g ) #define isAinChar(c) IS_JOINING_GROUP((c), AIN) #define isAlefChar(c) IS_JOINING_GROUP((c), ALEF) #define isDalChar(c) IS_JOINING_GROUP((c), DAL) -#if U_ICU_VERSION_MAJOR_NUM >= 58 #define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH)) -#else -#define isFehChar(c) IS_JOINING_GROUP((c), FEH) -#endif #define isGafChar(c) IS_JOINING_GROUP((c), GAF) #define isHehChar(c) IS_JOINING_GROUP((c), HEH) #define isKafChar(c) IS_JOINING_GROUP((c), KAF) #define isLamChar(c) IS_JOINING_GROUP((c), LAM) -#if U_ICU_VERSION_MAJOR_NUM >= 58 #define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF)) -#else -#define isQafChar(c) IS_JOINING_GROUP((c), QAF) -#endif #define isRehChar(c) IS_JOINING_GROUP((c), REH) #define isTahChar(c) IS_JOINING_GROUP((c), TAH) #define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA) #define isWawChar(c) IS_JOINING_GROUP((c), WAW) #define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN)) -// Beh and charters that behave like Beh in medial form. +// Beh and characters that behave like Beh in medial form. static bool isBehChar(sal_Unicode cCh) { bool bRet = false; @@ -100,9 +135,7 @@ static bool isBehChar(sal_Unicode cCh) { case U_JG_BEH: case U_JG_NOON: -#if U_ICU_VERSION_MAJOR_NUM >= 58 case U_JG_AFRICAN_NOON: -#endif case U_JG_NYA: case U_JG_YEH: case U_JG_FARSI_YEH: @@ -117,7 +150,7 @@ static bool isBehChar(sal_Unicode cCh) return bRet; } -// Yeh and charters that behave like Yeh in final form. +// Yeh and characters that behave like Yeh in final form. static bool isYehChar(sal_Unicode cCh) { bool bRet = false; @@ -185,21 +218,18 @@ void SwLineLayout::DeleteNext() { if (!m_pNext) return; - std::vector<SwLineLayout*> aNexts; SwLineLayout* pNext = m_pNext; do { - aNexts.push_back(pNext); SwLineLayout* pLastNext = pNext; pNext = pNext->GetNext(); pLastNext->SetNext(nullptr); + delete pLastNext; } while (pNext); - for (auto a : aNexts) - delete a; } -void SwLineLayout::Height(const sal_uInt16 nNew, const bool bText) +void SwLineLayout::Height(const SwTwips nNew, const bool bText) { SwPosSize::Height(nNew); if (bText) @@ -318,20 +348,25 @@ void SwLineLayout::CreateSpaceAdd( const tools::Long nInit ) SetLLSpaceAdd( nInit, 0 ); } -// Returns true if there are only blanks in [nStt, nEnd[ +// #i3952# Returns true if there are only blanks in [nStt, nEnd[ +// Used to implement IgnoreTabsAndBlanksForLineCalculation compat flag static bool lcl_HasOnlyBlanks(std::u16string_view rText, TextFrameIndex nStt, TextFrameIndex nEnd) { - bool bBlankOnly = true; while ( nStt < nEnd ) { - const sal_Unicode cChar = rText[ sal_Int32(nStt++) ]; - if ( ' ' != cChar && CH_FULL_BLANK != cChar && CH_SIX_PER_EM != cChar ) + switch (rText[sal_Int32(nStt++)]) { - bBlankOnly = false; - break; + case 0x0020: // SPACE + case 0x2002: // EN SPACE + case 0x2003: // EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x3000: // IDEOGRAPHIC SPACE + continue; + default: + return false; } } - return bBlankOnly; + return true; } // Swapped out from FormatLine() @@ -342,6 +377,10 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) sal_uInt16 nFlyAscent = 0; sal_uInt16 nFlyHeight = 0; sal_uInt16 nFlyDescent = 0; + + // If this line has a clearing break, then this is the portion's height. + sal_uInt16 nBreakHeight = 0; + bool bOnlyPostIts = true; SetHanging( false ); @@ -374,7 +413,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) } else { - const sal_uInt16 nLineHeight = Height(); + const SwTwips nLineHeight = Height(); Init( GetNextPortion() ); SwLinePortion *pPos = mpNextPortion; SwLinePortion *pLast = this; @@ -389,6 +428,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) // Null portions are eliminated. They can form if two FlyFrames // overlap. + // coverity[deref_arg] - "Cut" means next "GetNextPortion" returns a different Portion if( !pPos->Compress() ) { // Only take over Height and Ascent if the rest of the line @@ -400,7 +440,9 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) if( !GetAscent() ) SetAscent( pPos->GetAscent() ); } - delete pLast->Cut( pPos ); + SwLinePortion* pPortion = pLast->Cut( pPos ); + rLine.ClearIfIsFirstOfBorderMerge(pPortion); + delete pPortion; pPos = pLast->GetNextPortion(); continue; } @@ -410,7 +452,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) AddPrtWidth( pPos->Width() ); // #i3952# - if ( bIgnoreBlanksAndTabsForLineHeightCalculation ) + if (bIgnoreBlanksAndTabsForLineHeightCalculation && !rInf.GetLineStart()) { if ( pPos->InTabGrp() || pPos->IsHolePortion() || ( pPos->IsTextPortion() && @@ -437,8 +479,8 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) // We had an attribute change: Sum up/build maxima of length and mass - sal_uInt16 nPosHeight = pPos->Height(); - sal_uInt16 nPosAscent = pPos->GetAscent(); + SwTwips nPosHeight = pPos->Height(); + SwTwips nPosAscent = pPos->GetAscent(); SAL_WARN_IF( nPosHeight < nPosAscent, "sw.core", "SwLineLayout::CalcLine: bad ascent or height" ); @@ -451,10 +493,16 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) else if( !bHasFlyPortion && ( pPos->IsFlyCntPortion() || pPos->IsFlyPortion() ) ) bHasFlyPortion = true; - // To prevent that a paragraph-end-character does not change - // the line height through a Descent and thus causing the line - // to reformat. - if ( !pPos->IsBreakPortion() || !Height() ) + // A line break portion only influences the height of the line in case it's the only + // portion in the line, except when it's a clearing break. + bool bClearingBreak = false; + if (pPos->IsBreakPortion()) + { + auto pBreakPortion = static_cast<SwBreakPortion*>(pPos); + bClearingBreak = pBreakPortion->GetClear() != SwLineBreakClear::NONE; + nBreakHeight = nPosHeight; + } + if (!(pPos->IsBreakPortion() && !bClearingBreak) || !Height()) { if (!pPos->IsPostItsPortion()) bOnlyPostIts = false; @@ -567,7 +615,19 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) if( nFlyDescent > nFlyHeight - nFlyAscent ) Height( nFlyHeight + nFlyDescent, false ); else - Height( nFlyHeight, false ); + { + if (nBreakHeight > nFlyHeight) + { + // The line has no content, but it has a clearing break: then the line + // height is not only the intersection of the fly and line's rectangle, but + // also includes the clearing break's height. + Height(nBreakHeight, false); + } + else + { + Height(nFlyHeight, false); + } + } } else if( nMaxDescent > Height() - mnAscent ) Height( nMaxDescent + mnAscent, false ); @@ -591,14 +651,28 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) } } - // #i3952# + // #i3952# Whitespace does not increase line height if ( bHasBlankPortion && bHasOnlyBlankPortions ) { sal_uInt16 nTmpAscent = GetAscent(); sal_uInt16 nTmpHeight = Height(); rLine.GetAttrHandler().GetDefaultAscentAndHeight( rInf.GetVsh(), *rInf.GetOut(), nTmpAscent, nTmpHeight ); - SetAscent( nTmpAscent ); - Height( nTmpHeight, false ); + + short nEscapement = rLine.GetAttrHandler().GetFont()->GetEscapement(); + if (GetAscent() && Height() && !nTmpAscent && !nTmpHeight + && (nEscapement == DFLT_ESC_AUTO_SUPER || nEscapement == DFLT_ESC_AUTO_SUB)) + { + // We already had a calculated ascent + height, it would be cleared, automatic + // sub/superscript is set and we have no content. In this case it makes no sense to + // clear the old, correct ascent/height. + nTmpAscent = GetAscent(); + nTmpHeight = Height(); + } + + if (nTmpAscent < GetAscent() || GetAscent() <= 0) + SetAscent(nTmpAscent); + if (nTmpHeight < Height() || Height() <= 0) + Height(nTmpHeight, false); } // Robust: @@ -630,65 +704,70 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) SetRedline( bHasRedline ); // redlining: set crossing out for deleted anchored objects - if ( bHasFlyPortion ) + if ( !bHasFlyPortion ) + return; + + SwLinePortion *pPos = mpNextPortion; + TextFrameIndex nLineLength; + while ( pPos ) { - SwLinePortion *pPos = mpNextPortion; - TextFrameIndex nLineLength; - while ( pPos ) + TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + nLineLength; + nLineLength += pPos->GetLen(); + // anchored as characters + if( pPos->IsFlyCntPortion() ) { - TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + nLineLength; - nLineLength += pPos->GetLen(); - // anchored as characters - if( pPos->IsFlyCntPortion() ) + bool bDeleted = false; + size_t nAuthor = std::string::npos; + if ( bHasRedline ) { - bool bDeleted = false; - if ( bHasRedline ) - { - OUString sRedlineText; - bool bHasRedlineEnd; - enum RedlineType eRedlineEnd; - std::pair<SwTextNode const*, sal_Int32> const flyStart( - rInf.GetTextFrame()->MapViewToModel(nPorSttIdx)); - bool bHasFlyRedline = rLine.GetRedln()->CheckLine(flyStart.first->GetIndex(), - flyStart.second, flyStart.first->GetIndex(), flyStart.second, sRedlineText, - bHasRedlineEnd, eRedlineEnd, /*bFullLine=*/false); - bDeleted = bHasFlyRedline && eRedlineEnd == RedlineType::Delete; - } - static_cast<SwFlyCntPortion*>(pPos)->SetDeleted(bDeleted); + OUString sRedlineText; + bool bHasRedlineEnd; + enum RedlineType eRedlineEnd; + std::pair<SwTextNode const*, sal_Int32> const flyStart( + rInf.GetTextFrame()->MapViewToModel(nPorSttIdx)); + bool bHasFlyRedline = rLine.GetRedln()->CheckLine(flyStart.first->GetIndex(), + flyStart.second, flyStart.first->GetIndex(), flyStart.second, sRedlineText, + bHasRedlineEnd, eRedlineEnd, /*pAuthorAtPos=*/&nAuthor); + bDeleted = bHasFlyRedline && eRedlineEnd == RedlineType::Delete; } - // anchored to characters - else if ( pPos->IsFlyPortion() ) + static_cast<SwFlyCntPortion*>(pPos)->SetDeleted(bDeleted); + static_cast<SwFlyCntPortion*>(pPos)->SetAuthor(nAuthor); + } + // anchored to characters + else if ( pPos->IsFlyPortion() ) + { + const IDocumentRedlineAccess& rIDRA = + rInf.GetTextFrame()->GetDoc().getIDocumentRedlineAccess(); + SwSortedObjs *pObjs = rInf.GetTextFrame()->GetDrawObjs(); + if ( pObjs && IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) ) { - const IDocumentRedlineAccess& rIDRA = - rInf.GetTextFrame()->GetDoc().getIDocumentRedlineAccess(); - SwSortedObjs *pObjs = rInf.GetTextFrame()->GetDrawObjs(); - if ( pObjs && IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) ) + for ( size_t i = 0; rInf.GetTextFrame()->GetDrawObjs() && i < pObjs->size(); ++i ) { - for ( size_t i = 0; rInf.GetTextFrame()->GetDrawObjs() && i < pObjs->size(); ++i ) + SwAnchoredObject* pAnchoredObj = (*rInf.GetTextFrame()->GetDrawObjs())[i]; + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) { - SwAnchoredObject* pAnchoredObj = (*rInf.GetTextFrame()->GetDrawObjs())[i]; - if ( auto pFly = dynamic_cast<SwFlyFrame *>( pAnchoredObj ) ) + bool bDeleted = false; + size_t nAuthor = std::string::npos; + const SwFormatAnchor& rAnchor = pAnchoredObj->GetFrameFormat()->GetAnchor(); + if ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR ) { - bool bDeleted = false; - const SwFormatAnchor& rAnchor = pAnchoredObj->GetFrameFormat().GetAnchor(); - if ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR ) + SwPosition aAnchor = *rAnchor.GetContentAnchor(); + SwRedlineTable::size_type n = 0; + const SwRangeRedline* pFnd = + rIDRA.GetRedlineTable().FindAtPosition( aAnchor, n ); + if ( pFnd && RedlineType::Delete == pFnd->GetType() ) { - SwPosition aAnchor = *rAnchor.GetContentAnchor(); - const SwPaM aPam(aAnchor, aAnchor); - if ( rIDRA.HasRedline( aPam, RedlineType::Delete, - /*bStartOrEndInRange=*/false) ) - { - bDeleted = true; - } + bDeleted = true; + nAuthor = pFnd->GetAuthor(); } - pFly->SetDeleted(bDeleted); } - + pFly->SetDeleted(bDeleted); + pFly->SetAuthor(nAuthor); } } } - pPos = pPos->GetNextPortion(); } + pPos = pPos->GetNextPortion(); } } @@ -721,9 +800,8 @@ void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent, ( !pTmpPortion->IsFlyCntPortion() && !(pTmpPortion == this && pTmpPortion->GetNextPortion() ) ) ) ) { - SwTwips nPortionAsc = static_cast<SwTwips>(pTmpPortion->GetAscent()); - SwTwips nPortionDesc = static_cast<SwTwips>(pTmpPortion->Height()) - - nPortionAsc; + SwTwips nPortionAsc = pTmpPortion->GetAscent(); + SwTwips nPortionDesc = pTmpPortion->Height() - nPortionAsc; const bool bFlyCmp = pTmpPortion->IsFlyCntPortion() ? static_cast<const SwFlyCntPortion*>(pTmpPortion)->IsMax() : @@ -745,9 +823,19 @@ void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent, } } +void SwLineLayout::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, + TextFrameIndex& nOffset) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout")); + dumpAsXmlAttributes(pWriter, rText, nOffset); + nOffset += GetLen(); + + (void)xmlTextWriterEndElement(pWriter); +} + void SwLineLayout::ResetFlags() { - m_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bFly + m_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bLastHyph = m_bFly = m_bRest = m_bBlinking = m_bClipping = m_bContent = m_bRedline = m_bRedlineEnd = m_bForcedLeftMargin = m_bHanging = false; m_eRedlineEnd = RedlineType::None; @@ -826,19 +914,64 @@ SwFontScript SwScriptInfo::WhichFont(sal_Int32 nIdx, OUString const& rText) return lcl_ScriptToFont(nScript); } +static Color getBookmarkColor(const SwTextNode& rNode, const sw::mark::IBookmark* pBookmark) +{ + // search custom color in metadata, otherwise use COL_TRANSPARENT; + Color c = COL_TRANSPARENT; + + try + { + SwDoc& rDoc = const_cast<SwDoc&>(rNode.GetDoc()); + const rtl::Reference< SwXBookmark > xRef = SwXBookmark::CreateXBookmark(rDoc, + const_cast<sw::mark::IMark*>(static_cast<const sw::mark::IMark*>(pBookmark))); + const css::uno::Reference<css::rdf::XResource> xSubject(xRef); + uno::Reference<frame::XModel> xModel = rDoc.GetDocShell()->GetBaseModel(); + + static uno::Reference< uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext()); + + static uno::Reference< rdf::XURI > xODF_SHADING( + rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW); + + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess( + rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + const uno::Reference<rdf::XRepository>& xRepository = + xDocumentMetadataAccess->getRDFRepository(); + const uno::Reference<container::XEnumeration> xEnum( + xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW); + + rdf::Statement stmt; + if ( xEnum->hasMoreElements() && (xEnum->nextElement() >>= stmt) ) + { + const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY); + if ( xObject.is() ) + c = Color::STRtoRGB(xObject->getValue()); + } + } + catch (const lang::IllegalArgumentException&) + { + } + + return c; +} + static void InitBookmarks( std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter, std::vector<sw::Extent>::const_iterator iter, std::vector<sw::Extent>::const_iterator const end, TextFrameIndex nOffset, std::vector<std::pair<sw::mark::IBookmark const*, SwScriptInfo::MarkKind>> & rBookmarks, - std::vector<std::pair<TextFrameIndex, SwScriptInfo::MarkKind>> & o_rBookmarks) + std::vector<std::tuple<TextFrameIndex, SwScriptInfo::MarkKind, Color, OUString>> & o_rBookmarks) { SwTextNode const*const pNode(iter->pNode); for (auto const& it : rBookmarks) { assert(iter->pNode == pNode || pNode->GetIndex() < iter->pNode->GetIndex()); assert(!oPrevIter || (*oPrevIter)->pNode->GetIndex() <= pNode->GetIndex()); + + // search for custom bookmark boundary mark color + Color c = getBookmarkColor(*pNode, it.first); + switch (it.second) { case SwScriptInfo::MarkKind::Start: @@ -854,39 +987,39 @@ static void InitBookmarks( // assume "no" because the line break it contains isn't deleted. SwPosition const& rStart(it.first->GetMarkStart()); SwPosition const& rEnd(it.first->GetMarkEnd()); - assert(&rStart.nNode.GetNode() == pNode); + assert(&rStart.GetNode() == pNode); while (iter != end) { - if (&rStart.nNode.GetNode() != iter->pNode // iter moved to next node - || rStart.nContent.GetIndex() < iter->nStart) + if (&rStart.GetNode() != iter->pNode // iter moved to next node + || rStart.GetContentIndex() < iter->nStart) { - if (rEnd.nNode.GetIndex() < iter->pNode->GetIndex() - || (&rEnd.nNode.GetNode() == iter->pNode && rEnd.nContent.GetIndex() <= iter->nStart)) + if (rEnd.GetNodeIndex() < iter->pNode->GetIndex() + || (&rEnd.GetNode() == iter->pNode && rEnd.GetContentIndex() <= iter->nStart)) { break; // deleted - skip it } else { - o_rBookmarks.emplace_back(nOffset, it.second); + o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName()); break; } } - else if (rStart.nContent.GetIndex() <= iter->nEnd) + else if (rStart.GetContentIndex() <= iter->nEnd) { auto const iterNext(iter + 1); - if (rStart.nContent.GetIndex() == iter->nEnd + if (rStart.GetContentIndex() == iter->nEnd && (iterNext == end - ? &rEnd.nNode.GetNode() == iter->pNode - : (rEnd.nNode.GetIndex() < iterNext->pNode->GetIndex() - || (&rEnd.nNode.GetNode() == iterNext->pNode && rEnd.nContent.GetIndex() < iterNext->nStart)))) + ? &rEnd.GetNode() == iter->pNode + : (rEnd.GetNodeIndex() < iterNext->pNode->GetIndex() + || (&rEnd.GetNode() == iterNext->pNode && rEnd.GetContentIndex() < iterNext->nStart)))) { break; // deleted - skip it } else { o_rBookmarks.emplace_back( - nOffset + TextFrameIndex(rStart.nContent.GetIndex() - iter->nStart), - it.second); + nOffset + TextFrameIndex(rStart.GetContentIndex() - iter->nStart), + it.second, c, it.first->GetName()); break; } } @@ -899,13 +1032,13 @@ static void InitBookmarks( } if (iter == end) { - if (pNode->GetIndex() < rEnd.nNode.GetIndex()) // pNode is last node of merged + if (pNode->GetIndex() < rEnd.GetNodeIndex()) // pNode is last node of merged { break; // deleted - skip it } else { - o_rBookmarks.emplace_back(nOffset, it.second); + o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName()); } } break; @@ -913,36 +1046,36 @@ static void InitBookmarks( case SwScriptInfo::MarkKind::End: { SwPosition const& rEnd(it.first->GetMarkEnd()); - assert(&rEnd.nNode.GetNode() == pNode); + assert(&rEnd.GetNode() == pNode); while (true) { if (iter == end - || &rEnd.nNode.GetNode() != iter->pNode // iter moved to next node - || rEnd.nContent.GetIndex() <= iter->nStart) + || &rEnd.GetNode() != iter->pNode // iter moved to next node + || rEnd.GetContentIndex() <= iter->nStart) { SwPosition const& rStart(it.first->GetMarkStart()); // oPrevIter may point to pNode or a preceding node if (oPrevIter - ? ((*oPrevIter)->pNode->GetIndex() < rStart.nNode.GetIndex() - || ((*oPrevIter)->pNode == &rStart.nNode.GetNode() - && ((iter != end && &rEnd.nNode.GetNode() == iter->pNode && rEnd.nContent.GetIndex() == iter->nStart) - ? (*oPrevIter)->nEnd < rStart.nContent.GetIndex() - : (*oPrevIter)->nEnd <= rStart.nContent.GetIndex()))) - : rStart.nNode == rEnd.nNode) + ? ((*oPrevIter)->pNode->GetIndex() < rStart.GetNodeIndex() + || ((*oPrevIter)->pNode == &rStart.GetNode() + && ((iter != end && &rEnd.GetNode() == iter->pNode && rEnd.GetContentIndex() == iter->nStart) + ? (*oPrevIter)->nEnd < rStart.GetContentIndex() + : (*oPrevIter)->nEnd <= rStart.GetContentIndex()))) + : rStart.GetNode() == rEnd.GetNode()) { break; // deleted - skip it } else { - o_rBookmarks.emplace_back(nOffset, it.second); + o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName()); break; } } - else if (rEnd.nContent.GetIndex() <= iter->nEnd) + else if (rEnd.GetContentIndex() <= iter->nEnd) { o_rBookmarks.emplace_back( - nOffset + TextFrameIndex(rEnd.nContent.GetIndex() - iter->nStart), - it.second); + nOffset + TextFrameIndex(rEnd.GetContentIndex() - iter->nStart), + it.second, c, it.first->GetName()); break; } else @@ -957,26 +1090,26 @@ static void InitBookmarks( case SwScriptInfo::MarkKind::Point: { SwPosition const& rPos(it.first->GetMarkPos()); - assert(&rPos.nNode.GetNode() == pNode); + assert(&rPos.GetNode() == pNode); while (iter != end) { - if (&rPos.nNode.GetNode() != iter->pNode // iter moved to next node - || rPos.nContent.GetIndex() < iter->nStart) + if (&rPos.GetNode() != iter->pNode // iter moved to next node + || rPos.GetContentIndex() < iter->nStart) { break; // deleted - skip it } - else if (rPos.nContent.GetIndex() <= iter->nEnd) + else if (rPos.GetContentIndex() <= iter->nEnd) { - if (rPos.nContent.GetIndex() == iter->nEnd - && rPos.nContent.GetIndex() != iter->pNode->Len()) + if (rPos.GetContentIndex() == iter->nEnd + && rPos.GetContentIndex() != iter->pNode->Len()) { break; // deleted - skip it } else { o_rBookmarks.emplace_back( - nOffset + TextFrameIndex(rPos.nContent.GetIndex() - iter->nStart), - it.second); + nOffset + TextFrameIndex(rPos.GetContentIndex() - iter->nStart), + it.second, c, it.first->GetName()); } break; } @@ -990,6 +1123,10 @@ static void InitBookmarks( break; } } + if (iter == end) + { + break; // remaining marks are hidden + } } } @@ -1017,11 +1154,12 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, TextFrameIndex nOffset(0); std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter; for (auto iter = pMerged->extents.begin(); iter != pMerged->extents.end(); - oPrevIter = iter, ++iter) + oPrevIter = iter) { if (iter->pNode == pNode) { nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + ++iter; continue; // skip extents at end of previous node } pNode = iter->pNode; @@ -1037,39 +1175,66 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, const Range& rRange = aHiddenMulti.GetRange( i ); const sal_Int32 nStart = rRange.Min(); const sal_Int32 nEnd = rRange.Max() + 1; + bool isStartHandled(false); + ::std::optional<sal_Int32> oExtend; - while (true) + if (nEnd <= iter->nStart) + { // entirely in gap, skip this hidden range + continue; + } + + do { - // because of the selectRedLineDeleted call, never overlaps - // extents, must be contained inside one extent - assert(!(iter->nStart <= nStart && nStart < iter->nEnd && iter->nEnd < nEnd)); - assert(!(nStart < iter->nStart && iter->nStart < nEnd && nEnd <= iter->nEnd)); - if (iter->nStart <= nStart && nEnd <= iter->nEnd) + if (!isStartHandled && nStart <= iter->nEnd) { - if (iter->nStart == nStart && !m_HiddenChg.empty() + isStartHandled = true; + if (nStart <= iter->nStart && !m_HiddenChg.empty() && m_HiddenChg.back() == nOffset) { // previous one went until end of extent, extend it - m_HiddenChg.back() += TextFrameIndex(nEnd - iter->nStart); + oExtend.emplace(::std::min(iter->nEnd, nEnd) - ::std::max(iter->nStart, nStart)); } - else // new one + else { - m_HiddenChg.push_back(nOffset + TextFrameIndex(nStart - iter->nStart)); - m_HiddenChg.push_back(nOffset + TextFrameIndex(nEnd - iter->nStart)); + m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nStart - iter->nStart, sal_Int32(0)))); } - break; } - else + else if (oExtend) { - nOffset += TextFrameIndex(iter->nEnd - iter->nStart); - ++iter; - // because selectRedLineDeleted, must find it in pNode - assert(iter != pMerged->extents.end()); - assert(iter->pNode == pNode); + *oExtend += ::std::min(iter->nEnd, nEnd) - iter->nStart; + } + if (nEnd <= iter->nEnd) + { + if (oExtend) + { + m_HiddenChg.back() += TextFrameIndex(*oExtend); + } + else + { + m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nEnd - iter->nStart, sal_Int32(0)))); + } + break; // iterate to next hidden range } + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + ++iter; + } + while (iter != pMerged->extents.end() && iter->pNode == pNode); + if (iter == pMerged->extents.end() || iter->pNode != pNode) + { + if (isStartHandled) + { // dangling end + if (oExtend) + { + m_HiddenChg.back() += TextFrameIndex(*oExtend); + } + else + { + m_HiddenChg.push_back(nOffset); + } + } // else: beyond last extent in node, ignore + break; // skip hidden ranges beyond last extent in node } } - nOffset += TextFrameIndex(iter->nEnd - iter->nStart); } } else @@ -1081,25 +1246,38 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, for (auto const& it : bookmarks) { + // don't show __RefHeading__ bookmarks, which are hidden in Navigator, too + // (They are inserted automatically e.g. with the ToC at the beginning of + // the headings) + if (it.first->GetName().startsWith( + IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix())) + { + continue; + } + + // search for custom bookmark boundary mark color + Color c = getBookmarkColor(rNode, it.first); + switch (it.second) { case MarkKind::Start: - m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkStart().nContent.GetIndex()), it.second); + m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkStart().GetContentIndex()), it.second, c, it.first->GetName()); break; case MarkKind::End: - m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkEnd().nContent.GetIndex()), it.second); + m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkEnd().GetContentIndex()), it.second, c, it.first->GetName()); break; case MarkKind::Point: - m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkPos().nContent.GetIndex()), it.second); + m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkPos().GetContentIndex()), it.second, c, it.first->GetName()); break; } } + m_HiddenChg.reserve( aHiddenMulti.GetRangeCount() * 2 ); for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i) { const Range& rRange = aHiddenMulti.GetRange( i ); const sal_Int32 nStart = rRange.Min(); - const sal_Int32 nEnd = rRange.Max() + 1; + const sal_Int32 nEnd = rRange.Max() + (rText.isEmpty() ? 0 : 1); m_HiddenChg.push_back( TextFrameIndex(nStart) ); m_HiddenChg.push_back( TextFrameIndex(nEnd) ); @@ -1274,50 +1452,28 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, if (nChg > TextFrameIndex(rText.getLength()) || nChg < TextFrameIndex(0)) nChg = TextFrameIndex(rText.getLength()); - // #i28203# - // for 'complex' portions, we make sure that a portion does not contain more - // than one script: - if( i18n::ScriptType::COMPLEX == nScript ) - { - const short nScriptType = ScriptTypeDetector::getCTLScriptType( - rText, sal_Int32(nSearchStt) ); - TextFrameIndex nNextCTLScriptStart = nSearchStt; - short nCurrentScriptType = nScriptType; - while( css::i18n::CTLScriptType::CTL_UNKNOWN == nCurrentScriptType || nScriptType == nCurrentScriptType ) - { - nNextCTLScriptStart = TextFrameIndex( - ScriptTypeDetector::endOfCTLScriptType( - rText, sal_Int32(nNextCTLScriptStart))); - if (nNextCTLScriptStart >= TextFrameIndex(rText.getLength()) - || nNextCTLScriptStart >= nChg) - break; - nCurrentScriptType = ScriptTypeDetector::getCTLScriptType( - rText, sal_Int32(nNextCTLScriptStart)); - } - nChg = std::min( nChg, nNextCTLScriptStart ); - } - // special case for dotted circle since it can be used with complex // before a mark, so we want it associated with the mark's script - if (nChg < TextFrameIndex(rText.getLength()) && nChg > TextFrameIndex(0) - && (i18n::ScriptType::WEAK == - g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg) - 1))) + // tdf#112594: another special case for NNBSP followed by a Mongolian + // character, since NNBSP has special uses in Mongolian (tdf#112594) + auto nPos = sal_Int32(nChg); + auto nPrevPos = nPos; + auto nPrevChar = rText.iterateCodePoints(&nPrevPos, -1); + if (nChg < TextFrameIndex(rText.getLength()) && nChg > TextFrameIndex(0) && + i18n::ScriptType::WEAK == g_pBreakIt->GetBreakIter()->getScriptType(rText, nPrevPos)) { - int8_t nType = u_charType(rText[sal_Int32(nChg)]); - if (nType == U_NON_SPACING_MARK || nType == U_ENCLOSING_MARK || - nType == U_COMBINING_SPACING_MARK ) - { - m_ScriptChanges.emplace_back(nChg-TextFrameIndex(1), nScript); - } - else + auto nChar = rText.iterateCodePoints(&nPos, 0); + auto nType = unicode::getUnicodeType(nChar); + if (nType == css::i18n::UnicodeType::NON_SPACING_MARK || + nType == css::i18n::UnicodeType::ENCLOSING_MARK || + nType == css::i18n::UnicodeType::COMBINING_SPACING_MARK || + (nPrevChar == CHAR_NNBSP && + u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)) { - m_ScriptChanges.emplace_back(nChg, nScript); + nPos = nPrevPos; } } - else - { - m_ScriptChanges.emplace_back(nChg, nScript); - } + m_ScriptChanges.emplace_back(TextFrameIndex(nPos), nScript); ++nCnt; // if current script is asian, we search for compressible characters @@ -1340,6 +1496,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, case 0x3008: case 0x300A: case 0x300C: case 0x300E: case 0x3010: case 0x3014: case 0x3016: case 0x3018: case 0x301A: case 0x301D: + case 0xFF08: case 0xFF3B: case 0xFF5B: eState = SPECIAL_LEFT; break; // Right punctuation found @@ -1347,9 +1504,11 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, case 0x300D: case 0x300F: case 0x3011: case 0x3015: case 0x3017: case 0x3019: case 0x301B: case 0x301E: case 0x301F: + case 0xFF09: case 0xFF3D: case 0xFF5D: eState = SPECIAL_RIGHT; break; case 0x3001: case 0x3002: // Fullstop or comma + case 0xFF0C: case 0xFF0E: case 0xFF1A: case 0xFF1B: eState = SPECIAL_MIDDLE ; break; default: @@ -1406,7 +1565,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, [&rNode](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar) { return rNode.GetLang(nBegin, bNoChar ? 0 : 1, script); }); auto pGetLangOfChar(pMerged ? pGetLangOfCharM : pGetLangOfChar1); - SwScanner aScanner( pGetLangOfChar, rText, nullptr, ModelToViewHelper(), + SwScanner aScanner( std::move(pGetLangOfChar), rText, nullptr, ModelToViewHelper(), i18n::WordType::DICTIONARY_WORD, sal_Int32(nLastKashida), sal_Int32(nChg)); @@ -1415,10 +1574,9 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, { const OUString& rWord = aScanner.GetWord(); - sal_Int32 nIdx = 0; + sal_Int32 nIdx = 0, nPrevIdx = 0; sal_Int32 nKashidaPos = -1; - sal_Unicode cCh; - sal_Unicode cPrevCh = 0; + sal_Unicode cCh, cPrevCh = 0; int nPriorityLevel = 7; // 0..6 = level found // 7 not found @@ -1466,7 +1624,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 2; } } @@ -1487,7 +1645,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 3; } } @@ -1502,12 +1660,12 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if next character is Reh or Yeh-like sal_Unicode cNextCh = rWord[ nIdx + 1 ]; if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh )) - { + { SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 4; } } @@ -1529,7 +1687,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 5; } } @@ -1545,7 +1703,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 6; } } @@ -1554,7 +1712,10 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // Do not consider vowel marks when checking if a character // can be connected to previous character. if ( !isTransparentChar ( cCh) ) + { cPrevCh = cCh; + nPrevIdx = nIdx; + } ++nIdx; } // end of current word @@ -1776,29 +1937,47 @@ TextFrameIndex SwScriptInfo::NextBookmark(TextFrameIndex const nPos) const { for (auto const& it : m_Bookmarks) { - if (nPos < it.first) + if (nPos < std::get<0>(it)) { - return it.first; + return std::get<0>(it); } } return TextFrameIndex(COMPLETE_STRING); } -auto SwScriptInfo::GetBookmark(TextFrameIndex const nPos) const -> MarkKind +std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> + SwScriptInfo::GetBookmarks(TextFrameIndex const nPos) { - MarkKind ret{0}; + std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> aColors; for (auto const& it : m_Bookmarks) { - if (nPos == it.first) + if (nPos == std::get<0>(it)) { - ret |= it.second; + const OUString& sName = std::get<3>(it); + // filter hidden bookmarks imported from OOXML + // TODO import them as hidden bookmarks + if ( !( sName.startsWith("_Toc") || sName.startsWith("_Ref") ) ) + aColors.push_back(std::tuple<MarkKind, Color, + OUString>(std::get<1>(it), std::get<2>(it), std::get<3>(it))); } - else if (nPos < it.first) + else if (nPos < std::get<0>(it)) { break; } } - return ret; + + // sort bookmark boundary marks at the same position + // mark order: ] | [ + // color order: [c1 [c2 [c3 ... c3] c2] c1] + sort(aColors.begin(), aColors.end(), + [](std::tuple<MarkKind, Color, OUString> const a, std::tuple<MarkKind, Color, OUString> const b) { + return (MarkKind::End == std::get<0>(a) && MarkKind::End != std::get<0>(b)) || + (MarkKind::Point == std::get<0>(a) && MarkKind::Start == std::get<0>(b)) || + // if both are end or start, order by color + (MarkKind::End == std::get<0>(a) && MarkKind::End == std::get<0>(b) && std::get<1>(a) < std::get<1>(b)) || + (MarkKind::Start == std::get<0>(a) && MarkKind::Start == std::get<0>(b) && std::get<1>(b) < std::get<1>(a));}); + + return aColors; } // Takes a string and replaced the hidden ranges with cChar. @@ -2019,7 +2198,7 @@ size_t SwScriptInfo::HasKana(TextFrameIndex const nStart, TextFrameIndex const n return SAL_MAX_SIZE; } -tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, TextFrameIndex nLen, +tools::Long SwScriptInfo::Compress(KernArray& rKernArray, TextFrameIndex nIdx, TextFrameIndex nLen, const sal_uInt16 nCompress, const sal_uInt16 nFontHeight, bool bCenter, Point* pPoint ) const @@ -2055,7 +2234,7 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, return 0; tools::Long nSub = 0; - tools::Long nLast = nI ? pKernArray[ nI - 1 ] : 0; + tools::Long nLast = nI ? rKernArray[ nI - 1 ] : 0; do { const CompType nType = GetCompType( nCompIdx ); @@ -2067,7 +2246,7 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, nCompLen = nLen; // are we allowed to compress the character? - if ( pKernArray[ nI ] - nLast < nMinWidth ) + if ( rKernArray[ nI ] - nLast < nMinWidth ) { nIdx++; nI++; } @@ -2078,7 +2257,7 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, SAL_WARN_IF( SwScriptInfo::NONE == nType, "sw.core", "None compression?!" ); // nLast is width of current character - nLast -= pKernArray[ nI ]; + nLast -= rKernArray[ nI ]; nLast *= nCompress; tools::Long nMove = 0; @@ -2101,10 +2280,11 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, else nLast /= 100000; nSub -= nLast; - nLast = pKernArray[ nI ]; + nLast = rKernArray[ nI ]; if( nI && nMove ) - pKernArray[ nI - 1 ] += nMove; - pKernArray[ nI++ ] -= nSub; + rKernArray.adjust(nI - 1, nMove); + rKernArray.adjust(nI, -nSub); + ++nI; ++nIdx; } } @@ -2123,8 +2303,9 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, while( nIdx < nTmpChg ) { - nLast = pKernArray[ nI ]; - pKernArray[ nI++ ] -= nSub; + nLast = rKernArray[ nI ]; + rKernArray.adjust(nI, -nSub); + ++nI; ++nIdx; } } while( nIdx < nLen ); @@ -2136,8 +2317,8 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, // total number of kashida positions, or the number of kashida positions after some positions // have been dropped, depending on the state of the m_KashidaInvalid set. -sal_Int32 SwScriptInfo::KashidaJustify( tools::Long* pKernArray, - tools::Long* pScrArray, +sal_Int32 SwScriptInfo::KashidaJustify( KernArray* pKernArray, + sal_Bool* pKashidaArray, TextFrameIndex const nStt, TextFrameIndex const nLen, tools::Long nSpaceAdd ) const @@ -2193,6 +2374,11 @@ sal_Int32 SwScriptInfo::KashidaJustify( tools::Long* pKernArray, { TextFrameIndex nArrayPos = nIdx - nStt; + // mark Kashida insertion positions, code in VCL will use this + // array to know where to insert Kashida. + if (pKashidaArray) + pKashidaArray[sal_Int32(nArrayPos)] = true; + // next kashida position ++nCntKash; while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash)) @@ -2206,9 +2392,7 @@ sal_Int32 SwScriptInfo::KashidaJustify( tools::Long* pKernArray, while ( nArrayPos < nArrayEnd ) { - pKernArray[ sal_Int32(nArrayPos) ] += nKashAdd; - if ( pScrArray ) - pScrArray[ sal_Int32(nArrayPos) ] += nKashAdd; + pKernArray->adjust(sal_Int32(nArrayPos), nKashAdd); ++nArrayPos; } nKashAdd += nSpaceAdd; @@ -2395,13 +2579,13 @@ void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt, } } -TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pKernArray, - tools::Long* pScrArray, TextFrameIndex const nStt, +TextFrameIndex SwScriptInfo::ThaiJustify( std::u16string_view aText, KernArray* pKernArray, + TextFrameIndex const nStt, TextFrameIndex const nLen, TextFrameIndex nNumberOfBlanks, tools::Long nSpaceAdd ) { - SAL_WARN_IF( nStt + nLen > TextFrameIndex(rText.getLength()), "sw.core", "String in ThaiJustify too small" ); + SAL_WARN_IF( nStt + nLen > TextFrameIndex(aText.size()), "sw.core", "String in ThaiJustify too small" ); SwTwips nNumOfTwipsToDistribute = nSpaceAdd * sal_Int32(nNumberOfBlanks) / SPACING_PRECISION_FACTOR; @@ -2411,7 +2595,7 @@ TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pK for (sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI) { - const sal_Unicode cCh = rText[sal_Int32(nStt) + nI]; + const sal_Unicode cCh = aText[sal_Int32(nStt) + nI]; // check if character is not above or below base if ( ( 0xE34 > cCh || cCh > 0xE3A ) && @@ -2427,8 +2611,8 @@ TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pK ++nCnt; } - if ( pKernArray ) pKernArray[ nI ] += nSpaceSum; - if ( pScrArray ) pScrArray[ nI ] += nSpaceSum; + if (pKernArray) + pKernArray->adjust(nI, nSpaceSum); } return nCnt; @@ -2485,6 +2669,40 @@ TextFrameIndex SwParaPortion::GetParLen() const return nLen; } +bool SwParaPortion::HasNumberingPortion(FootnoteOrNot const eFootnote) const +{ + SwLinePortion const* pPortion(nullptr); + // the first line may contain only fly portion... + for (SwLineLayout const* pLine = this; pLine && !pPortion; pLine = pLine->GetNext()) + { + pPortion = pLine->GetFirstPortion(); + while (pPortion && (pPortion->InGlueGrp() || pPortion->IsKernPortion() || pPortion->IsFlyPortion())) + { // skip margins and fly spacers - numbering should be first then + pPortion = pPortion->GetNextPortion(); + } + } + if (pPortion && pPortion->InHyphGrp()) + { // weird special case, bullet with soft hyphen + pPortion = pPortion->GetNextPortion(); + } + return pPortion && pPortion->InNumberGrp() + && (eFootnote == SwParaPortion::FootnoteToo || !pPortion->IsFootnoteNumPortion()); +} + +bool SwParaPortion::HasContentPortions() const +{ + SwLinePortion const* pPortion(nullptr); + for (SwLineLayout const* pLine = this; pLine && !pPortion; pLine = pLine->GetNext()) + { + pPortion = pLine->GetFirstPortion(); + while (pPortion && (pPortion->InGlueGrp() || pPortion->IsKernPortion() || pPortion->IsFlyPortion())) + { // skip margins and fly spacers + pPortion = pPortion->GetNextPortion(); + } + } + return pPortion != nullptr; +} + const SwDropPortion *SwParaPortion::FindDropPortion() const { const SwLineLayout *pLay = this; @@ -2502,6 +2720,16 @@ const SwDropPortion *SwParaPortion::FindDropPortion() const return nullptr; } +void SwParaPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, + TextFrameIndex& nOffset) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwParaPortion")); + dumpAsXmlAttributes(pWriter, rText, nOffset); + nOffset += GetLen(); + + (void)xmlTextWriterEndElement(pWriter); +} + void SwLineLayout::Init( SwLinePortion* pNextPortion ) { Height( 0, false ); @@ -2558,6 +2786,32 @@ SwTwips SwTextFrame::HangingMargin() const return nRet; } +SwTwips SwTextFrame::GetLowerMarginForFlyIntersect() const +{ + const IDocumentSettingAccess& rIDSA = GetDoc().getIDocumentSettingAccess(); + if (!rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN)) + { + // Word >= 2013 style or Writer style: lower margin is ignored when determining the text + // frame height. + return 0; + } + + const SwAttrSet* pAttrSet = GetTextNodeForParaProps()->GetpSwAttrSet(); + if (!pAttrSet) + { + return 0; + } + + // If it has multiple lines, then probably it already has the needed fly portion. + // Limit this to empty paragraphs for now. + if ((GetPara() && GetPara()->GetNext()) || !GetText().isEmpty()) + { + return 0; + } + + return pAttrSet->GetULSpace().GetLower(); +} + void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, MultiSelection & rHiddenMulti, std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks) @@ -2565,9 +2819,8 @@ void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1) || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len())); - const SfxPoolItem* pItem = nullptr; - if( SfxItemState::SET == rNode.GetSwAttrSet().GetItemState( RES_CHRATR_HIDDEN, true, &pItem ) && - static_cast<const SvxCharHiddenItem*>(pItem)->GetValue() ) + const SvxCharHiddenItem* pItem = rNode.GetSwAttrSet().GetItemIfSet( RES_CHRATR_HIDDEN ); + if( pItem && pItem->GetValue() ) { rHiddenMulti.SelectAll(); } @@ -2593,7 +2846,7 @@ void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, } } - for (const SwIndex* pIndex = rNode.GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + for (const SwContentIndex* pIndex = rNode.GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) { const sw::mark::IMark* pMark = pIndex->GetMark(); const sw::mark::IBookmark* pBookmark = dynamic_cast<const sw::mark::IBookmark*>(pMark); @@ -2614,33 +2867,13 @@ void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, } } - bool bHide = false; + // condition is evaluated in DocumentFieldsManager::UpdateExpFields() if (pBookmark && pBookmark->IsHidden()) { - // bookmark is marked as hidden - bHide = true; - - // bookmark is marked as hidden with conditions - if (!pBookmark->GetHideCondition().isEmpty()) - { - SwDoc& rDoc = const_cast<SwDoc&>(rNode.GetDoc()); - SwCalc aCalc(rDoc); - rDoc.getIDocumentFieldsAccess().FieldsToCalc(aCalc, rNode.GetIndex(), SAL_MAX_INT32); - - SwSbxValue aValue = aCalc.Calculate(pBookmark->GetHideCondition()); - if(!aValue.IsVoidValue()) - { - bHide = aValue.GetBool(); - } - } - } - - if (bHide) - { // intersect bookmark range with textnode range and add the intersection to rHiddenMulti - const sal_Int32 nSt = pBookmark->GetMarkStart().nContent.GetIndex(); - const sal_Int32 nEnd = pBookmark->GetMarkEnd().nContent.GetIndex(); + const sal_Int32 nSt = pBookmark->GetMarkStart().GetContentIndex(); + const sal_Int32 nEnd = pBookmark->GetMarkEnd().GetContentIndex(); if( nEnd > nSt ) { @@ -2666,7 +2899,7 @@ void SwScriptInfo::selectRedLineDeleted(const SwTextNode& rNode, MultiSelection { const SwRangeRedline* pRed = rIDRA.GetRedlineTable()[ nAct ]; - if (pRed->Start()->nNode > rNode.GetIndex()) + if (pRed->Start()->GetNode() > rNode) break; if (pRed->GetType() != RedlineType::Delete) @@ -2734,12 +2967,12 @@ TextFrameIndex SwScriptInfo::CountCJKCharacters(const OUString &rText, return nCount; } -void SwScriptInfo::CJKJustify( const OUString& rText, tools::Long* pKernArray, - tools::Long* pScrArray, TextFrameIndex const nStt, +void SwScriptInfo::CJKJustify( const OUString& rText, KernArray& rKernArray, + TextFrameIndex const nStt, TextFrameIndex const nLen, LanguageType aLang, tools::Long nSpaceAdd, bool bIsSpaceStop ) { - assert( pKernArray != nullptr && sal_Int32(nStt) >= 0 ); + assert( sal_Int32(nStt) >= 0 ); if (sal_Int32(nLen) <= 0) return; @@ -2757,9 +2990,7 @@ void SwScriptInfo::CJKJustify( const OUString& rText, tools::Long* pKernArray, if (nNext < sal_Int32(nStt + nLen) || !bIsSpaceStop) nSpaceSum += nSpaceAdd; } - pKernArray[ nI ] += nSpaceSum; - if ( pScrArray ) - pScrArray[ nI ] += nSpaceSum; + rKernArray.adjust(nI, nSpaceSum); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |