/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// This is (meant to be) functionally equivalent to 'delete m_pNext' where // deleting a SwLineLayout recursively deletes the owned m_pNext SwLineLayout. // // Here, instead of using a potentially deep stack, iterate over all the // SwLineLayouts that would be deleted recursively and delete them linearly void SwLineLayout::DeleteNext()
{ if (!m_pNext) return;
SwLineLayout* pNext = m_pNext; do
{
SwLineLayout* pLastNext = pNext;
pNext = pNext->GetNext();
pLastNext->SetNext(nullptr); delete pLastNext;
} while (pNext);
}
// class SwLineLayout: This is the layout of a single line, which is made // up of its dimension, the character count and the word spacing in the line. // Line objects are managed in an own pool, in order to store them continuously // in memory so that they are paged out together and don't fragment memory.
SwLineLayout::~SwLineLayout()
{
Truncate();
DeleteNext();
m_pLLSpaceAdd.reset();
m_pKanaComp.reset();
}
SwLinePortion *SwLineLayout::Insert( SwLinePortion *pIns )
{ // First attribute change: copy mass and length from *pIns into the first // text portion if( !mpNextPortion )
{ if( GetLen() )
{
mpNextPortion = SwTextPortion::CopyLinePortion(*this); if( IsBlinking() )
{
SetBlinking( false );
}
} else
{
SetNextPortion( pIns ); return pIns;
}
} // Call with scope or we'll end up with recursion! return mpNextPortion->SwLinePortion::Insert( pIns );
}
SwLinePortion *SwLineLayout::Append( SwLinePortion *pIns )
{ // First attribute change: copy mass and length from *pIns into the first // text portion if( !mpNextPortion )
mpNextPortion = SwTextPortion::CopyLinePortion(*this); // Call with scope or we'll end up with recursion! return mpNextPortion->SwLinePortion::Append( pIns );
}
// #i3952# Returns true if there are only blanks in [nStt, nEnd[ // Used to implement IgnoreTabsAndBlanksForLineCalculation compat flag staticbool lcl_HasOnlyBlanks(std::u16string_view rText, TextFrameIndex nStt, TextFrameIndex nEnd, bool isFieldMarkPortion)
{ while ( nStt < nEnd )
{ switch (rText[sal_Int32(nStt++)])
{ case 0x0020: // SPACE case 0x2003: // EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x3000: // IDEOGRAPHIC SPACE continue; case 0x2002: // EN SPACE : if (isFieldMarkPortion) returnfalse; else continue; default: returnfalse;
}
} returntrue;
}
// Swapped out from FormatLine() void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf )
{ const SwTwips nLineWidth = rInf.RealWidth();
// A group is a segment in the portion chain of pCurr or a fixed // portion spanning to the end or the next fixed portion while( pPos )
{
SAL_WARN_IF( PortionType::NONE == pPos->GetWhichPor(), "sw.core", "SwLineLayout::CalcLine: don't use SwLinePortions !" );
// Null portions are eliminated. They can form if two FlyFrames // overlap. // coverity[deref_arg] - "Cut" means next "GetNextPortion" returns a different Portion if( !pPos->Compress() )
{ // Only take over Height and Ascent if the rest of the line // is empty. if( !pPos->GetNextPortion() )
{ if( !Height() )
Height( pPos->Height(), false ); if( !GetAscent() )
SetAscent( pPos->GetAscent() );
}
SwLinePortion* pPortion = pLast->Cut( pPos );
rLine.ClearIfIsFirstOfBorderMerge(pPortion); delete pPortion;
pPos = pLast->GetNextPortion(); continue;
}
// A line break portion only influences the height of the line in case it's the only // portion in the line, except when it's a clearing break. bool bClearingBreak = false; if (pPos->IsBreakPortion())
{ auto pBreakPortion = static_cast<SwBreakPortion*>(pPos);
bClearingBreak = pBreakPortion->GetClear() != SwLineBreakClear::NONE;
nBreakHeight = nPosHeight;
} if (!(pPos->IsBreakPortion() && !bClearingBreak) || !Height())
{ if (!pPos->IsPostItsPortion()) bOnlyPostIts = false;
if( bTmpDummy && nFlyHeight )
{
mnAscent = nFlyAscent; if( nFlyDescent > nFlyHeight - nFlyAscent )
Height( nFlyHeight + nFlyDescent, false ); else
{ if (nBreakHeight > nFlyHeight)
{ // The line has no content, but it has a clearing break: then the line // height is not only the intersection of the fly and line's rectangle, but // also includes the clearing break's height.
Height(nBreakHeight, false);
} else
{
Height(nFlyHeight, false);
}
}
} elseif( nMaxDescent > Height() - mnAscent )
Height( nMaxDescent + mnAscent, false );
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());
} elseif (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);
} elseif (bHasBlankPortion)
{ if( mnAscent < nSpacePortionAscent )
mnAscent = nSpacePortionAscent; if (Height() < nSpacePortionHeight)
Height(nSpacePortionHeight, true);
}
} // #i3952# Whitespace does not increase line height elseif (bHasBlankPortion && bHasOnlyBlankPortions)
{
sal_uInt16 nTmpAscent = GetAscent();
sal_uInt16 nTmpHeight = Height();
rLine.GetAttrHandler().GetDefaultAscentAndHeight( rInf.GetVsh(), *rInf.GetOut(), nTmpAscent, nTmpHeight );
short nEscapement = rLine.GetAttrHandler().GetFont()->GetEscapement(); if (GetAscent() && Height() && !nTmpAscent && !nTmpHeight
&& (nEscapement == DFLT_ESC_AUTO_SUPER || nEscapement == DFLT_ESC_AUTO_SUB))
{ // We already had a calculated ascent + height, it would be cleared, automatic // sub/superscript is set and we have no content. In this case it makes no sense to // clear the old, correct ascent/height.
nTmpAscent = GetAscent();
nTmpHeight = Height();
}
if (nTmpAscent < GetAscent() || GetAscent() <= 0)
SetAscent(nTmpAscent); if (nTmpHeight < Height() || Height() <= 0)
Height(nTmpHeight, false);
}
static Color getBookmarkColor(const SwTextNode& rNode, sw::mark::Bookmark* pBookmark)
{ // search custom color in metadata, otherwise use COL_TRANSPARENT;
Color c = COL_TRANSPARENT;
for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i)
{ const Range& rRange = aHiddenMulti.GetRange( i ); const sal_Int32 nStart = rRange.Min(); const sal_Int32 nEnd = rRange.Max() + 1; bool isStartHandled(false);
::std::optional<sal_Int32> oExtend;
if (nEnd <= iter->nStart)
{ // entirely in gap, skip this hidden range continue;
}
do
{ if (!isStartHandled && nStart <= iter->nEnd)
{
isStartHandled = true; if (nStart <= iter->nStart && !m_HiddenChg.empty()
&& m_HiddenChg.back() == nOffset)
{ // previous one went until end of extent, extend it
oExtend.emplace(::std::min(iter->nEnd, nEnd) - ::std::max(iter->nStart, nStart));
} else
{
m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nStart - iter->nStart, sal_Int32(0))));
}
} elseif (oExtend)
{
*oExtend += ::std::min(iter->nEnd, nEnd) - iter->nStart;
} if (nEnd <= iter->nEnd)
{ if (oExtend)
{
m_HiddenChg.back() += TextFrameIndex(*oExtend);
} else
{
m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nEnd - iter->nStart, sal_Int32(0))));
} break; // iterate to next hidden range
}
nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
++iter;
} while (iter != pMerged->extents.end() && iter->pNode == pNode); if (iter == pMerged->extents.end() || iter->pNode != pNode)
{ if (isStartHandled)
{ // dangling end if (oExtend)
{
m_HiddenChg.back() += TextFrameIndex(*oExtend);
} else
{
m_HiddenChg.push_back(nOffset);
}
} // else: beyond last extent in node, ignore break; // skip hidden ranges beyond last extent in node
}
}
}
} else
{
Range aRange( 0, !rText.isEmpty() ? rText.getLength() - 1 : 0 );
MultiSelection aHiddenMulti( aRange );
std::vector<std::pair<sw::mark::Bookmark*, MarkKind>> bookmarks;
CalcHiddenRanges(rNode, aHiddenMulti, &bookmarks);
for (autoconst& 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().GetContentIndex()), it.second, c, it.first->GetName(), sType); break; case MarkKind::End:
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().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() + (rText.isEmpty() ? 0 : 1);
if( nChg )
{ // if change position = 0 we do not use any data from the arrays // because by deleting all characters of the first group at the beginning // of a paragraph nScript is set to a wrong value
SAL_WARN_IF( !CountScriptChg(), "sw.core", "Where're my changes of script?" ); while( nCnt < CountScriptChg() )
{ if ( nChg > GetScriptChg( nCnt ) )
nCnt++; else
{
nScript = GetScriptType( nCnt ); break;
}
} if( CharCompressType::NONE != aCompEnum )
{ while( nCntComp < CountCompChg() )
{ if ( nChg <= GetCompStart( nCntComp ) ) break;
nCntComp++;
}
}
}
// ADJUST nChg VALUE:
// by stepping back one position we know that we are inside a group // declared as an nScript group if ( nChg )
--nChg;
// we go back in our group until we reach the first character of // type nScript while ( nChg > nGrpStart &&
nScript != g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)))
--nChg;
// If we are at the start of a group, we do not trust nScript, // we better get nScript from the breakiterator: if ( nChg == nGrpStart )
nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)));
// INVALID DATA FROM THE SCRIPT INFO ARRAYS HAS TO BE DELETED:
// remove invalid entries from script information arrays
m_ScriptChanges.erase(m_ScriptChanges.begin() + nCnt, m_ScriptChanges.end());
// get the start of the last compression group
TextFrameIndex nLastCompression = nChg; if( nCntComp )
{
--nCntComp;
nLastCompression = GetCompStart( nCntComp ); if( nChg >= nLastCompression + GetCompLen( nCntComp ) )
{
nLastCompression = nChg;
++nCntComp;
}
}
// remove invalid entries from compression information arrays
m_CompressionChanges.erase(m_CompressionChanges.begin() + nCntComp,
m_CompressionChanges.end());
// 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())
{ if (static_cast<sal_Int32>(nChg) < pScriptScanner->Peek().m_nEndIndex)
{ break;
}
pScriptScanner->Advance();
}
// UPDATE THE SCRIPT INFO ARRAYS:
while (nChg < TextFrameIndex(rText.getLength())
|| (m_ScriptChanges.empty() && rText.isEmpty()))
{ auto stChange = pScriptScanner->Peek();
pScriptScanner->Advance();
// if current script is asian, we search for compressible characters // in this range if ( CharCompressType::NONE != aCompEnum &&
i18n::ScriptType::ASIAN == nScript )
{
CompType ePrevState = NONE;
CompType eState = NONE;
TextFrameIndex nPrevChg = nLastCompression;
// examine current character switch ( cChar )
{ // Left punctuation found case 0x3008: case 0x300A: case 0x300C: case 0x300E: case 0x3010: case 0x3014: case 0x3016: case 0x3018: case 0x301A: case 0x301D: case 0xFF08: case 0xFF3B: case 0xFF5B:
eState = SPECIAL_LEFT; break; // Right punctuation found case 0x3009: case 0x300B: case 0x300D: case 0x300F: case 0x3011: case 0x3015: case 0x3017: case 0x3019: case 0x301B: case 0x301E: case 0x301F: case 0xFF09: case 0xFF3D: case 0xFF5D:
eState = SPECIAL_RIGHT; break; case 0x3001: case 0x3002: // Fullstop or comma case 0xFF0C: case 0xFF0E: case 0xFF1A: case 0xFF1B:
eState = SPECIAL_MIDDLE ; break; default:
eState = ( 0x3040 <= cChar && 0x3100 > cChar ) ? KANA : NONE;
}
// insert range of compressible characters if( ePrevState != eState )
{ if ( ePrevState != NONE )
{ // insert start and type if ( CharCompressType::PunctuationAndKana == aCompEnum ||
ePrevState != KANA )
{
m_CompressionChanges.emplace_back(nPrevChg,
nLastCompression - nPrevChg, ePrevState);
}
}
// we still have to examine last entry if ( ePrevState != NONE )
{ // insert start and type if ( CharCompressType::PunctuationAndKana == aCompEnum ||
ePrevState != KANA )
{
m_CompressionChanges.emplace_back(nPrevChg,
nLastCompression - nPrevChg, ePrevState);
}
}
} elseif (m_bAdjustBlock && i18n::ScriptType::COMPLEX == nScript)
{ if (SwScriptInfo::IsKashidaScriptText(
rText, TextFrameIndex{ stChange.m_nStartIndex },
TextFrameIndex{ stChange.m_nEndIndex - stChange.m_nStartIndex }))
{
m_bParagraphContainsKashidaScript = true;
}
}
if (nChg < TextFrameIndex(rText.getLength()))
nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)));
nLastCompression = nChg;
}
// remove invalid entries from direction information arrays
m_DirectionChanges.clear();
// Perform Unicode Bidi Algorithm for text direction information
pDirScanner->Reset(); while (!pDirScanner->AtEnd())
{ auto stDirChange = pDirScanner->Peek();
m_DirectionChanges.emplace_back(TextFrameIndex{ stDirChange.m_nEndIndex },
stDirChange.m_nLevel);
pDirScanner->Advance();
}
}
// returns the position of the next character which belongs to another script // than the character of the actual (input) position. // If there's no script change until the end of the paragraph, it will return // COMPLETE_STRING. // Scripts are Asian (Chinese, Japanese, Korean), // Latin ( English etc.) // and Complex ( Hebrew, Arabian )
TextFrameIndex SwScriptInfo::NextScriptChg(const TextFrameIndex nPos) const
{ const size_t nEnd = CountScriptChg(); for( size_t nX = 0; nX < nEnd; ++nX )
{ if( nPos < GetScriptChg( nX ) ) return GetScriptChg( nX );
}
return TextFrameIndex(COMPLETE_STRING);
}
// returns the script of the character at the input position
sal_Int16 SwScriptInfo::ScriptType(const TextFrameIndex nPos) const
{ const size_t nEnd = CountScriptChg(); for( size_t nX = 0; nX < nEnd; ++nX )
{ if( nPos < GetScriptChg( nX ) ) return GetScriptType( nX );
}
// the default is the application language script return SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
}
// Takes a SwTextNode and deletes the hidden ranges from the node. void SwScriptInfo::DeleteHiddenRanges( SwTextNode& rNode )
{
std::vector<sal_Int32> aList;
sal_Int32 nHiddenStart;
sal_Int32 nHiddenEnd;
GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList ); auto rFirst( aList.crbegin() ); auto rLast( aList.crend() ); while ( rFirst != rLast )
{
nHiddenEnd = *(rFirst++);
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.24Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.