2062 lines
85 KiB
C++
2062 lines
85 KiB
C++
/* -*- 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 .
|
|
*/
|
|
|
|
#include <ndtxt.hxx>
|
|
#include <doc.hxx>
|
|
#include <paratr.hxx>
|
|
#include <flyfrm.hxx>
|
|
#include <pam.hxx>
|
|
#include <swselectionlist.hxx>
|
|
#include <sortedobjs.hxx>
|
|
#include <editeng/adjustitem.hxx>
|
|
#include <editeng/lspcitem.hxx>
|
|
#include <editeng/lrspitem.hxx>
|
|
#include <frmatr.hxx>
|
|
#include <tgrditem.hxx>
|
|
#include <IDocumentSettingAccess.hxx>
|
|
#include <pagefrm.hxx>
|
|
|
|
#include "itrtxt.hxx"
|
|
#include <txtfrm.hxx>
|
|
#include <flyfrms.hxx>
|
|
#include "porfld.hxx"
|
|
#include "porfly.hxx"
|
|
#include "pordrop.hxx"
|
|
#include <crstate.hxx>
|
|
#include "pormulti.hxx"
|
|
#include <numrule.hxx>
|
|
#include <com/sun/star/i18n/ScriptType.hpp>
|
|
|
|
// Not reentrant !!!
|
|
// is set in GetCharRect and is interpreted in UnitUp/Down.
|
|
bool SwTextCursor::s_bRightMargin = false;
|
|
|
|
// After calculating the position of a character during GetCharRect
|
|
// this function allows to find the coordinates of a position (defined
|
|
// in pCMS->pSpecialPos) inside a special portion (e.g., a field)
|
|
static void lcl_GetCharRectInsideField( SwTextSizeInfo& rInf, SwRect& rOrig,
|
|
const SwCursorMoveState& rCMS,
|
|
const SwLinePortion& rPor )
|
|
{
|
|
assert(rCMS.m_pSpecialPos && "Information about special pos missing");
|
|
|
|
if ( rPor.InFieldGrp() && !static_cast<const SwFieldPortion&>(rPor).GetExp().isEmpty() )
|
|
{
|
|
const sal_Int32 nCharOfst = rCMS.m_pSpecialPos->nCharOfst;
|
|
sal_Int32 nFieldIdx = 0;
|
|
sal_Int32 nFieldLen = 0;
|
|
|
|
OUString sString;
|
|
const OUString* pString = nullptr;
|
|
const SwLinePortion* pPor = &rPor;
|
|
do
|
|
{
|
|
if ( pPor->InFieldGrp() )
|
|
{
|
|
sString = static_cast<const SwFieldPortion*>(pPor)->GetExp();
|
|
pString = &sString;
|
|
nFieldLen = pString->getLength();
|
|
}
|
|
else
|
|
{
|
|
pString = nullptr;
|
|
nFieldLen = 0;
|
|
}
|
|
|
|
if ( ! pPor->GetNextPortion() || nFieldIdx + nFieldLen > nCharOfst )
|
|
break;
|
|
|
|
nFieldIdx += nFieldLen;
|
|
rOrig.Pos().AdjustX(pPor->Width() );
|
|
pPor = pPor->GetNextPortion();
|
|
|
|
} while ( true );
|
|
|
|
OSL_ENSURE( nCharOfst >= nFieldIdx, "Request of position inside field failed" );
|
|
sal_Int32 nLen = nCharOfst - nFieldIdx + 1;
|
|
|
|
if ( pString )
|
|
{
|
|
// get script for field portion
|
|
rInf.GetFont()->SetActual( SwScriptInfo::WhichFont(0, *pString) );
|
|
|
|
TextFrameIndex const nOldLen = pPor->GetLen();
|
|
const_cast<SwLinePortion*>(pPor)->SetLen(TextFrameIndex(nLen - 1));
|
|
const SwTwips nX1 = pPor->GetLen() ?
|
|
pPor->GetTextSize( rInf ).Width() :
|
|
0;
|
|
|
|
SwTwips nX2 = 0;
|
|
if ( rCMS.m_bRealWidth )
|
|
{
|
|
const_cast<SwLinePortion*>(pPor)->SetLen(TextFrameIndex(nLen));
|
|
nX2 = pPor->GetTextSize( rInf ).Width();
|
|
}
|
|
|
|
const_cast<SwLinePortion*>(pPor)->SetLen( nOldLen );
|
|
|
|
rOrig.Pos().AdjustX(nX1 );
|
|
rOrig.Width( ( nX2 > nX1 ) ?
|
|
( nX2 - nX1 ) :
|
|
1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// special cases: no common fields, e.g., graphic number portion,
|
|
// FlyInCntPortions, Notes
|
|
rOrig.Width( rCMS.m_bRealWidth && rPor.Width() ? rPor.Width() : 1 );
|
|
}
|
|
}
|
|
|
|
// #i111284#
|
|
namespace {
|
|
bool IsLabelAlignmentActive( const SwTextNode& rTextNode )
|
|
{
|
|
bool bRet( false );
|
|
|
|
if ( rTextNode.GetNumRule() )
|
|
{
|
|
int nListLevel = rTextNode.GetActualListLevel();
|
|
|
|
if (nListLevel < 0)
|
|
nListLevel = 0;
|
|
|
|
if (nListLevel >= MAXLEVEL)
|
|
nListLevel = MAXLEVEL - 1;
|
|
|
|
const SwNumFormat& rNumFormat =
|
|
rTextNode.GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) );
|
|
if ( rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
|
|
{
|
|
bRet = true;
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
} // end of anonymous namespace
|
|
|
|
void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *pNewInf )
|
|
{
|
|
CtorInitTextIter( pNewFrame, pNewInf );
|
|
|
|
m_pInf = pNewInf;
|
|
GetInfo().SetFont( GetFnt() );
|
|
const SwTextNode *const pNode = m_pFrame->GetTextNodeForParaProps();
|
|
|
|
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() != ::sw::ListLevelIndents::No;
|
|
const bool bListLevelIndentsApplicableAndLabelAlignmentActive = bListLevelIndentsApplicable && bLabelAlignmentActive;
|
|
|
|
// Carefully adjust the text formatting ranges.
|
|
|
|
// This whole area desperately needs some rework. There are
|
|
// quite a couple of values that need to be considered:
|
|
// 1. paragraph indent
|
|
// 2. paragraph first line indent
|
|
// 3. numbering indent
|
|
// 4. numbering spacing to text
|
|
// 5. paragraph border
|
|
// Note: These values have already been used during calculation
|
|
// of the printing area of the paragraph.
|
|
const int nLMWithNum = pNode->GetLeftMarginWithNum( true );
|
|
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();
|
|
(rTextLeftMargin.ResolveLeft(rFirstLine, stMetrics)
|
|
- rTextLeftMargin.ResolveTextLeft(stMetrics));
|
|
}
|
|
else
|
|
{
|
|
// #i95907#
|
|
// #i111284#
|
|
if ( bListLevelIndentsApplicableAndLabelAlignmentActive ||
|
|
!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#
|
|
(rTextLeftMargin.ResolveLeft(rFirstLine, stMetrics)
|
|
- rTextLeftMargin.ResolveTextLeft(stMetrics));
|
|
}
|
|
else
|
|
{
|
|
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();
|
|
|
|
// 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
|
|
mnRight = mnLeft + 1; // einen goennen wir uns immer
|
|
}
|
|
|
|
if( m_pFrame->IsFollow() && m_pFrame->GetOffset() )
|
|
mnFirst = mnLeft;
|
|
else
|
|
{
|
|
short nFLOfst = 0;
|
|
tools::Long nFirstLineOfs = 0;
|
|
if (!pNode->GetFirstLineOfsWithNum(nFLOfst, stMetrics) && rFirstLine.IsAutoFirst())
|
|
{
|
|
nFirstLineOfs = GetFnt()->GetSize( GetFnt()->GetActual() ).Height();
|
|
LanguageType const aLang = m_pFrame->GetLangOfChar(
|
|
TextFrameIndex(0), css::i18n::ScriptType::ASIAN);
|
|
if (aLang != LANGUAGE_KOREAN && aLang != LANGUAGE_JAPANESE)
|
|
nFirstLineOfs<<=1;
|
|
|
|
// 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))
|
|
{
|
|
const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing();
|
|
if( pSpace )
|
|
{
|
|
switch( pSpace->GetLineSpaceRule() )
|
|
{
|
|
case SvxLineSpaceRule::Auto:
|
|
break;
|
|
case SvxLineSpaceRule::Min:
|
|
{
|
|
if( nFirstLineOfs < pSpace->GetLineHeight() )
|
|
nFirstLineOfs = pSpace->GetLineHeight();
|
|
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;
|
|
break;
|
|
}
|
|
case SvxInterLineSpaceRule::Fix:
|
|
{
|
|
nFirstLineOfs += pSpace->GetInterLineSpace();
|
|
break;
|
|
}
|
|
default: OSL_FAIL( ": unknown InterLineSpaceRule" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
nFirstLineOfs = nFLOfst;
|
|
|
|
// #i95907#
|
|
// #i111284#
|
|
if ( m_pFrame->IsRightToLeft() ||
|
|
bListLevelIndentsApplicableAndLabelAlignmentActive ||
|
|
!pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) )
|
|
{
|
|
if ( nFirstLineOfs < 0 && m_pFrame->IsInTab() &&
|
|
mnLeft == m_pFrame->getFramePrintArea().Left() + m_pFrame->getFrameArea().Left() &&
|
|
!m_pFrame->IsRightToLeft() &&
|
|
!bListLevelIndentsApplicableAndLabelAlignmentActive )
|
|
{
|
|
// tdf#130218 always show hanging indent in narrow table cells
|
|
// to avoid hiding the text content of the first line
|
|
mnLeft -= nFirstLineOfs;
|
|
}
|
|
|
|
mnFirst = mnLeft + nFirstLineOfs;
|
|
}
|
|
else
|
|
{
|
|
mnFirst = m_pFrame->getFrameArea().Left()
|
|
+ std::max(rTextLeftMargin.ResolveTextLeft(stMetrics) + nLMWithNum
|
|
+ nFirstLineOfs,
|
|
m_pFrame->getFramePrintArea().Left());
|
|
}
|
|
|
|
// Note: <SwTextFrame::GetAdditionalFirstLineOffset()> returns a negative
|
|
// value for the new list label position and space mode LABEL_ALIGNMENT
|
|
// and label alignment CENTER and RIGHT in L2R layout respectively
|
|
// label alignment LEFT and CENTER in R2L layout
|
|
mnFirst += m_pFrame->GetAdditionalFirstLineOffset();
|
|
|
|
if( mnFirst >= mnRight )
|
|
mnFirst = mnRight - 1;
|
|
}
|
|
const SvxAdjustItem& rAdjust = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust();
|
|
mnAdjust = rAdjust.GetAdjust();
|
|
|
|
// left is left and right is right
|
|
if ( m_pFrame->IsRightToLeft() )
|
|
{
|
|
if ( SvxAdjust::Left == mnAdjust )
|
|
mnAdjust = SvxAdjust::Right;
|
|
else if ( SvxAdjust::Right == mnAdjust )
|
|
mnAdjust = SvxAdjust::Left;
|
|
}
|
|
|
|
m_bOneBlock = rAdjust.GetOneWord() == SvxAdjust::Block;
|
|
m_bLastBlock = rAdjust.GetLastBlock() == SvxAdjust::Block;
|
|
m_bLastCenter = rAdjust.GetLastBlock() == SvxAdjust::Center;
|
|
|
|
// #i91133#
|
|
mnTabLeft = pNode->GetLeftMarginForTabCalculation();
|
|
|
|
DropInit();
|
|
}
|
|
|
|
void SwTextMargin::DropInit()
|
|
{
|
|
mnDropLeft = mnDropLines = mnDropHeight = mnDropDescent = 0;
|
|
const SwParaPortion *pPara = GetInfo().GetParaPortion();
|
|
if( pPara )
|
|
{
|
|
const SwDropPortion *pPorDrop = pPara->FindDropPortion();
|
|
if ( pPorDrop )
|
|
{
|
|
mnDropLeft = pPorDrop->GetDropLeft();
|
|
mnDropLines = pPorDrop->GetLines();
|
|
mnDropHeight = pPorDrop->GetDropHeight();
|
|
mnDropDescent = pPorDrop->GetDropDescent();
|
|
}
|
|
}
|
|
}
|
|
|
|
// The function is interpreting / observing / evaluating / keeping / respecting the first line indention and the specified width.
|
|
SwTwips SwTextMargin::GetLineStart() const
|
|
{
|
|
SwTwips nRet = GetLeftMargin();
|
|
if( GetAdjust() != SvxAdjust::Left &&
|
|
!m_pCurr->GetFirstPortion()->IsMarginPortion() )
|
|
{
|
|
// If the first portion is a Margin, then the
|
|
// adjustment is expressed by the portions.
|
|
if( GetAdjust() == SvxAdjust::Right )
|
|
nRet = Right() - CurrWidth();
|
|
else if( GetAdjust() == SvxAdjust::Center )
|
|
nRet += (GetLineWidth() - CurrWidth()) / 2;
|
|
}
|
|
return nRet;
|
|
}
|
|
|
|
void SwTextCursor::CtorInitTextCursor( SwTextFrame *pNewFrame, SwTextSizeInfo *pNewInf )
|
|
{
|
|
CtorInitTextMargin( pNewFrame, pNewInf );
|
|
// 6096: Attention, the iterators are derived!
|
|
// 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 )
|
|
{
|
|
// 1170: Ambiguity of document positions
|
|
s_bRightMargin = true;
|
|
CharCursorToLine(nOfst);
|
|
|
|
// Somehow twisted: nOfst names the position behind the last
|
|
// character of the last line == This is the position in front of the first character
|
|
// of the line, in which we are situated:
|
|
if( nOfst != GetStart() || !m_pCurr->GetLen() )
|
|
{
|
|
// 8810: Master line RightMargin, after that LeftMargin
|
|
GetCharRect( pOrig, nOfst, pCMS, nMax );
|
|
s_bRightMargin = nOfst >= GetEnd() && nOfst < TextFrameIndex(GetInfo().GetText().getLength());
|
|
return;
|
|
}
|
|
|
|
if( !GetPrev() || !GetPrev()->GetLen() || !PrevLine() )
|
|
{
|
|
GetCharRect( pOrig, nOfst, pCMS, nMax );
|
|
return;
|
|
}
|
|
|
|
// If necessary, as catch up, do the adjustment
|
|
GetAdjusted();
|
|
|
|
tools::Long nX = 0;
|
|
tools::Long nLast = 0;
|
|
SwLinePortion *pPor = m_pCurr->GetFirstPortion();
|
|
|
|
SwTwips nTmpHeight, nTmpAscent;
|
|
CalcAscentAndHeight( nTmpAscent, nTmpHeight );
|
|
sal_uInt16 nPorHeight = nTmpHeight;
|
|
sal_uInt16 nPorAscent = nTmpAscent;
|
|
|
|
// Search for the last Text/EndPortion of the line
|
|
while( pPor )
|
|
{
|
|
nX += pPor->Width();
|
|
if( pPor->InTextGrp() || ( pPor->GetLen() && !pPor->IsFlyPortion()
|
|
&& !pPor->IsHolePortion() ) || pPor->IsBreakPortion() )
|
|
{
|
|
nLast = nX;
|
|
nPorHeight = pPor->Height();
|
|
nPorAscent = pPor->GetAscent();
|
|
}
|
|
pPor = pPor->GetNextPortion();
|
|
}
|
|
|
|
const Size aCharSize( 1, nTmpHeight );
|
|
pOrig->Pos( GetTopLeft() );
|
|
pOrig->SSize( aCharSize );
|
|
pOrig->Pos().AdjustX(nLast );
|
|
const SwTwips nTmpRight = Right() - 1;
|
|
if( pOrig->Left() > nTmpRight )
|
|
pOrig->Pos().setX( nTmpRight );
|
|
|
|
if ( pCMS && pCMS->m_bRealHeight )
|
|
{
|
|
if ( nTmpAscent > nPorAscent )
|
|
pCMS->m_aRealHeight.setX( nTmpAscent - nPorAscent );
|
|
else
|
|
pCMS->m_aRealHeight.setX( 0 );
|
|
OSL_ENSURE( nPorHeight, "GetCharRect: Missing Portion-Height" );
|
|
pCMS->m_aRealHeight.setY( nPorHeight );
|
|
}
|
|
}
|
|
|
|
// internal function, called by SwTextCursor::GetCharRect() to calculate
|
|
// the relative character position in the current line.
|
|
// pOrig refers to x and y coordinates, width and height of the cursor
|
|
// pCMS is used for restricting the cursor, if there are different font
|
|
// heights in one line ( first value = offset to y of pOrig, second
|
|
// value = real height of (shortened) cursor
|
|
void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst,
|
|
SwCursorMoveState* pCMS )
|
|
{
|
|
const OUString aText = GetInfo().GetText();
|
|
SwTextSizeInfo aInf( GetInfo(), &aText, m_nStart );
|
|
if( GetPropFont() )
|
|
aInf.GetFont()->SetProportion( GetPropFont() );
|
|
SwTwips nTmpAscent, nTmpHeight; // Line height
|
|
CalcAscentAndHeight( nTmpAscent, nTmpHeight );
|
|
const Size aCharSize( 1, nTmpHeight );
|
|
const Point aCharPos;
|
|
pOrig->Pos( aCharPos );
|
|
pOrig->SSize( aCharSize );
|
|
|
|
// If we are looking for a position inside a field which covers
|
|
// more than one line we may not skip any "empty portions" at the
|
|
// beginning of a line
|
|
const bool bInsideFirstField = pCMS && pCMS->m_pSpecialPos &&
|
|
( pCMS->m_pSpecialPos->nLineOfst ||
|
|
SwSPExtendRange::BEFORE ==
|
|
pCMS->m_pSpecialPos->nExtendRange );
|
|
|
|
bool bWidth = pCMS && pCMS->m_bRealWidth;
|
|
if( !m_pCurr->GetLen() && !m_pCurr->Width() )
|
|
{
|
|
if ( pCMS && pCMS->m_bRealHeight )
|
|
{
|
|
pCMS->m_aRealHeight.setX( 0 );
|
|
pCMS->m_aRealHeight.setY( nTmpHeight );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SwTwips nPorHeight = nTmpHeight;
|
|
SwTwips nPorAscent = nTmpAscent;
|
|
SwTwips nX = 0;
|
|
SwTwips nTmpFirst = 0;
|
|
SwLinePortion *pPor = m_pCurr->GetFirstPortion();
|
|
SwBidiPortion* pLastBidiPor = nullptr;
|
|
TextFrameIndex nLastBidiIdx(-1);
|
|
SwTwips nLastBidiPorWidth = 0;
|
|
std::deque<sal_uInt16>* pKanaComp = m_pCurr->GetpKanaComp();
|
|
sal_uInt16 nSpaceIdx = 0;
|
|
size_t nKanaIdx = 0;
|
|
tools::Long nSpaceAdd = m_pCurr->IsSpaceAdd() ? m_pCurr->GetLLSpaceAdd( 0 ) : 0;
|
|
|
|
bool bNoText = true;
|
|
|
|
// First all portions without Len at beginning of line are skipped.
|
|
// Exceptions are the mean special portions from WhichFirstPortion:
|
|
// Num, ErgoSum, FootnoteNum, FieldRests
|
|
// 8477: but also the only Textportion of an empty line with
|
|
// Right/Center-Adjustment! So not just pPor->GetExpandPortion() ...
|
|
while( pPor && !pPor->GetLen() && ! bInsideFirstField )
|
|
{
|
|
nX += pPor->Width();
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
nX += pPor->CalcSpacing( nSpaceAdd, aInf );
|
|
if( bNoText )
|
|
nTmpFirst = nX;
|
|
// 8670: EndPortions count once as TextPortions.
|
|
// if( pPor->InTextGrp() || pPor->IsBreakPortion() )
|
|
if( pPor->InTextGrp() || pPor->IsBreakPortion() || pPor->InTabGrp() )
|
|
{
|
|
bNoText = false;
|
|
nTmpFirst = nX;
|
|
}
|
|
if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
|
|
{
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
|
|
++nKanaIdx;
|
|
}
|
|
if( pPor->InFixMargGrp() )
|
|
{
|
|
if( pPor->IsMarginPortion() )
|
|
bNoText = false;
|
|
else
|
|
{
|
|
// fix margin portion => next SpaceAdd, KanaComp value
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
|
|
++nKanaIdx;
|
|
}
|
|
}
|
|
pPor = pPor->GetNextPortion();
|
|
}
|
|
|
|
if( !pPor )
|
|
{
|
|
// There's just Spezialportions.
|
|
nX = nTmpFirst;
|
|
}
|
|
else
|
|
{
|
|
if( !pPor->IsMarginPortion() && !pPor->IsPostItsPortion() &&
|
|
(!pPor->InFieldGrp() || pPor->GetAscent() ) )
|
|
{
|
|
nPorHeight = pPor->Height();
|
|
nPorAscent = pPor->GetAscent();
|
|
}
|
|
while( pPor && !pPor->IsBreakPortion() && ( aInf.GetIdx() < nOfst ||
|
|
( bWidth && ( pPor->IsKernPortion() || pPor->IsMultiPortion() ) ) ) )
|
|
{
|
|
if( !pPor->IsMarginPortion() && !pPor->IsPostItsPortion() &&
|
|
(!pPor->InFieldGrp() || pPor->GetAscent() ) )
|
|
{
|
|
nPorHeight = pPor->Height();
|
|
nPorAscent = pPor->GetAscent();
|
|
}
|
|
|
|
// If we are behind the portion, we add the portion width to
|
|
// nX. Special case: nOfst = aInf.GetIdx() + pPor->GetLen().
|
|
// For common portions (including BidiPortions) we want to add
|
|
// the portion width to nX. For MultiPortions, nExtra = 0,
|
|
// therefore we go to the 'else' branch and start a recursion.
|
|
const TextFrameIndex nExtra( (pPor->IsMultiPortion()
|
|
&& !static_cast<SwMultiPortion*>(pPor)->IsBidi()
|
|
&& !bWidth)
|
|
? 0 : 1 );
|
|
if ( aInf.GetIdx() + pPor->GetLen() < nOfst + nExtra )
|
|
{
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
// 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
|
|
{
|
|
if( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() )
|
|
{
|
|
// update to current SpaceAdd, KanaComp values
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if ( pKanaComp &&
|
|
( nKanaIdx + 1 ) < pKanaComp->size()
|
|
)
|
|
++nKanaIdx;
|
|
}
|
|
if ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() &&
|
|
!pPor->GetNextPortion()->IsMarginPortion() ) )
|
|
nX += pPor->PrtWidth();
|
|
}
|
|
if( pPor->IsMultiPortion() )
|
|
{
|
|
if ( static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
|
|
{
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
|
|
++nKanaIdx;
|
|
}
|
|
|
|
// if we are right behind a BidiPortion, we have to
|
|
// hold a pointer to the BidiPortion in order to
|
|
// find the correct cursor position, depending on the
|
|
// cursor level
|
|
if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() &&
|
|
aInf.GetIdx() + pPor->GetLen() == nOfst )
|
|
{
|
|
pLastBidiPor = static_cast<SwBidiPortion*>(pPor);
|
|
nLastBidiIdx = aInf.GetIdx();
|
|
nLastBidiPorWidth = pLastBidiPor->Width() +
|
|
pLastBidiPor->CalcSpacing( nSpaceAdd, aInf );
|
|
}
|
|
}
|
|
|
|
aInf.SetIdx( aInf.GetIdx() + pPor->GetLen() );
|
|
pPor = pPor->GetNextPortion();
|
|
}
|
|
else
|
|
{
|
|
if( pPor->IsMultiPortion() )
|
|
{
|
|
GetInfo().SetMulti( true );
|
|
pOrig->Pos().AdjustY(nTmpAscent - nPorAscent );
|
|
|
|
if( pCMS && pCMS->m_b2Lines )
|
|
{
|
|
const bool bRecursion (pCMS->m_p2Lines);
|
|
if ( !bRecursion )
|
|
{
|
|
pCMS->m_p2Lines.reset(new Sw2LinesPos);
|
|
pCMS->m_p2Lines->aLine = SwRect(aCharPos, aCharSize);
|
|
}
|
|
|
|
if( static_cast<SwMultiPortion*>(pPor)->HasRotation() )
|
|
{
|
|
if( static_cast<SwMultiPortion*>(pPor)->IsRevers() )
|
|
pCMS->m_p2Lines->nMultiType = MultiPortionType::ROT_270;
|
|
else
|
|
pCMS->m_p2Lines->nMultiType = MultiPortionType::ROT_90;
|
|
}
|
|
else if( static_cast<SwMultiPortion*>(pPor)->IsDouble() )
|
|
pCMS->m_p2Lines->nMultiType = MultiPortionType::TWOLINE;
|
|
else if( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
|
|
pCMS->m_p2Lines->nMultiType = MultiPortionType::BIDI;
|
|
else
|
|
pCMS->m_p2Lines->nMultiType = MultiPortionType::RUBY;
|
|
|
|
SwTwips nTmpWidth = pPor->Width();
|
|
if( nSpaceAdd )
|
|
nTmpWidth += pPor->CalcSpacing(nSpaceAdd, aInf);
|
|
|
|
SwRect aRect( Point(aCharPos.X() + nX, pOrig->Top() ),
|
|
Size( nTmpWidth, pPor->Height() ) );
|
|
|
|
if ( ! bRecursion )
|
|
pCMS->m_p2Lines->aPortion = aRect;
|
|
else
|
|
pCMS->m_p2Lines->aPortion2 = aRect;
|
|
}
|
|
|
|
// In a multi-portion we use GetCharRect()-function
|
|
// recursively and must add the x-position
|
|
// of the multi-portion.
|
|
TextFrameIndex const nOldStart = m_nStart;
|
|
SwTwips nOldY = m_nY;
|
|
sal_uInt8 nOldProp = GetPropFont();
|
|
m_nStart = aInf.GetIdx();
|
|
SwLineLayout* pOldCurr = m_pCurr;
|
|
m_pCurr = &static_cast<SwMultiPortion*>(pPor)->GetRoot();
|
|
if( static_cast<SwMultiPortion*>(pPor)->IsDouble() )
|
|
SetPropFont( 50 );
|
|
|
|
SwTextGridItem const*const pGrid(
|
|
GetGridItem(GetTextFrame()->FindPageFrame()));
|
|
const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
|
|
const sal_uInt16 nRubyHeight = bHasGrid ?
|
|
pGrid->GetRubyHeight() : 0;
|
|
|
|
if( m_nStart + m_pCurr->GetLen() <= nOfst && GetNext() &&
|
|
( ! static_cast<SwMultiPortion*>(pPor)->IsRuby() ||
|
|
static_cast<SwMultiPortion*>(pPor)->OnTop() ) )
|
|
{
|
|
sal_uInt16 nOffset;
|
|
// in grid mode we may only add the height of the
|
|
// ruby line if ruby line is on top
|
|
if ( bHasGrid &&
|
|
static_cast<SwMultiPortion*>(pPor)->IsRuby() &&
|
|
static_cast<SwMultiPortion*>(pPor)->OnTop() )
|
|
nOffset = nRubyHeight;
|
|
else
|
|
nOffset = GetLineHeight();
|
|
|
|
pOrig->Pos().AdjustY(nOffset );
|
|
Next();
|
|
}
|
|
|
|
const bool bSpaceChg = static_cast<SwMultiPortion*>(pPor)->
|
|
ChgSpaceAdd( m_pCurr, nSpaceAdd );
|
|
Point aOldPos = pOrig->Pos();
|
|
|
|
// Ok, for ruby portions in grid mode we have to
|
|
// temporarily set the inner line height to the
|
|
// outer line height because that value is needed
|
|
// for the adjustment inside the recursion
|
|
const sal_uInt16 nOldRubyHeight = m_pCurr->Height();
|
|
const sal_uInt16 nOldRubyRealHeight = m_pCurr->GetRealHeight();
|
|
const bool bChgHeight =
|
|
static_cast<SwMultiPortion*>(pPor)->IsRuby() && bHasGrid;
|
|
|
|
if ( bChgHeight )
|
|
{
|
|
m_pCurr->Height( pOldCurr->Height() - nRubyHeight );
|
|
m_pCurr->SetRealHeight( pOldCurr->GetRealHeight() -
|
|
nRubyHeight );
|
|
}
|
|
|
|
SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
|
|
if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
|
|
{
|
|
aLayoutModeModifier.Modify(
|
|
static_cast<SwBidiPortion*>(pPor)->GetLevel() % 2 );
|
|
}
|
|
|
|
GetCharRect_( pOrig, nOfst, pCMS );
|
|
|
|
if ( bChgHeight )
|
|
{
|
|
m_pCurr->Height( nOldRubyHeight );
|
|
m_pCurr->SetRealHeight( nOldRubyRealHeight );
|
|
}
|
|
|
|
// if we are still in the first row of
|
|
// our 2 line multiportion, we use the FirstMulti flag
|
|
// to indicate this
|
|
if ( static_cast<SwMultiPortion*>(pPor)->IsDouble() )
|
|
{
|
|
// the recursion may have damaged our font size
|
|
SetPropFont( nOldProp );
|
|
GetInfo().GetFont()->SetProportion( 100 );
|
|
|
|
if ( m_pCurr == &static_cast<SwMultiPortion*>(pPor)->GetRoot() )
|
|
{
|
|
GetInfo().SetFirstMulti( true );
|
|
|
|
// we want to treat a double line portion like a
|
|
// single line portion, if there is no text in
|
|
// the second line
|
|
if ( !m_pCurr->GetNext() ||
|
|
!m_pCurr->GetNext()->GetLen() )
|
|
GetInfo().SetMulti( false );
|
|
}
|
|
}
|
|
// ruby portions are treated like single line portions
|
|
else if( static_cast<SwMultiPortion*>(pPor)->IsRuby() ||
|
|
static_cast<SwMultiPortion*>(pPor)->IsBidi() )
|
|
GetInfo().SetMulti( false );
|
|
|
|
// calculate cursor values
|
|
if( static_cast<SwMultiPortion*>(pPor)->HasRotation() )
|
|
{
|
|
GetInfo().SetMulti( false );
|
|
tools::Long nTmp = pOrig->Width();
|
|
pOrig->Width( pOrig->Height() );
|
|
pOrig->Height( nTmp );
|
|
nTmp = pOrig->Left() - aOldPos.X();
|
|
|
|
// if we travel into our rotated portion from
|
|
// a line below, we have to take care, that the
|
|
// y coord in pOrig is less than line height:
|
|
if ( nTmp )
|
|
nTmp--;
|
|
|
|
pOrig->Pos().setX( nX + aOldPos.X() );
|
|
if( static_cast<SwMultiPortion*>(pPor)->IsRevers() )
|
|
pOrig->Pos().setY( aOldPos.Y() + nTmp );
|
|
else
|
|
pOrig->Pos().setY( aOldPos.Y()
|
|
+ pPor->Height() - nTmp - pOrig->Height() );
|
|
if ( pCMS && pCMS->m_bRealHeight )
|
|
{
|
|
pCMS->m_aRealHeight.setY( -pCMS->m_aRealHeight.Y() );
|
|
// result for rotated multi portion is not
|
|
// correct for reverse (270 degree) portions
|
|
if( static_cast<SwMultiPortion*>(pPor)->IsRevers() )
|
|
{
|
|
if ( SvxParaVertAlignItem::Align::Automatic ==
|
|
GetLineInfo().GetVertAlign() )
|
|
// if vertical alignment is set to auto,
|
|
// we switch from base line alignment
|
|
// to centered alignment
|
|
pCMS->m_aRealHeight.setX(
|
|
( pOrig->Width() +
|
|
pCMS->m_aRealHeight.Y() ) / 2 );
|
|
else
|
|
pCMS->m_aRealHeight.setX(
|
|
pOrig->Width() -
|
|
pCMS->m_aRealHeight.X() +
|
|
pCMS->m_aRealHeight.Y() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pOrig->Pos().AdjustY(aOldPos.Y() );
|
|
if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
|
|
{
|
|
const SwTwips nPorWidth = pPor->Width() +
|
|
pPor->CalcSpacing( nSpaceAdd, aInf );
|
|
const SwTwips nInsideOfst = pOrig->Pos().X();
|
|
pOrig->Pos().setX( nX + nPorWidth -
|
|
nInsideOfst - pOrig->Width() );
|
|
}
|
|
else
|
|
pOrig->Pos().AdjustX(nX );
|
|
|
|
if( static_cast<SwMultiPortion*>(pPor)->HasBrackets() )
|
|
pOrig->Pos().AdjustX(
|
|
static_cast<SwDoubleLinePortion*>(pPor)->PreWidth() );
|
|
}
|
|
|
|
if( bSpaceChg )
|
|
SwDoubleLinePortion::ResetSpaceAdd( m_pCurr );
|
|
|
|
m_pCurr = pOldCurr;
|
|
m_nStart = nOldStart;
|
|
m_nY = nOldY;
|
|
m_bPrev = false;
|
|
|
|
return;
|
|
}
|
|
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.SetMeasureLen(pPor->GetLen());
|
|
if (aInf.GetLen() < aInf.GetMeasureLen())
|
|
{
|
|
pPor->SetLen(aInf.GetMeasureLen());
|
|
aInf.SetLen(pPor->GetLen());
|
|
}
|
|
if( nX || !pPor->InNumberGrp() )
|
|
{
|
|
SeekAndChg( aInf );
|
|
const bool bOldOnWin = aInf.OnWin();
|
|
aInf.SetOnWin( false ); // no BULLETs!
|
|
SwTwips nTmp = nX;
|
|
aInf.SetKanaComp( pKanaComp );
|
|
aInf.SetKanaIdx( nKanaIdx );
|
|
nX += pPor->GetTextSize( aInf ).Width();
|
|
aInf.SetOnWin( bOldOnWin );
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
nX += pPor->CalcSpacing( nSpaceAdd, aInf );
|
|
if( bWidth )
|
|
{
|
|
pPor->SetLen(pPor->GetLen() + TextFrameIndex(1));
|
|
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 );
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
nTmp += pPor->CalcSpacing(nSpaceAdd, aInf);
|
|
pOrig->Width( nTmp - nX );
|
|
}
|
|
}
|
|
pPor->SetLen( nOldLen );
|
|
|
|
// Shift the cursor with the right border width
|
|
// Note: nX remains positive because GetTextSize() also include the width of the right border
|
|
if( aInf.GetIdx() < nOfst && nOfst < aInf.GetIdx() + pPor->GetLen() )
|
|
{
|
|
// Find the current drop portion part and use its right border
|
|
if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 )
|
|
{
|
|
SwDropPortion* pDrop = static_cast<SwDropPortion*>(pPor);
|
|
const SwDropPortionPart* pCurrPart = pDrop->GetPart();
|
|
TextFrameIndex nSumLength(0);
|
|
while( pCurrPart && (nSumLength += pCurrPart->GetLen()) < nOfst - aInf.GetIdx() )
|
|
{
|
|
pCurrPart = pCurrPart->GetFollow();
|
|
}
|
|
if( pCurrPart && nSumLength != nOfst - aInf.GetIdx() &&
|
|
pCurrPart->GetFont().GetRightBorder() && !pCurrPart->GetJoinBorderWithNext() )
|
|
{
|
|
nX -= pCurrPart->GetFont().GetRightBorderSpace();
|
|
}
|
|
}
|
|
else if( GetInfo().GetFont()->GetRightBorder() && !pPor->GetJoinBorderWithNext())
|
|
{
|
|
nX -= GetInfo().GetFont()->GetRightBorderSpace();
|
|
}
|
|
}
|
|
}
|
|
bWidth = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pPor )
|
|
{
|
|
OSL_ENSURE( !pPor->InNumberGrp() || bInsideFirstField, "Number surprise" );
|
|
bool bEmptyField = false;
|
|
if( pPor->InFieldGrp() && pPor->GetLen() )
|
|
{
|
|
SwFieldPortion *pTmp = static_cast<SwFieldPortion*>(pPor);
|
|
while( pTmp->HasFollow() && pTmp->GetExp().isEmpty() )
|
|
{
|
|
sal_uInt16 nAddX = pTmp->Width();
|
|
SwLinePortion *pNext = pTmp->GetNextPortion();
|
|
while( pNext && !pNext->InFieldGrp() )
|
|
{
|
|
OSL_ENSURE( !pNext->GetLen(), "Where's my field follow?" );
|
|
nAddX += pNext->Width();
|
|
pNext = pNext->GetNextPortion();
|
|
}
|
|
if( !pNext )
|
|
break;
|
|
pTmp = static_cast<SwFieldPortion*>(pNext);
|
|
nPorHeight = pTmp->Height();
|
|
nPorAscent = pTmp->GetAscent();
|
|
nX += nAddX;
|
|
bEmptyField = true;
|
|
}
|
|
}
|
|
// 8513: Fields in justified text, skipped
|
|
while( pPor && !pPor->GetLen() && ! bInsideFirstField &&
|
|
( pPor->IsFlyPortion() || pPor->IsKernPortion() ||
|
|
pPor->IsBlankPortion() || pPor->InTabGrp() ||
|
|
( !bEmptyField && pPor->InFieldGrp() ) ) )
|
|
{
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
nX += pPor->PrtWidth() +
|
|
pPor->CalcSpacing( nSpaceAdd, aInf );
|
|
else
|
|
{
|
|
if( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() )
|
|
{
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
|
|
++nKanaIdx;
|
|
}
|
|
if ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() &&
|
|
!pPor->GetNextPortion()->IsMarginPortion() ) )
|
|
nX += pPor->PrtWidth();
|
|
}
|
|
if( pPor->IsMultiPortion() &&
|
|
static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
|
|
{
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
|
|
++nKanaIdx;
|
|
}
|
|
if( !pPor->IsFlyPortion() )
|
|
{
|
|
nPorHeight = pPor->Height();
|
|
nPorAscent = pPor->GetAscent();
|
|
}
|
|
pPor = pPor->GetNextPortion();
|
|
}
|
|
|
|
if( aInf.GetIdx() == nOfst && pPor && pPor->InHyphGrp() &&
|
|
pPor->GetNextPortion() && pPor->GetNextPortion()->InFixGrp() )
|
|
{
|
|
// All special portions have to be skipped
|
|
// Taking the German word "zusammen" as example: zu-[FLY]sammen, 'u' == 19, 's' == 20; Right()
|
|
// Without the adjustment we end up in front of '-', with the
|
|
// adjustment in front of the 's'.
|
|
while( pPor && !pPor->GetLen() )
|
|
{
|
|
nX += pPor->Width();
|
|
if( !pPor->IsMarginPortion() )
|
|
{
|
|
nPorHeight = pPor->Height();
|
|
nPorAscent = pPor->GetAscent();
|
|
}
|
|
pPor = pPor->GetNextPortion();
|
|
}
|
|
}
|
|
if( pPor && pCMS )
|
|
{
|
|
if( pCMS->m_bFieldInfo && pPor->InFieldGrp() && pPor->Width() )
|
|
pOrig->Width( pPor->Width() );
|
|
if( pPor->IsDropPortion() )
|
|
{
|
|
nPorAscent = static_cast<SwDropPortion*>(pPor)->GetDropHeight();
|
|
// The drop height is only calculated, if we have more than
|
|
// one line. Otherwise it is 0.
|
|
if ( ! nPorAscent)
|
|
nPorAscent = pPor->Height();
|
|
nPorHeight = nPorAscent;
|
|
pOrig->Height( nPorHeight +
|
|
static_cast<SwDropPortion*>(pPor)->GetDropDescent() );
|
|
if( nTmpHeight < pOrig->Height() )
|
|
{
|
|
nTmpAscent = nPorAscent;
|
|
nTmpHeight = sal_uInt16( pOrig->Height() );
|
|
}
|
|
}
|
|
if( bWidth && pPor->PrtWidth() && pPor->GetLen() &&
|
|
aInf.GetIdx() == nOfst )
|
|
{
|
|
if( !pPor->IsFlyPortion() && pPor->Height() &&
|
|
pPor->GetAscent() )
|
|
{
|
|
nPorHeight = pPor->Height();
|
|
nPorAscent = pPor->GetAscent();
|
|
}
|
|
SwTwips nTmp;
|
|
if (TextFrameIndex(2) > pPor->GetLen())
|
|
{
|
|
nTmp = pPor->Width();
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
nTmp += pPor->CalcSpacing( nSpaceAdd, aInf );
|
|
}
|
|
else
|
|
{
|
|
const bool bOldOnWin = aInf.OnWin();
|
|
TextFrameIndex const nOldLen = pPor->GetLen();
|
|
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 );
|
|
aInf.SetKanaIdx( nKanaIdx );
|
|
nTmp = pPor->GetTextSize( aInf ).Width();
|
|
aInf.SetOnWin( bOldOnWin );
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
nTmp += pPor->CalcSpacing( nSpaceAdd, aInf );
|
|
pPor->SetLen( nOldLen );
|
|
}
|
|
pOrig->Width( nTmp );
|
|
}
|
|
|
|
// travel inside field portion?
|
|
if ( pCMS->m_pSpecialPos )
|
|
{
|
|
// apply attributes to font
|
|
Seek( nOfst );
|
|
lcl_GetCharRectInsideField( aInf, *pOrig, *pCMS, *pPor );
|
|
}
|
|
}
|
|
}
|
|
|
|
// special case: We are at the beginning of a BidiPortion or
|
|
// directly behind a BidiPortion
|
|
if ( pCMS &&
|
|
( pLastBidiPor ||
|
|
( pPor &&
|
|
pPor->IsMultiPortion() &&
|
|
static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ) )
|
|
{
|
|
// we determine if the cursor has to blink before or behind
|
|
// the bidi portion
|
|
if ( pLastBidiPor )
|
|
{
|
|
const sal_uInt8 nPortionLevel = pLastBidiPor->GetLevel();
|
|
|
|
if ( pCMS->m_nCursorBidiLevel >= nPortionLevel )
|
|
{
|
|
// we came from inside the bidi portion, we want to blink
|
|
// behind the portion
|
|
pOrig->Pos().AdjustX( -nLastBidiPorWidth );
|
|
|
|
// Again, there is a special case: logically behind
|
|
// the portion can actually mean that the cursor is inside
|
|
// the portion. This can happen is the last portion
|
|
// inside the bidi portion is a nested bidi portion
|
|
SwLineLayout& rLineLayout =
|
|
static_cast<SwMultiPortion*>(pLastBidiPor)->GetRoot();
|
|
|
|
const SwLinePortion *pLast = rLineLayout.FindLastPortion();
|
|
if ( pLast->IsMultiPortion() )
|
|
{
|
|
OSL_ENSURE( static_cast<const SwMultiPortion*>(pLast)->IsBidi(),
|
|
"Non-BidiPortion inside BidiPortion" );
|
|
TextFrameIndex const nIdx = aInf.GetIdx();
|
|
// correct the index before using CalcSpacing.
|
|
aInf.SetIdx(nLastBidiIdx);
|
|
pOrig->Pos().AdjustX(pLast->Width() +
|
|
pLast->CalcSpacing( nSpaceAdd, aInf ) );
|
|
aInf.SetIdx(nIdx);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const sal_uInt8 nPortionLevel = static_cast<SwBidiPortion*>(pPor)->GetLevel();
|
|
|
|
if ( pCMS->m_nCursorBidiLevel >= nPortionLevel )
|
|
{
|
|
// we came from inside the bidi portion, we want to blink
|
|
// behind the portion
|
|
pOrig->Pos().AdjustX(pPor->Width() +
|
|
pPor->CalcSpacing( nSpaceAdd, aInf ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
pOrig->Pos().AdjustX(nX );
|
|
|
|
if ( pCMS && pCMS->m_bRealHeight )
|
|
{
|
|
nTmpAscent = AdjustBaseLine( *m_pCurr, nullptr, nPorHeight, nPorAscent );
|
|
if ( nTmpAscent > nPorAscent )
|
|
pCMS->m_aRealHeight.setX( nTmpAscent - nPorAscent );
|
|
else
|
|
pCMS->m_aRealHeight.setX( 0 );
|
|
OSL_ENSURE( nPorHeight, "GetCharRect: Missing Portion-Height" );
|
|
if ( nTmpHeight > nPorHeight )
|
|
pCMS->m_aRealHeight.setY( nPorHeight );
|
|
else
|
|
pCMS->m_aRealHeight.setY( nTmpHeight );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst,
|
|
SwCursorMoveState* pCMS, const tools::Long nMax )
|
|
{
|
|
CharCursorToLine(nOfst);
|
|
|
|
// Indicates that a position inside a special portion (field, number portion)
|
|
// is requested.
|
|
const bool bSpecialPos = pCMS && pCMS->m_pSpecialPos;
|
|
TextFrameIndex nFindOfst = nOfst;
|
|
|
|
if ( bSpecialPos )
|
|
{
|
|
const SwSPExtendRange nExtendRange = pCMS->m_pSpecialPos->nExtendRange;
|
|
|
|
OSL_ENSURE( ! pCMS->m_pSpecialPos->nLineOfst || SwSPExtendRange::BEFORE != nExtendRange,
|
|
"LineOffset AND Number Portion?" );
|
|
|
|
// portions which are behind the string
|
|
if ( SwSPExtendRange::BEHIND == nExtendRange )
|
|
++nFindOfst;
|
|
|
|
// skip lines for fields which cover more than one line
|
|
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 );
|
|
|
|
pOrig->Pos().AdjustX(aCharPos.X() );
|
|
pOrig->Pos().AdjustY(aCharPos.Y() );
|
|
|
|
if( pCMS && pCMS->m_b2Lines && pCMS->m_p2Lines )
|
|
{
|
|
pCMS->m_p2Lines->aLine.Pos().AdjustX(aCharPos.X() );
|
|
pCMS->m_p2Lines->aLine.Pos().AdjustY(aCharPos.Y() );
|
|
pCMS->m_p2Lines->aPortion.Pos().AdjustX(aCharPos.X() );
|
|
pCMS->m_p2Lines->aPortion.Pos().AdjustY(aCharPos.Y() );
|
|
}
|
|
|
|
if( nMax )
|
|
{
|
|
if( pOrig->Top() + pOrig->Height() > nMax )
|
|
{
|
|
if( pOrig->Top() > nMax )
|
|
pOrig->Top( nMax );
|
|
pOrig->Height( nMax - pOrig->Top() );
|
|
}
|
|
if ( pCMS && pCMS->m_bRealHeight && pCMS->m_aRealHeight.Y() >= 0 )
|
|
{
|
|
tools::Long nTmp = pCMS->m_aRealHeight.X() + pOrig->Top();
|
|
if( nTmp >= nMax )
|
|
{
|
|
pCMS->m_aRealHeight.setX( nMax - pOrig->Top() );
|
|
pCMS->m_aRealHeight.setY( 0 );
|
|
}
|
|
else if( nTmp + pCMS->m_aRealHeight.Y() > nMax )
|
|
pCMS->m_aRealHeight.setY( nMax - nTmp );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if SwTextCursor::GetModelPositionForViewPoint() should consider the next portion when calculating the
|
|
* doc model position from a Point.
|
|
*/
|
|
static bool ConsiderNextPortionForCursorOffset(const SwLinePortion* pPor, SwTwips nWidth30, sal_uInt16 nX)
|
|
{
|
|
if (!pPor->GetNextPortion() || pPor->IsBreakPortion())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// tdf#138592: consider all following zero-width text portions of current text portion,
|
|
// like combining characters.
|
|
if (nWidth30 == nX && pPor->IsTextPortion() && pPor->GetNextPortion()->IsTextPortion()
|
|
&& pPor->GetNextPortion()->Width() == 0)
|
|
return true;
|
|
|
|
// If we're past the target position, stop the iteration in general.
|
|
// Exception: don't stop the iteration between as-char fly portions and their comments.
|
|
if (nWidth30 >= nX && (!pPor->IsFlyCntPortion() || !pPor->GetNextPortion()->IsPostItsPortion()))
|
|
{
|
|
// Normally returns false.
|
|
|
|
// Another exception: If the cursor is at the very end of the portion, and the next portion is a comment,
|
|
// then place the cursor after the zero-width comment. This is primarily to benefit the very end of a line.
|
|
return nWidth30 == nX && pPor->GetNextPortion()->IsPostItsPortion();
|
|
}
|
|
|
|
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
|
|
{
|
|
// If necessary, as catch up, do the adjustment
|
|
GetAdjusted();
|
|
|
|
const OUString &rText = GetInfo().GetText();
|
|
TextFrameIndex nOffset(0);
|
|
|
|
// x is the horizontal offset within the line.
|
|
SwTwips x = rPoint.X();
|
|
const SwTwips nLeftMargin = GetLineStart();
|
|
SwTwips nRightMargin = GetLineEnd() +
|
|
( GetCurr()->IsHanging() ? GetCurr()->GetHangingMargin() : 0 );
|
|
if( nRightMargin == nLeftMargin )
|
|
nRightMargin += 30;
|
|
|
|
const bool bLeftOver = x < nLeftMargin;
|
|
if( bLeftOver )
|
|
x = nLeftMargin;
|
|
const bool bRightOver = x > nRightMargin;
|
|
const bool bRightAllowed = pCMS && ( pCMS->m_eState == CursorMoveState::NONE );
|
|
|
|
// Until here everything in document coordinates.
|
|
x -= nLeftMargin;
|
|
|
|
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 bLastHyph = false;
|
|
|
|
std::deque<sal_uInt16> *pKanaComp = m_pCurr->GetpKanaComp();
|
|
TextFrameIndex const nOldIdx = GetInfo().GetIdx();
|
|
sal_uInt16 nSpaceIdx = 0;
|
|
size_t nKanaIdx = 0;
|
|
tools::Long nSpaceAdd = m_pCurr->IsSpaceAdd() ? m_pCurr->GetLLSpaceAdd( 0 ) : 0;
|
|
short nKanaComp = pKanaComp ? (*pKanaComp)[0] : 0;
|
|
|
|
// nWidth is the width of the line, or the width of
|
|
// the paragraph with the font change, in which nX is situated.
|
|
// 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 += pPor->CalcSpacing( nSpaceAdd, GetInfo() );
|
|
}
|
|
if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) ||
|
|
( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
|
|
)
|
|
{
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if( pKanaComp )
|
|
{
|
|
if ( nKanaIdx + 1 < pKanaComp->size() )
|
|
nKanaComp = (*pKanaComp)[++nKanaIdx];
|
|
else
|
|
nKanaComp = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
SwTwips nWidth30;
|
|
if ( pPor->IsPostItsPortion() )
|
|
nWidth30 = 0;
|
|
else
|
|
nWidth30 = ! nWidth && pPor->GetLen() && pPor->InToxRefOrFieldGrp() ?
|
|
30 :
|
|
nWidth;
|
|
|
|
while (ConsiderNextPortionForCursorOffset(pPor, nWidth30, nX))
|
|
{
|
|
nX -= nWidth;
|
|
nCurrStart += pPor->GetLen();
|
|
pPor = pPor->GetNextPortion();
|
|
nWidth = pPor->Width();
|
|
if ( m_pCurr->IsSpaceAdd() || pKanaComp )
|
|
{
|
|
if ( pPor->InSpaceGrp() && nSpaceAdd )
|
|
{
|
|
const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart );
|
|
nWidth += pPor->CalcSpacing( nSpaceAdd, GetInfo() );
|
|
}
|
|
|
|
if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) ||
|
|
( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
|
|
)
|
|
{
|
|
if ( m_pCurr->IsSpaceAdd() )
|
|
{
|
|
if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
|
|
nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
|
|
else
|
|
nSpaceAdd = 0;
|
|
}
|
|
|
|
if ( pKanaComp )
|
|
{
|
|
if( nKanaIdx + 1 < pKanaComp->size() )
|
|
nKanaComp = (*pKanaComp)[++nKanaIdx];
|
|
else
|
|
nKanaComp = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pPor->IsPostItsPortion() )
|
|
nWidth30 = 0;
|
|
else
|
|
nWidth30 = ! nWidth && pPor->GetLen() && pPor->InToxRefOrFieldGrp() ?
|
|
30 :
|
|
nWidth;
|
|
if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() )
|
|
bLastHyph = pPor->InHyphGrp();
|
|
}
|
|
|
|
const bool bLastPortion = (nullptr == pPor->GetNextPortion());
|
|
|
|
if( nX==nWidth )
|
|
{
|
|
SwLinePortion *pNextPor = pPor->GetNextPortion();
|
|
while( pNextPor && pNextPor->InFieldGrp() && !pNextPor->Width() )
|
|
{
|
|
nCurrStart += pPor->GetLen();
|
|
pPor = pNextPor;
|
|
if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() )
|
|
bLastHyph = pPor->InHyphGrp();
|
|
pNextPor = pPor->GetNextPortion();
|
|
}
|
|
}
|
|
|
|
const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nOldIdx );
|
|
|
|
TextFrameIndex nLength = pPor->GetLen();
|
|
|
|
const bool bFieldInfo = pCMS && pCMS->m_bFieldInfo;
|
|
|
|
if( bFieldInfo && ( nWidth30 < nX || bRightOver || bLeftOver ||
|
|
( pPor->InNumberGrp() && !pPor->IsFootnoteNumPortion() ) ||
|
|
( pPor->IsMarginPortion() && nWidth > nX + 30 ) ) )
|
|
pCMS->m_bPosCorr = true;
|
|
|
|
// #i27615#
|
|
if (pCMS && pCMS->m_bInFrontOfLabel)
|
|
{
|
|
if (2 * nX >= nWidth || !pPor->InNumberGrp() || pPor->IsFootnoteNumPortion())
|
|
pCMS->m_bInFrontOfLabel = false;
|
|
}
|
|
|
|
// 7684: We are exactly ended up at their HyphPortion. It is our task to
|
|
// provide, that we end up in the String.
|
|
// 7993: If length = 0, then we must exit...
|
|
if( !nLength )
|
|
{
|
|
if( pCMS )
|
|
{
|
|
if( pPor->IsFlyPortion() && bFieldInfo )
|
|
pCMS->m_bPosCorr = true;
|
|
|
|
if (!bRightOver && nX)
|
|
{
|
|
if( pPor->IsFootnoteNumPortion())
|
|
pCMS->m_bFootnoteNoInfo = true;
|
|
else if (pPor->InNumberGrp() ) // #i23726#
|
|
{
|
|
pCMS->m_nInNumPortionOffset = nX;
|
|
pCMS->m_bInNumPortion = true;
|
|
}
|
|
}
|
|
}
|
|
if( !nCurrStart )
|
|
return TextFrameIndex(0);
|
|
|
|
// 7849, 7816: pPor->GetHyphPortion is mandatory!
|
|
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
|
|
nCurrStart < TextFrameIndex(rText.getLength())))
|
|
--nCurrStart;
|
|
else if( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->IsFollow()
|
|
&& nWidth > nX )
|
|
{
|
|
if( bFieldInfo )
|
|
--nCurrStart;
|
|
else
|
|
{
|
|
sal_uInt16 nHeight = pPor->Height();
|
|
if ( !nHeight || nHeight > nWidth )
|
|
nHeight = nWidth;
|
|
if( bChgNode && nWidth - nHeight/2 > nX )
|
|
--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
|
|
if (!(bChgNode && pPos && pPor->IsFlyCntPortion())
|
|
// if we want to get the position inside the field, we should not return
|
|
&& (!pCMS || !pCMS->m_pSpecialPos))
|
|
{
|
|
if ( pPor->InFieldGrp() ||
|
|
( pPor->IsMultiPortion() &&
|
|
static_cast<SwMultiPortion*>(pPor)->IsBidi() ) )
|
|
{
|
|
sal_uInt16 nHeight = 0;
|
|
if( !bFieldInfo )
|
|
{
|
|
nHeight = pPor->Height();
|
|
if ( !nHeight || nHeight > nWidth )
|
|
nHeight = nWidth;
|
|
}
|
|
|
|
if( nWidth - nHeight/2 <= nX &&
|
|
( ! pPor->InFieldGrp() ||
|
|
!static_cast<SwFieldPortion*>(pPor)->HasFollow() ) )
|
|
{
|
|
if (pPor->InFieldGrp())
|
|
{
|
|
nCurrStart += static_cast<SwFieldPortion*>(pPor)->GetFieldLen();
|
|
}
|
|
else
|
|
{
|
|
++nCurrStart;
|
|
}
|
|
}
|
|
}
|
|
else if ( ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() &&
|
|
!pPor->GetNextPortion()->IsMarginPortion() &&
|
|
!pPor->GetNextPortion()->IsHolePortion() ) )
|
|
&& ( nWidth/2 < nX ) &&
|
|
( !bFieldInfo ||
|
|
( pPor->GetNextPortion() &&
|
|
pPor->GetNextPortion()->IsPostItsPortion() ) )
|
|
&& ( bRightAllowed || !bLastHyph ))
|
|
++nCurrStart;
|
|
|
|
return nCurrStart;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pPor->IsPostItsPortion() || pPor->InToxRefGrp())
|
|
{
|
|
SwPostItsPortion* pPostItsPortion = pPor->IsPostItsPortion() ? dynamic_cast<SwPostItsPortion*>(pPor) : nullptr;
|
|
if (pPostItsPortion)
|
|
{
|
|
if (!pPostItsPortion->IsScript()) // tdf#141079
|
|
{
|
|
// Offset would be nCurrStart + nLength below, do the same for post-it portions.
|
|
nCurrStart += pPor->GetLen();
|
|
}
|
|
}
|
|
return nCurrStart;
|
|
}
|
|
if ( pPor->InFieldGrp() )
|
|
{
|
|
if( bRightOver && !static_cast<SwFieldPortion*>(pPor)->HasFollow() )
|
|
{
|
|
nCurrStart += static_cast<SwFieldPortion*>(pPor)->GetFieldLen();
|
|
}
|
|
return nCurrStart;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip space at the end of the line
|
|
if( bLastPortion && (m_pCurr->GetNext() || m_pFrame->GetFollow() )
|
|
&& sal_Int32(nLength) != 0
|
|
&& rText[sal_Int32(nCurrStart + nLength) - 1] == ' ')
|
|
{
|
|
--nLength;
|
|
}
|
|
|
|
if( nWidth > nX ||
|
|
( nWidth == nX && pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsDouble() ) )
|
|
{
|
|
if( pPor->IsMultiPortion() )
|
|
{
|
|
// In a multi-portion we use GetModelPositionForViewPoint()-function recursively
|
|
SwTwips nTmpY = rPoint.Y() - m_pCurr->GetAscent() + pPor->GetAscent();
|
|
// if we are in the first line of a double line portion, we have
|
|
// to add a value to nTmpY for not staying in this line
|
|
// we also want to skip the first line, if we are inside ruby
|
|
if ( ( static_cast<SwTextSizeInfo*>(m_pInf)->IsMulti() &&
|
|
static_cast<SwTextSizeInfo*>(m_pInf)->IsFirstMulti() ) ||
|
|
( static_cast<SwMultiPortion*>(pPor)->IsRuby() &&
|
|
static_cast<SwMultiPortion*>(pPor)->OnTop() ) )
|
|
nTmpY += static_cast<SwMultiPortion*>(pPor)->Height();
|
|
|
|
// Important for cursor traveling in ruby portions:
|
|
// We have to set nTmpY to 0 in order to stay in the first row
|
|
// if the phonetic line is the second row
|
|
if ( static_cast<SwMultiPortion*>(pPor)->IsRuby() &&
|
|
! static_cast<SwMultiPortion*>(pPor)->OnTop() )
|
|
nTmpY = 0;
|
|
|
|
SwTextCursorSave aSave( const_cast<SwTextCursor*>(this), static_cast<SwMultiPortion*>(pPor),
|
|
nTmpY, nX, nCurrStart, nSpaceAdd );
|
|
|
|
SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
|
|
if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
|
|
{
|
|
const sal_uInt8 nBidiLevel = static_cast<SwBidiPortion*>(pPor)->GetLevel();
|
|
aLayoutModeModifier.Modify( nBidiLevel % 2 );
|
|
}
|
|
|
|
if( static_cast<SwMultiPortion*>(pPor)->HasRotation() )
|
|
{
|
|
nTmpY -= m_nY;
|
|
if( !static_cast<SwMultiPortion*>(pPor)->IsRevers() )
|
|
nTmpY = pPor->Height() - nTmpY;
|
|
if( nTmpY < 0 )
|
|
nTmpY = 0;
|
|
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 -= nPreWidth;
|
|
else
|
|
nX = 0;
|
|
}
|
|
|
|
return GetModelPositionForViewPoint( pPos, Point( GetLineStart() + nX, rPoint.Y() ),
|
|
bChgNode, pCMS );
|
|
}
|
|
if( pPor->InTextGrp() || pPor->IsHolePortion() )
|
|
{
|
|
sal_uInt8 nOldProp;
|
|
if( GetPropFont() )
|
|
{
|
|
const_cast<SwFont*>(GetFnt())->SetProportion( GetPropFont() );
|
|
nOldProp = GetFnt()->GetPropr();
|
|
}
|
|
else
|
|
nOldProp = 0;
|
|
{
|
|
SwTextSizeInfo aSizeInf( GetInfo(), &rText, nCurrStart );
|
|
const_cast<SwTextCursor*>(this)->SeekAndChg( aSizeInf );
|
|
SwTextSlot aDiffText( &aSizeInf, pPor, false, false );
|
|
SwFontSave aSave( aSizeInf, pPor->IsDropPortion() ?
|
|
static_cast<SwDropPortion*>(pPor)->GetFnt() : nullptr );
|
|
|
|
SwParaPortion* pPara = const_cast<SwParaPortion*>(GetInfo().GetParaPortion());
|
|
OSL_ENSURE( pPara, "No paragraph!" );
|
|
|
|
// protect against bugs elsewhere
|
|
SAL_WARN_IF( aSizeInf.GetIdx().get() + pPor->GetLen().get() > aSizeInf.GetText().getLength(), "sw", "portion and text are out of sync" );
|
|
TextFrameIndex nSafeLen( std::min(pPor->GetLen().get(), aSizeInf.GetText().getLength() - aSizeInf.GetIdx().get()) );
|
|
|
|
SwDrawTextInfo aDrawInf(aSizeInf.GetVsh(), *aSizeInf.GetOut(),
|
|
&pPara->GetScriptInfo(), aSizeInf.GetText(),
|
|
aSizeInf.GetIdx(), 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 )
|
|
{
|
|
SwDropPortion* pDrop = static_cast<SwDropPortion*>(pPor);
|
|
const SwDropPortionPart* pCurrPart = pDrop->GetPart();
|
|
sal_uInt16 nSumWidth = 0;
|
|
sal_uInt16 nSumBorderWidth = 0;
|
|
// Shift offset with the right and left border of previous parts and left border of actual one
|
|
while (pCurrPart && nSumWidth <= nX - sal_Int32(nCurrStart))
|
|
{
|
|
nSumWidth += pCurrPart->GetWidth();
|
|
if( pCurrPart->GetFont().GetLeftBorder() && !pCurrPart->GetJoinBorderWithPrev() )
|
|
{
|
|
nSumBorderWidth += pCurrPart->GetFont().GetLeftBorderSpace();
|
|
}
|
|
if (nSumWidth <= nX - sal_Int32(nCurrStart) && pCurrPart->GetFont().GetRightBorder() &&
|
|
!pCurrPart->GetJoinBorderWithNext() )
|
|
{
|
|
nSumBorderWidth += pCurrPart->GetFont().GetRightBorderSpace();
|
|
}
|
|
pCurrPart = pCurrPart->GetFollow();
|
|
}
|
|
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(static_cast<SwTwips>(0), nX - GetInfo().GetFont()->GetLeftBorderSpace());
|
|
}
|
|
|
|
aDrawInf.SetOffset( nX );
|
|
|
|
if ( nSpaceAdd )
|
|
{
|
|
TextFrameIndex nCharCnt(0);
|
|
// #i41860# Thai justified alignment needs some
|
|
// additional information:
|
|
aDrawInf.SetNumberOfBlanks( pPor->InTextGrp() ?
|
|
static_cast<const SwTextPortion*>(pPor)->GetSpaceCnt( aSizeInf, nCharCnt ) :
|
|
TextFrameIndex(0) );
|
|
}
|
|
|
|
if ( pPor->InFieldGrp() && pCMS && pCMS->m_pSpecialPos )
|
|
aDrawInf.SetLen( TextFrameIndex(COMPLETE_STRING) );
|
|
|
|
aDrawInf.SetSpace( nSpaceAdd );
|
|
aDrawInf.SetFont( aSizeInf.GetFont() );
|
|
aDrawInf.SetFrame( m_pFrame );
|
|
aDrawInf.SetSnapToGrid( aSizeInf.SnapToGrid() );
|
|
aDrawInf.SetPosMatchesBounds( pCMS && pCMS->m_bPosMatchesBounds );
|
|
|
|
if ( SwFontScript::CJK == aSizeInf.GetFont()->GetActual() &&
|
|
pPara->GetScriptInfo().CountCompChg() &&
|
|
! pPor->InFieldGrp() )
|
|
aDrawInf.SetKanaComp( nKanaComp );
|
|
|
|
nLength = aSizeInf.GetFont()->GetModelPositionForViewPoint_( aDrawInf );
|
|
|
|
// get position inside field portion?
|
|
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);
|
|
}
|
|
|
|
// set cursor bidi level
|
|
if ( pCMS )
|
|
pCMS->m_nCursorBidiLevel =
|
|
aDrawInf.GetCursorBidiLevel();
|
|
}
|
|
if( nOldProp )
|
|
const_cast<SwFont*>(GetFnt())->SetProportion( nOldProp );
|
|
}
|
|
else
|
|
{
|
|
sw::FlyContentPortion* pFlyPor(nullptr);
|
|
if(bChgNode && pPos && (pFlyPor = dynamic_cast<sw::FlyContentPortion*>(pPor)))
|
|
{
|
|
// JP 24.11.94: if the Position is not in Fly, then
|
|
// we many not return with COMPLETE_STRING as value!
|
|
// (BugId: 9692 + Change in feshview)
|
|
SwFlyInContentFrame *pTmp = pFlyPor->GetFlyFrame();
|
|
SwFrame* pLower = pTmp->GetLower();
|
|
// Allow non-text-frames to get SwGrfNode for as-char anchored images into pPos
|
|
// instead of the closest SwTextNode, to be consistent with at-char behavior.
|
|
bool bChgNodeInner
|
|
= pLower
|
|
&& (pLower->IsTextFrame() || pLower->IsLayoutFrame()
|
|
|| (pLower->IsNoTextFrame()
|
|
&& (!pCMS || pCMS->m_eState != CursorMoveState::SetOnlyText)));
|
|
Point aTmpPoint( rPoint );
|
|
|
|
if ( m_pFrame->IsRightToLeft() )
|
|
m_pFrame->SwitchLTRtoRTL( aTmpPoint );
|
|
|
|
if ( m_pFrame->IsVertical() )
|
|
m_pFrame->SwitchHorizontalToVertical( aTmpPoint );
|
|
|
|
if( bChgNodeInner && pTmp->getFrameArea().Contains( aTmpPoint ) &&
|
|
!( pTmp->IsProtected() ) )
|
|
{
|
|
pFlyPor->GetFlyCursorOfst(aTmpPoint, *pPos, pCMS);
|
|
// After a change of the frame, our font must be still
|
|
// available for/in the OutputDevice.
|
|
// For comparison: Paint and new SwFlyCntPortion !
|
|
static_cast<SwTextSizeInfo*>(m_pInf)->SelectFont();
|
|
|
|
// 6776: The pIter->GetModelPositionForViewPoint is returning here
|
|
// from a nesting with COMPLETE_STRING.
|
|
return TextFrameIndex(COMPLETE_STRING);
|
|
}
|
|
}
|
|
else
|
|
nLength = pPor->GetModelPositionForViewPoint( nX );
|
|
}
|
|
}
|
|
nOffset = nCurrStart + nLength;
|
|
|
|
// 7684: We end up in front of the HyphPortion. We must assure
|
|
// that we end up in the string.
|
|
// If we are at end of line in front of FlyFrames, we must proceed the same way.
|
|
if( nOffset && pPor->GetLen() == nLength && pPor->GetNextPortion() &&
|
|
!pPor->GetNextPortion()->GetLen() && pPor->GetNextPortion()->InHyphGrp() )
|
|
--nOffset;
|
|
|
|
return nOffset;
|
|
}
|
|
|
|
/** Looks for text portions which are inside the given rectangle
|
|
|
|
For a rectangular text selection every text portions which is inside the given
|
|
rectangle has to be put into the SwSelectionList as SwPaM
|
|
From these SwPaM the SwCursors will be created.
|
|
|
|
@param rSelList
|
|
The container for the overlapped text portions
|
|
|
|
@param rRect
|
|
A rectangle in document coordinates, text inside this rectangle has to be
|
|
selected.
|
|
|
|
@return [ true, false ]
|
|
true if any overlapping text portion has been found and put into list
|
|
false if no portion overlaps, the list has been unchanged
|
|
*/
|
|
bool SwTextFrame::FillSelection( SwSelectionList& rSelList, const SwRect& rRect ) const
|
|
{
|
|
bool bRet = false;
|
|
// GetPaintArea() instead getFrameArea() for negative indents
|
|
SwRect aTmpFrame( GetPaintArea() );
|
|
if( !rRect.Overlaps( aTmpFrame ) )
|
|
return false;
|
|
if( rSelList.checkContext( this ) )
|
|
{
|
|
SwRect aRect( aTmpFrame );
|
|
aRect.Intersection( rRect );
|
|
SwPosition aPosL( MapViewToModelPos(TextFrameIndex(0)) );
|
|
if( IsEmpty() )
|
|
{
|
|
SwPaM *pPam = new SwPaM( aPosL, aPosL );
|
|
rSelList.insertPaM( pPam );
|
|
}
|
|
else if( aRect.HasArea() )
|
|
{
|
|
SwPosition aOld(aPosL.GetNodes().GetEndOfContent());
|
|
SwPosition aPosR( aPosL );
|
|
Point aPoint;
|
|
SwTextInfo aInf( const_cast<SwTextFrame*>(this) );
|
|
SwTextIter aLine( const_cast<SwTextFrame*>(this), &aInf );
|
|
// We have to care for top-to-bottom layout, where right becomes top etc.
|
|
SwRectFnSet aRectFnSet(this);
|
|
SwTwips nTop = aRectFnSet.GetTop(aRect);
|
|
SwTwips nBottom = aRectFnSet.GetBottom(aRect);
|
|
SwTwips nLeft = aRectFnSet.GetLeft(aRect);
|
|
SwTwips nRight = aRectFnSet.GetRight(aRect);
|
|
SwTwips nY = aLine.Y(); // Top position of the first line
|
|
SwTwips nLastY = nY;
|
|
while( nY < nTop && aLine.Next() ) // line above rectangle
|
|
{
|
|
nLastY = nY;
|
|
nY = aLine.Y();
|
|
}
|
|
bool bLastLine = false;
|
|
if( nY < nTop && !aLine.GetNext() )
|
|
{
|
|
bLastLine = true;
|
|
nY += aLine.GetLineHeight();
|
|
}
|
|
do // check the lines for overlapping
|
|
{
|
|
if( nLastY < nTop ) // if the last line was above rectangle
|
|
nLastY = nTop;
|
|
if( nY > nBottom ) // if the current line leaves the rectangle
|
|
nY = nBottom;
|
|
if( nY >= nLastY ) // gotcha: overlapping
|
|
{
|
|
nLastY += nY;
|
|
nLastY /= 2;
|
|
if( aRectFnSet.IsVert() )
|
|
{
|
|
aPoint.setX( nLastY );
|
|
aPoint.setY( nLeft );
|
|
}
|
|
else
|
|
{
|
|
aPoint.setX( nLeft );
|
|
aPoint.setY( nLastY );
|
|
}
|
|
// Looking for the position of the left border of the rectangle
|
|
// in this text line
|
|
SwCursorMoveState aState( CursorMoveState::UpDown );
|
|
if( GetModelPositionForViewPoint( &aPosL, aPoint, &aState ) )
|
|
{
|
|
if( aRectFnSet.IsVert() )
|
|
{
|
|
aPoint.setX( nLastY );
|
|
aPoint.setY( nRight );
|
|
}
|
|
else
|
|
{
|
|
aPoint.setX( nRight );
|
|
aPoint.setY( nLastY );
|
|
}
|
|
// If we get a right position and if the left position
|
|
// is not the same like the left position of the line before
|
|
// which could happen e.g. for field portions or fly frames
|
|
// a SwPaM will be inserted with these positions
|
|
if( GetModelPositionForViewPoint( &aPosR, aPoint, &aState ) &&
|
|
aOld != aPosL)
|
|
{
|
|
SwPaM *pPam = new SwPaM( aPosL, aPosR );
|
|
rSelList.insertPaM( pPam );
|
|
aOld = aPosL;
|
|
}
|
|
}
|
|
}
|
|
if( aLine.Next() )
|
|
{
|
|
nLastY = nY;
|
|
nY = aLine.Y();
|
|
}
|
|
else if( !bLastLine )
|
|
{
|
|
bLastLine = true;
|
|
nLastY = nY;
|
|
nY += aLine.GetLineHeight();
|
|
}
|
|
else
|
|
break;
|
|
}while( nLastY < nBottom );
|
|
}
|
|
}
|
|
if( GetDrawObjs() )
|
|
{
|
|
const SwSortedObjs &rObjs = *GetDrawObjs();
|
|
for (SwAnchoredObject* pAnchoredObj : rObjs)
|
|
{
|
|
const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame();
|
|
if( !pFly )
|
|
continue;
|
|
if( pFly->IsFlyInContentFrame() && pFly->FillSelection( rSelList, rRect ) )
|
|
bRet = true;
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|