/* -*- 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() );
}
// In asian typography, there are full width and half width characters. // Full width punctuation characters can be compressed by 50% // to determine this, we compare the font width with 75% of its height const tools::Long nMinWidth = ( 3 * nFontHeight ) / 4;
// 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::IsKashidaScriptText(const OUString& rText,
TextFrameIndex const nStt, TextFrameIndex const nLen)
{ usingnamespace ::com::sun::star::i18n; staticconst ScriptTypeList typeList[] = {
{ 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: const CharClass& rCC = GetAppCharClass();
sal_Int32 nIdx = sal_Int32(nStt); const sal_Int32 nEnd = sal_Int32(nStt + nLen); while ( nIdx < nEnd && !rCC.isLetterNumeric( rText, nIdx ) )
{
++nIdx;
}
if( nIdx == nEnd )
{ // no regular character found in this portion. Go backward:
--nIdx; while ( nIdx >= 0 && !rCC.isLetterNumeric( rText, nIdx ) )
{
--nIdx;
}
}
// looks for hanging punctuation portions in the paragraph // and return the maximum right offset of them. // If no such portion is found, the Margin/Hanging-flags will be updated.
SwTwips SwLineLayout::GetHangingMargin_() const
{
SwLinePortion* pPor = GetNextPortion(); bool bFound = false;
SwTwips nDiff = 0; while( pPor)
{ if( pPor->IsHangingPortion() )
{
nDiff = static_cast<SwHangingPortion*>(pPor)->GetInnerWidth() - pPor->Width(); if( nDiff )
bFound = true;
} // the last post its portion elseif ( pPor->IsPostItsPortion() && ! pPor->GetNextPortion() )
nDiff = mnAscent;
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;
}
// 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;
}
// condition is evaluated in DocumentFieldsManager::UpdateExpFields() if (pBookmark && pBookmark->IsHidden())
{ // intersect bookmark range with textnode range and add the intersection to rHiddenMulti
for ( ; nAct < rIDRA.GetRedlineTable().size(); nAct++ )
{ const SwRangeRedline* pRed = rIDRA.GetRedlineTable()[ nAct ];
if (pRed->Start()->GetNode() > rNode) break;
if (pRed->GetType() != RedlineType::Delete) continue;
sal_Int32 nRedlStart;
sal_Int32 nRedlnEnd;
pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd ); //clip it if the redline extends past the end of the nodes text
nRedlnEnd = std::min<sal_Int32>(nRedlnEnd, rNode.GetText().getLength()); if ( nRedlnEnd > nRedlStart )
{
Range aTmp( nRedlStart, nRedlnEnd - 1 );
rHiddenMulti.Select( aTmp, bSelect );
}
}
}
// If there are any hidden ranges in the current text node, we have // to unhide the redlining ranges:
selectRedLineDeleted(rNode, rHiddenMulti, false);
// We calculated a lot of stuff. Finally we can update the flags at the text node.
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.