blob: ccc6df6c4109d5aa439c7bb08639e6502b2a2854 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sw.hxx"
#include "errhdl.hxx" // ASSERT
#include "txtcfg.hxx"
#include "porlay.hxx"
#include "itrform2.hxx"
#include "porglue.hxx"
#include "porexp.hxx" // SwQuoVadisPortion
#include "blink.hxx" // pBlink
#include "redlnitr.hxx" // SwRedlineItr
#include "porfly.hxx" // SwFlyCntPortion
#include <porrst.hxx> // SwHangingPortion
#include <pormulti.hxx> // SwMultiPortion
#include <breakit.hxx>
#include <unicode/uchar.h>
#include <com/sun/star/i18n/ScriptType.hdl>
#include <com/sun/star/i18n/CTLScriptType.hdl>
#include <com/sun/star/i18n/WordType.hdl>
#include <paratr.hxx>
#include <editeng/adjitem.hxx>
#include <editeng/scripttypeitem.hxx>
#include <editeng/charhiddenitem.hxx>
#include <vcl/outdev.hxx>
#include <editeng/blnkitem.hxx>
#include <tools/multisel.hxx>
#include <unotools/charclass.hxx>
#include <i18npool/mslangid.hxx>
#include <charfmt.hxx>
#include <fchrfmt.hxx>
#include <docary.hxx> // SwRedlineTbl
#include <redline.hxx> // SwRedline
#include <section.hxx>
#include <switerator.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentContentOperations.hxx>
using namespace ::com::sun::star;
using namespace i18n::ScriptType;
//#ifdef BIDI
#include <unicode/ubidi.h>
#include <i18nutil/unicode.hxx> //unicode::getUnicodeScriptType
sal_Bool isAlefChar ( xub_Unicode cCh )
{
return ( cCh == 0x622 || cCh == 0x623 || cCh == 0x625 || cCh == 0x627 ||
cCh == 0x622 || cCh == 0x671 || cCh == 0x672 || cCh == 0x673 || cCh == 0x675 );
}
sal_Bool isWawChar ( xub_Unicode cCh )
{
return ( cCh == 0x624 || cCh == 0x648 || cCh == 0x676 || cCh == 0x677 ||
( cCh >= 0x6C4 && cCh <= 0x6CB ) || cCh == 0x6CF );
}
sal_Bool isDalChar ( xub_Unicode cCh )
{
return ( cCh == 0x62F || cCh == 0x630 || cCh == 0x688 || cCh == 0x689 || cCh == 0x690 );
}
sal_Bool isRehChar ( xub_Unicode cCh )
{
return ( cCh == 0x631 || cCh == 0x632 || ( cCh >= 0x691 && cCh <= 0x699 ));
}
sal_Bool isTehMarbutaChar ( xub_Unicode cCh )
{
return ( cCh == 0x629 || cCh == 0x6C0 );
}
sal_Bool isBaaChar ( xub_Unicode cCh )
{
return ( cCh == 0x628 || cCh == 0x62A || cCh == 0x62B || cCh == 0x679 || cCh == 0x680 );
}
sal_Bool isYehChar ( xub_Unicode cCh )
{
return ( cCh == 0x626 || cCh == 0x649 || cCh == 0x64A || cCh == 0x678 || cCh == 0x6CC ||
cCh == 0x6CE || cCh == 0x6D0 || cCh == 0x6D1 );
}
sal_Bool isSeenOrSadChar ( xub_Unicode cCh )
{
return ( ( cCh >= 0x633 && cCh <= 0x636 ) || ( cCh >= 0x69A && cCh <= 0x69E )
|| cCh == 0x6FA || cCh == 0x6FB );
}
sal_Bool isHahChar ( xub_Unicode cCh )
{
return ( ( cCh >= 0x62C && cCh <= 0x62E ) || ( cCh >= 0x681 && cCh <= 0x687 )
|| cCh == 0x6BF );
}
sal_Bool isAinChar ( xub_Unicode cCh )
{
return ( cCh == 0x639 || cCh == 0x63A || cCh == 0x6A0 || cCh == 0x6FC );
}
sal_Bool isKafChar ( xub_Unicode cCh )
{
return ( cCh == 0x643 || ( cCh >= 0x6AC && cCh <= 0x6AE ) );
}
sal_Bool isLamChar ( xub_Unicode cCh )
{
return ( cCh == 0x644 || ( cCh >= 0x6B5 && cCh <= 0x6B8 ) );
}
sal_Bool isGafChar ( xub_Unicode cCh )
{
return ( cCh == 0x6A9 || cCh == 0x6AB ||( cCh >= 0x6AF && cCh <= 0x6B4 ) );
}
sal_Bool isQafChar ( xub_Unicode cCh )
{
return ( cCh == 0x642 || cCh == 0x6A7 || cCh == 0x6A8 );
}
sal_Bool isFeChar ( xub_Unicode cCh )
{
return ( cCh == 0x641 || ( cCh >= 0x6A1 && cCh <= 0x6A6 ) );
}
sal_Bool isTransparentChar ( xub_Unicode cCh )
{
return ( ( cCh >= 0x610 && cCh <= 0x61A ) ||
( cCh >= 0x64B && cCh <= 0x65E ) ||
( cCh == 0x670 ) ||
( cCh >= 0x6D6 && cCh <= 0x6DC ) ||
( cCh >= 0x6DF && cCh <= 0x6E4 ) ||
( cCh >= 0x6E7 && cCh <= 0x6E8 ) ||
( cCh >= 0x6EA && cCh <= 0x6ED ));
}
/*************************************************************************
* lcl_IsLigature
*
* Checks if cCh + cNectCh builds a ligature (used for Kashidas)
*************************************************************************/
sal_Bool lcl_IsLigature( xub_Unicode cCh, xub_Unicode cNextCh )
{
// Lam + Alef
return ( isLamChar ( cCh ) && isAlefChar ( cNextCh ));
}
/*************************************************************************
* lcl_ConnectToPrev
*
* Checks if cCh is connectable to cPrevCh (used for Kashidas)
*************************************************************************/
sal_Bool lcl_ConnectToPrev( xub_Unicode cCh, xub_Unicode cPrevCh )
{
// Alef, Dal, Thal, Reh, Zain, and Waw do not connect to the left
// Uh, there seem to be some more characters that are not connectable
// to the left. So we look for the characters that are actually connectable
// to the left. Here is the complete list of WH:
// (hennerdrewes):
// added lam forms 0x06B5..0x06B8
// added 0x6FA..0x6FC, according to unicode documentation, although not present in my fonts
// added heh goal 0x6C1
sal_Bool bRet = 0x628 == cPrevCh ||
( 0x62A <= cPrevCh && cPrevCh <= 0x62E ) ||
( 0x633 <= cPrevCh && cPrevCh <= 0x647 ) ||
0x649 == cPrevCh || // Alef Maksura does connect !!!
0x64A == cPrevCh ||
( 0x678 <= cPrevCh && cPrevCh <= 0x687 ) ||
( 0x69A <= cPrevCh && cPrevCh <= 0x6C1 ) ||
( 0x6C3 <= cPrevCh && cPrevCh <= 0x6D3 ) ||
( 0x6FA <= cPrevCh && cPrevCh <= 0x6FC ) ;
// check for ligatures cPrevChar + cChar
if( bRet )
bRet = !lcl_IsLigature( cPrevCh, cCh );
return bRet;
}
/*************************************************************************
* lcl_HasStrongLTR
*************************************************************************/
bool lcl_HasStrongLTR ( const String& rTxt, xub_StrLen nStart, xub_StrLen nEnd )
{
for ( xub_StrLen nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx )
{
const UCharDirection nCharDir = u_charDirection ( rTxt.GetChar ( nCharIdx ));
if ( nCharDir == U_LEFT_TO_RIGHT ||
nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
return true;
}
return false;
}
/*************************************************************************
* SwLineLayout::~SwLineLayout()
*
* class SwLineLayout: Das Layout einer einzelnen Zeile. Dazu
* gehoeren vor allen Dingen die Dimension, die Anzahl der
* Character und der Wortzwischenraeume in der Zeile.
* Zeilenobjekte werden in einem eigenen Pool verwaltet, um zu
* erreichen, dass sie im Speicher moeglichst beeinander liegen
* (d.h. zusammen gepaged werden und den Speicher nicht
* fragmentieren).
*************************************************************************/
SwLineLayout::~SwLineLayout()
{
Truncate();
if( GetNext() )
delete GetNext();
if( pBlink )
pBlink->Delete( this );
delete pLLSpaceAdd;
if ( pKanaComp )
delete pKanaComp;
}
/*************************************************************************
* virtual SwLineLayout::Insert()
*************************************************************************/
SwLinePortion *SwLineLayout::Insert( SwLinePortion *pIns )
{
// Erster Attributwechsel, Masse und Laengen
// aus *pCurr in die erste Textportion kopieren.
if( !pPortion )
{
if( GetLen() )
{
pPortion = new SwTxtPortion( *(SwLinePortion*)this );
if( IsBlinking() && pBlink )
{
SetBlinking( sal_False );
pBlink->Replace( this, pPortion );
}
}
else
{
SetPortion( pIns );
return pIns;
}
}
// mit Skope aufrufen, sonst Rekursion !
return pPortion->SwLinePortion::Insert( pIns );
}
/*************************************************************************
* virtual SwLineLayout::Append()
*************************************************************************/
SwLinePortion *SwLineLayout::Append( SwLinePortion *pIns )
{
// Erster Attributwechsel, Masse und Laengen
// aus *pCurr in die erste Textportion kopieren.
if( !pPortion )
pPortion = new SwTxtPortion( *(SwLinePortion*)this );
// mit Skope aufrufen, sonst Rekursion !
return pPortion->SwLinePortion::Append( pIns );
}
/*************************************************************************
* virtual SwLineLayout::Format()
*************************************************************************/
// fuer die Sonderbehandlung bei leeren Zeilen
sal_Bool SwLineLayout::Format( SwTxtFormatInfo &rInf )
{
if( GetLen() )
return SwTxtPortion::Format( rInf );
else
{
Height( rInf.GetTxtHeight() );
return sal_True;
}
}
/*************************************************************************
* SwLineLayout::CalcLeftMargin()
*
* Wir sammeln alle FlyPortions am Anfang der Zeile zu einer MarginPortion.
*************************************************************************/
SwMarginPortion *SwLineLayout::CalcLeftMargin()
{
SwMarginPortion *pLeft = (GetPortion() && GetPortion()->IsMarginPortion()) ?
(SwMarginPortion *)GetPortion() : 0;
if( !GetPortion() )
SetPortion( new SwTxtPortion( *(SwLinePortion*)this ) );
if( !pLeft )
{
pLeft = new SwMarginPortion( 0 );
pLeft->SetPortion( GetPortion() );
SetPortion( pLeft );
}
else
{
pLeft->Height( 0 );
pLeft->Width( 0 );
pLeft->SetLen( 0 );
pLeft->SetAscent( 0 );
pLeft->SetPortion( NULL );
pLeft->SetFixWidth(0);
}
SwLinePortion *pPos = pLeft->GetPortion();
while( pPos )
{
DBG_LOOP;
if( pPos->IsFlyPortion() )
{
// Die FlyPortion wird ausgesogen ...
pLeft->Join( (SwGluePortion*)pPos );
pPos = pLeft->GetPortion();
if( GetpKanaComp() )
GetKanaComp().Remove( 0, 1 );
}
else
pPos = 0;
}
return pLeft;
}
/*************************************************************************
* SwLineLayout::InitSpaceAdd()
*************************************************************************/
void SwLineLayout::InitSpaceAdd()
{
if ( !pLLSpaceAdd )
CreateSpaceAdd();
else
SetLLSpaceAdd( 0, 0 );
}
/*************************************************************************
* SwLineLayout::CreateSpaceAdd()
*************************************************************************/
void SwLineLayout::CreateSpaceAdd( const long nInit )
{
pLLSpaceAdd = new std::vector<long>;
SetLLSpaceAdd( nInit, 0 );
}
/*************************************************************************
* Local helper function. Returns true if there are only blanks
* in [nStt, nEnd[
*************************************************************************/
bool lcl_HasOnlyBlanks( const XubString& rTxt, xub_StrLen nStt, xub_StrLen nEnd )
{
bool bBlankOnly = true;
while ( nStt < nEnd )
{
const xub_Unicode cChar = rTxt.GetChar( nStt++ );
if ( ' ' != cChar && 0x3000 != cChar )
{
bBlankOnly = false;
break;
}
}
return bBlankOnly;
}
/*************************************************************************
* SwLineLayout::CalcLine()
*
* Aus FormatLine() ausgelagert.
*************************************************************************/
void SwLineLayout::CalcLine( SwTxtFormatter &rLine, SwTxtFormatInfo &rInf )
{
const KSHORT nLineWidth = rInf.RealWidth();
KSHORT nFlyAscent = 0;
KSHORT nFlyHeight = 0;
KSHORT nFlyDescent = 0;
sal_Bool bOnlyPostIts = sal_True;
SetHanging( sal_False );
sal_Bool bTmpDummy = ( 0 == GetLen() );
SwFlyCntPortion* pFlyCnt = 0;
if( bTmpDummy )
{
nFlyAscent = 0;
nFlyHeight = 0;
nFlyDescent = 0;
}
// --> FME 2006-03-01 #i3952#
const bool bIgnoreBlanksAndTabsForLineHeightCalculation =
rInf.GetTxtFrm()->GetNode()->getIDocumentSettingAccess()->get(IDocumentSettingAccess::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION);
bool bHasBlankPortion = false;
bool bHasOnlyBlankPortions = true;
// <--
if( pPortion )
{
SetCntnt( sal_False );
if( pPortion->IsBreakPortion() )
{
SetLen( pPortion->GetLen() );
if( GetLen() )
bTmpDummy = sal_False;
}
else
{
Init( GetPortion() );
SwLinePortion *pPos = pPortion;
SwLinePortion *pLast = this;
KSHORT nMaxDescent = 0;
// Eine Gruppe ist ein Abschnitt in der Portion-Kette von
// pCurr oder einer Fix-Portion bis zum Ende bzw. zur naechsten
// Fix-Portion.
while( pPos )
{
DBG_LOOP;
ASSERT( POR_LIN != pPos->GetWhichPor(),
"SwLineLayout::CalcLine: don't use SwLinePortions !" );
// Null-Portions werden eliminiert. Sie koennen entstehen,
// wenn zwei FlyFrms ueberlappen.
if( !pPos->Compress() )
{
// 8110: Hoehe und Ascent nur uebernehmen, wenn sonst in der
// Zeile nichts mehr los ist.
if( !pPos->GetPortion() )
{
if( !Height() )
Height( pPos->Height() );
if( !GetAscent() )
SetAscent( pPos->GetAscent() );
}
delete pLast->Cut( pPos );
pPos = pLast->GetPortion();
continue;
}
const xub_StrLen nPorSttIdx = rInf.GetLineStart() + nLineLength;
nLineLength = nLineLength + pPos->GetLen();
AddPrtWidth( pPos->Width() );
// --> FME 2006-03-01 #i3952#
if ( bIgnoreBlanksAndTabsForLineHeightCalculation )
{
if ( pPos->InTabGrp() || pPos->IsHolePortion() ||
( pPos->IsTextPortion() &&
lcl_HasOnlyBlanks( rInf.GetTxt(), nPorSttIdx, nPorSttIdx + pPos->GetLen() ) ) )
{
pLast = pPos;
pPos = pPos->GetPortion();
bHasBlankPortion = true;
continue;
}
}
// <--
bHasOnlyBlankPortions = false;
// Es gab Attributwechsel: Laengen und Masse aufaddieren;
// bzw.Maxima bilden.
KSHORT nPosHeight = pPos->Height();
KSHORT nPosAscent = pPos->GetAscent();
ASSERT( nPosHeight >= nPosAscent,
"SwLineLayout::CalcLine: bad ascent or height" );
if( pPos->IsHangingPortion() )
{
SetHanging( sal_True );
rInf.GetParaPortion()->SetMargin( sal_True );
}
// Damit ein Paragraphende-Zeichen nicht durch ein Descent zu einer
// geaenderten Zeilenhoehe und zum Umformatieren fuehrt.
if ( !pPos->IsBreakPortion() || !Height() )
{
bOnlyPostIts &= pPos->IsPostItsPortion();
if( bTmpDummy && !nLineLength )
{
if( pPos->IsFlyPortion() )
{
if( nFlyHeight < nPosHeight )
nFlyHeight = nPosHeight;
if( nFlyAscent < nPosAscent )
nFlyAscent = nPosAscent;
if( nFlyDescent < nPosHeight - nPosAscent )
nFlyDescent = nPosHeight - nPosAscent;
}
else
{
if( pPos->InNumberGrp() )
{
KSHORT nTmp = rInf.GetFont()->GetAscent(
rInf.GetVsh(), *rInf.GetOut() );
if( nTmp > nPosAscent )
{
nPosHeight += nTmp - nPosAscent;
nPosAscent = nTmp;
}
nTmp = rInf.GetFont()->GetHeight( rInf.GetVsh(),
*rInf.GetOut() );
if( nTmp > nPosHeight )
nPosHeight = nTmp;
}
Height( nPosHeight );
nAscent = nPosAscent;
nMaxDescent = nPosHeight - nPosAscent;
}
}
else if( !pPos->IsFlyPortion() )
{
if( Height() < nPosHeight )
Height( nPosHeight );
if( pPos->IsFlyCntPortion() || ( pPos->IsMultiPortion()
&& ((SwMultiPortion*)pPos)->HasFlyInCntnt() ) )
rLine.SetFlyInCntBase();
if( pPos->IsFlyCntPortion() &&
((SwFlyCntPortion*)pPos)->GetAlign() )
{
((SwFlyCntPortion*)pPos)->SetMax( sal_False );
if( !pFlyCnt || pPos->Height() > pFlyCnt->Height() )
pFlyCnt = (SwFlyCntPortion*)pPos;
}
else
{
if( nAscent < nPosAscent )
nAscent = nPosAscent;
if( nMaxDescent < nPosHeight - nPosAscent )
nMaxDescent = nPosHeight - nPosAscent;
}
}
}
else if( pPos->GetLen() )
bTmpDummy = sal_False;
if( !HasCntnt() && !pPos->InNumberGrp() )
{
if ( pPos->InExpGrp() )
{
XubString aTxt;
if( pPos->GetExpTxt( rInf, aTxt ) && aTxt.Len() )
SetCntnt( sal_True );
}
else if( ( pPos->InTxtGrp() || pPos->IsMultiPortion() ) &&
pPos->GetLen() )
SetCntnt( sal_True );
}
bTmpDummy = bTmpDummy && !HasCntnt() &&
( !pPos->Width() || pPos->IsFlyPortion() );
pLast = pPos;
pPos = pPos->GetPortion();
}
if( pFlyCnt )
{
if( pFlyCnt->Height() == Height() )
{
pFlyCnt->SetMax( sal_True );
if( Height() > nMaxDescent + nAscent )
{
if( 3 == pFlyCnt->GetAlign() ) // Bottom
nAscent = Height() - nMaxDescent;
else if( 2 == pFlyCnt->GetAlign() ) // Center
nAscent = ( Height() + nAscent - nMaxDescent ) / 2;
}
pFlyCnt->SetAscent( nAscent );
}
}
if( bTmpDummy && nFlyHeight )
{
nAscent = nFlyAscent;
if( nFlyDescent > nFlyHeight - nFlyAscent )
Height( nFlyHeight + nFlyDescent );
else
Height( nFlyHeight );
}
else if( nMaxDescent > Height() - nAscent )
Height( nMaxDescent + nAscent );
if( bOnlyPostIts && !( bHasBlankPortion && bHasOnlyBlankPortions ) )
{
Height( rInf.GetFont()->GetHeight( rInf.GetVsh(), *rInf.GetOut() ) );
nAscent = rInf.GetFont()->GetAscent( rInf.GetVsh(), *rInf.GetOut() );
}
}
}
else
{
SetCntnt( !bTmpDummy );
// --> FME 2006-03-01 #i3952#
if ( bIgnoreBlanksAndTabsForLineHeightCalculation &&
lcl_HasOnlyBlanks( rInf.GetTxt(), rInf.GetLineStart(), rInf.GetLineStart() + GetLen() ) )
{
bHasBlankPortion = true;
}
// <--
}
// --> FME 2006-03-01 #i3952#
if ( bHasBlankPortion && bHasOnlyBlankPortions )
{
sal_uInt16 nTmpAscent = GetAscent();
sal_uInt16 nTmpHeight = Height();
rLine.GetAttrHandler().GetDefaultAscentAndHeight( rInf.GetVsh(), *rInf.GetOut(), nTmpAscent, nTmpHeight );
SetAscent( nTmpAscent );
Height( nTmpHeight );
}
// <--
// Robust:
if( nLineWidth < Width() )
Width( nLineWidth );
ASSERT( nLineWidth >= Width(), "SwLineLayout::CalcLine: line is bursting" );
SetDummy( bTmpDummy );
SetRedline( rLine.GetRedln() &&
rLine.GetRedln()->CheckLine( rLine.GetStart(), rLine.GetEnd() ) );
}
// --> OD 2005-05-20 #i47162# - add optional parameter <_bNoFlyCntPorAndLinePor>
// to control, if the fly content portions and line portion are considered.
void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent,
SwTwips& _orDescent,
SwTwips& _orObjAscent,
SwTwips& _orObjDescent,
const SwLinePortion* _pDontConsiderPortion,
const bool _bNoFlyCntPorAndLinePor ) const
{
_orAscent = 0;
_orDescent = 0;
_orObjAscent = 0;
_orObjDescent = 0;
const SwLinePortion* pTmpPortion = this;
if ( !pTmpPortion->GetLen() && pTmpPortion->GetPortion() )
{
pTmpPortion = pTmpPortion->GetPortion();
}
while ( pTmpPortion )
{
if ( !pTmpPortion->IsBreakPortion() && !pTmpPortion->IsFlyPortion() &&
( !_bNoFlyCntPorAndLinePor ||
( !pTmpPortion->IsFlyCntPortion() &&
!(pTmpPortion == this && pTmpPortion->GetPortion() ) ) ) )
{
SwTwips nPortionAsc = static_cast<SwTwips>(pTmpPortion->GetAscent());
SwTwips nPortionDesc = static_cast<SwTwips>(pTmpPortion->Height()) -
nPortionAsc;
const sal_Bool bFlyCmp = pTmpPortion->IsFlyCntPortion() ?
static_cast<const SwFlyCntPortion*>(pTmpPortion)->IsMax() :
!( pTmpPortion == _pDontConsiderPortion );
if ( bFlyCmp )
{
_orObjAscent = Max( _orObjAscent, nPortionAsc );
_orObjDescent = Max( _orObjDescent, nPortionDesc );
}
if ( !pTmpPortion->IsFlyCntPortion() && !pTmpPortion->IsGrfNumPortion() )
{
_orAscent = Max( _orAscent, nPortionAsc );
_orDescent = Max( _orDescent, nPortionDesc );
}
}
pTmpPortion = pTmpPortion->GetPortion();
}
}
/*************************************************************************
* class SwCharRange
*************************************************************************/
SwCharRange &SwCharRange::operator+=(const SwCharRange &rRange)
{
if(0 != rRange.nLen ) {
if(0 == nLen) {
nStart = rRange.nStart;
nLen = rRange.nLen ;
}
else {
if(rRange.nStart + rRange.nLen > nStart + nLen) {
nLen = rRange.nStart + rRange.nLen - nStart;
}
if(rRange.nStart < nStart) {
nLen += nStart - rRange.nStart;
nStart = rRange.nStart;
}
}
}
return *this;
}
/*************************************************************************
* SwScriptInfo::SwScriptInfo()
*************************************************************************/
SwScriptInfo::SwScriptInfo() :
nInvalidityPos( 0 ),
nDefaultDir( 0 )
{
};
/*************************************************************************
* SwScriptInfo::~SwScriptInfo()
*************************************************************************/
SwScriptInfo::~SwScriptInfo()
{
}
/*************************************************************************
* SwScriptInfo::WhichFont()
*
* Converts i18n Script Type (LATIN, ASIAN, COMPLEX, WEAK) to
* Sw Script Types (SW_LATIN, SW_CJK, SW_CTL), used to identify the font
*************************************************************************/
sal_uInt8 SwScriptInfo::WhichFont( xub_StrLen nIdx, const String* pTxt, const SwScriptInfo* pSI )
{
ASSERT( pTxt || pSI,"How should I determine the script type?" );
sal_uInt16 nScript;
// First we try to use our SwScriptInfo
if ( pSI )
nScript = pSI->ScriptType( nIdx );
else
// Ok, we have to ask the break iterator
nScript = pBreakIt->GetRealScriptOfText( *pTxt, nIdx );
switch ( nScript ) {
case i18n::ScriptType::LATIN : return SW_LATIN;
case i18n::ScriptType::ASIAN : return SW_CJK;
case i18n::ScriptType::COMPLEX : return SW_CTL;
}
ASSERT( sal_False, "Somebody tells lies about the script type!" );
return SW_LATIN;
}
/*************************************************************************
* SwScriptInfo::InitScriptInfo()
*
* searches for script changes in rTxt and stores them
*************************************************************************/
void SwScriptInfo::InitScriptInfo( const SwTxtNode& rNode )
{
InitScriptInfo( rNode, nDefaultDir == UBIDI_RTL );
}
void SwScriptInfo::InitScriptInfo( const SwTxtNode& rNode, sal_Bool bRTL )
{
if( !pBreakIt->GetBreakIter().is() )
return;
const String& rTxt = rNode.GetTxt();
//
// HIDDEN TEXT INFORMATION
//
Range aRange( 0, rTxt.Len() ? rTxt.Len() - 1 : 0 );
MultiSelection aHiddenMulti( aRange );
CalcHiddenRanges( rNode, aHiddenMulti );
aHiddenChg.clear();
sal_uInt16 i = 0;
for( i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
{
const Range& rRange = aHiddenMulti.GetRange( i );
const xub_StrLen nStart = (xub_StrLen)rRange.Min();
const xub_StrLen nEnd = (xub_StrLen)rRange.Max() + 1;
aHiddenChg.push_back( nStart );
aHiddenChg.push_back( nEnd );
}
//
// SCRIPT AND SCRIPT RELATED INFORMATION
//
xub_StrLen nChg = nInvalidityPos;
// STRING_LEN means the data structure is up to date
nInvalidityPos = STRING_LEN;
// this is the default direction
nDefaultDir = static_cast<sal_uInt8>(bRTL ? UBIDI_RTL : UBIDI_LTR);
// counter for script info arrays
sal_uInt16 nCnt = 0;
// counter for compression information arrays
sal_uInt16 nCntComp = 0;
// counter for kashida array
sal_uInt16 nCntKash = 0;
sal_uInt8 nScript = i18n::ScriptType::LATIN;
// compression type
const SwCharCompressType aCompEnum = rNode.getIDocumentSettingAccess()->getCharacterCompressionType();
// justification type
const sal_Bool bAdjustBlock = SVX_ADJUST_BLOCK ==
rNode.GetSwAttrSet().GetAdjust().GetAdjust();
//
// FIND INVALID RANGES IN SCRIPT INFO ARRAYS:
//
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
ASSERT( CountScriptChg(), "Where're my changes of script?" );
while( nCnt < CountScriptChg() )
{
if ( nChg > GetScriptChg( nCnt ) )
nCnt++;
else
{
nScript = GetScriptType( nCnt );
break;
}
}
if( CHARCOMPRESS_NONE != aCompEnum )
{
while( nCntComp < CountCompChg() )
{
if ( nChg > GetCompStart( nCntComp ) )
nCntComp++;
else
break;
}
}
if ( bAdjustBlock )
{
while( nCntKash < CountKashida() )
{
if ( nChg > GetKashida( nCntKash ) )
nCntKash++;
else
break;
}
}
}
//
// ADJUST nChg VALUE:
//
// by stepping back one position we know that we are inside a group
// declared as an nScript group
if ( nChg )
--nChg;
const xub_StrLen nGrpStart = nCnt ? GetScriptChg( nCnt - 1 ) : 0;
// we go back in our group until we reach the first character of
// type nScript
while ( nChg > nGrpStart &&
nScript != pBreakIt->GetBreakIter()->getScriptType( rTxt, 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 = (sal_uInt8)pBreakIt->GetBreakIter()->getScriptType( rTxt, nChg );
//
// INVALID DATA FROM THE SCRIPT INFO ARRAYS HAS TO BE DELETED:
//
// remove invalid entries from script information arrays
const size_t nScriptRemove = aScriptChg.size() - nCnt;
aScriptChg.erase( aScriptChg.begin() + nCnt, aScriptChg.end() );
aScriptType.erase( aScriptType.begin() + nCnt, aScriptType.begin() + (nCnt + nScriptRemove) );
// get the start of the last compression group
sal_uInt16 nLastCompression = nChg;
if( nCntComp )
{
--nCntComp;
nLastCompression = GetCompStart( nCntComp );
if( nChg >= nLastCompression + GetCompLen( nCntComp ) )
{
nLastCompression = nChg;
++nCntComp;
}
}
// remove invalid entries from compression information arrays
const size_t nCompRemove = aCompChg.size() - nCntComp;
aCompChg.erase( aCompChg.begin() + nCntComp, aCompChg.end() );
aCompLen.erase( aCompLen.begin() + nCntComp, aCompLen.begin() + (nCntComp + nCompRemove) );
aCompType.erase( aCompType.begin() + nCntComp, aCompType.end() );
// get the start of the last kashida group
sal_uInt16 nLastKashida = nChg;
if( nCntKash && i18n::ScriptType::COMPLEX == nScript )
{
--nCntKash;
nLastKashida = GetKashida( nCntKash );
}
// remove invalid entries from kashida array
aKashida.erase( aKashida.begin() + nCntKash, aKashida.end() );
//
// TAKE CARE OF WEAK CHARACTERS: WE MUST FIND AN APPROPRIATE
// SCRIPT FOR WEAK CHARACTERS AT THE BEGINNING OF A PARAGRAPH
//
if( WEAK == pBreakIt->GetBreakIter()->getScriptType( rTxt, nChg ) )
{
// If the beginning of the current group is weak, this means that
// all of the characters in this grounp are weak. We have to assign
// the scripts to these characters depending on the fonts which are
// set for these characters to display them.
xub_StrLen nEnd =
(xub_StrLen)pBreakIt->GetBreakIter()->endOfScript( rTxt, nChg, WEAK );
if( nEnd > rTxt.Len() )
nEnd = rTxt.Len();
nScript = (sal_uInt8)GetI18NScriptTypeOfLanguage( (sal_uInt16)GetAppLanguage() );
ASSERT( i18n::ScriptType::LATIN == nScript ||
i18n::ScriptType::ASIAN == nScript ||
i18n::ScriptType::COMPLEX == nScript, "Wrong default language" );
nChg = nEnd;
// Get next script type or set to weak in order to exit
sal_uInt8 nNextScript = ( nEnd < rTxt.Len() ) ?
(sal_uInt8)pBreakIt->GetBreakIter()->getScriptType( rTxt, nEnd ) :
(sal_uInt8)WEAK;
if ( nScript != nNextScript )
{
aScriptChg.insert( aScriptChg.begin() + nCnt, nEnd );
aScriptType.insert( aScriptType.begin() + nCnt, nScript );
nCnt++;
nScript = nNextScript;
}
}
//
// UPDATE THE SCRIPT INFO ARRAYS:
//
while ( nChg < rTxt.Len() || ( aScriptChg.empty() && !rTxt.Len() ) )
{
ASSERT( i18n::ScriptType::WEAK != nScript,
"Inserting WEAK into SwScriptInfo structure" );
ASSERT( STRING_LEN != nChg, "65K? Strange length of script section" );
xub_StrLen nSearchStt = nChg;
nChg = (xub_StrLen)pBreakIt->GetBreakIter()->endOfScript( rTxt, nSearchStt, nScript );
if ( nChg > rTxt.Len() )
nChg = rTxt.Len();
// --> FME 2008-09-17 #i28203#
// for 'complex' portions, we make sure that a portion does not contain more
// than one script:
if( i18n::ScriptType::COMPLEX == nScript && pBreakIt->GetScriptTypeDetector().is() )
{
const short nScriptType = pBreakIt->GetScriptTypeDetector()->getCTLScriptType( rTxt, nSearchStt );
xub_StrLen nNextCTLScriptStart = nSearchStt;
short nCurrentScriptType = nScriptType;
while( com::sun::star::i18n::CTLScriptType::CTL_UNKNOWN == nCurrentScriptType || nScriptType == nCurrentScriptType )
{
nNextCTLScriptStart = (xub_StrLen)pBreakIt->GetScriptTypeDetector()->endOfCTLScriptType( rTxt, nNextCTLScriptStart );
if( nNextCTLScriptStart < rTxt.Len() && nNextCTLScriptStart < nChg )
nCurrentScriptType = pBreakIt->GetScriptTypeDetector()->getCTLScriptType( rTxt, nNextCTLScriptStart );
else
break;
}
nChg = Min( nChg, nNextCTLScriptStart );
}
// <--
// special case for dotted circle since it can be used with complex
// before a mark, so we want it associated with the mark's script
if (nChg < rTxt.Len() && nChg > 0 && (i18n::ScriptType::WEAK ==
pBreakIt->GetBreakIter()->getScriptType(rTxt,nChg - 1)))
{
int8_t nType = u_charType(rTxt.GetChar(nChg) );
if (nType == U_NON_SPACING_MARK || nType == U_ENCLOSING_MARK ||
nType == U_COMBINING_SPACING_MARK )
{
aScriptChg.insert( aScriptChg.begin() + nCnt, nChg - 1 );
}
else
{
aScriptChg.insert( aScriptChg.begin() + nCnt, nChg );
}
}
else
{
aScriptChg.insert( aScriptChg.begin() + nCnt, nChg );
}
aScriptType.insert( aScriptType.begin() + nCnt, nScript );
nCnt++;
// if current script is asian, we search for compressable characters
// in this range
if ( CHARCOMPRESS_NONE != aCompEnum &&
i18n::ScriptType::ASIAN == nScript )
{
sal_uInt8 ePrevState = NONE;
sal_uInt8 eState;
sal_uInt16 nPrevChg = nLastCompression;
while ( nLastCompression < nChg )
{
xub_Unicode cChar = rTxt.GetChar( 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:
eState = SPECIAL_LEFT;
break;
// Right punctuation found
case 0x3001: case 0x3002: case 0x3009: case 0x300B:
case 0x300D: case 0x300F: case 0x3011: case 0x3015:
case 0x3017: case 0x3019: case 0x301B: case 0x301E:
case 0x301F:
eState = SPECIAL_RIGHT;
break;
default:
eState = static_cast<sal_uInt8>( ( 0x3040 <= cChar && 0x3100 > cChar ) ? KANA : NONE );
}
// insert range of compressable characters
if( ePrevState != eState )
{
if ( ePrevState != NONE )
{
// insert start and type
if ( CHARCOMPRESS_PUNCTUATION_KANA == aCompEnum ||
ePrevState != KANA )
{
aCompChg.insert( aCompChg.begin() + nCntComp, nPrevChg );
sal_uInt8 nTmpType = ePrevState;
aCompType.insert( aCompType.begin() + nCntComp, nTmpType );
aCompLen.insert( aCompLen.begin() + nCntComp, nLastCompression - nPrevChg );
nCntComp++;
}
}
ePrevState = eState;
nPrevChg = nLastCompression;
}
nLastCompression++;
}
// we still have to examine last entry
if ( ePrevState != NONE )
{
// insert start and type
if ( CHARCOMPRESS_PUNCTUATION_KANA == aCompEnum ||
ePrevState != KANA )
{
aCompChg.insert( aCompChg.begin() + nCntComp, nPrevChg );
sal_uInt8 nTmpType = ePrevState;
aCompType.insert( aCompType.begin() + nCntComp, nTmpType );
aCompLen.insert( aCompLen.begin() + nCntComp, nLastCompression - nPrevChg );
nCntComp++;
}
}
}
// we search for connecting opportunities (kashida)
else if ( bAdjustBlock && i18n::ScriptType::COMPLEX == nScript )
{
SwScanner aScanner( rNode, rNode.GetTxt(), 0, 0,
i18n::WordType::DICTIONARY_WORD,
nLastKashida, nChg );
// the search has to be performed on a per word base
while ( aScanner.NextWord() )
{
const XubString& rWord = aScanner.GetWord();
xub_StrLen nIdx = 0;
xub_StrLen nKashidaPos = STRING_LEN;
xub_Unicode cCh;
xub_Unicode cPrevCh = 0;
sal_uInt16 nPriorityLevel = 7; // 0..6 = level found
// 7 not found
xub_StrLen nWordLen = rWord.Len();
// ignore trailing vowel chars
while( nWordLen && isTransparentChar( rWord.GetChar( nWordLen - 1 )))
--nWordLen;
while (nIdx < nWordLen)
{
cCh = rWord.GetChar( nIdx );
// 1. Priority:
// after user inserted kashida
if ( 0x640 == cCh )
{
nKashidaPos = aScanner.GetBegin() + nIdx;
nPriorityLevel = 0;
}
// 2. Priority:
// after a Seen or Sad
if (nPriorityLevel >= 1 && nIdx < nWordLen - 1)
{
if( isSeenOrSadChar( cCh )
&& (rWord.GetChar( nIdx+1 ) != 0x200C) ) // #i98410#: prevent ZWNJ expansion
{
nKashidaPos = aScanner.GetBegin() + nIdx;
nPriorityLevel = 1;
}
}
// 3. Priority:
// before final form of Teh Marbuta, Hah, Dal
if ( nPriorityLevel >= 2 && nIdx > 0 )
{
if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining)
isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word
( isHahChar ( cCh ) && nIdx == nWordLen - 1)) // Hah (dual joining) only at end of word
{
ASSERT( 0 != cPrevCh, "No previous character" )
// check if character is connectable to previous character,
if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
{
nKashidaPos = aScanner.GetBegin() + nIdx - 1;
nPriorityLevel = 2;
}
}
}
// 4. Priority:
// before final form of Alef, Lam or Kaf
if ( nPriorityLevel >= 3 && nIdx > 0 )
{
if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word
(( isLamChar ( cCh ) || // Lam
isKafChar ( cCh ) || // Kaf (both dual joining)
isGafChar ( cCh ) )
&& nIdx == nWordLen - 1)) // only at end of word
{
ASSERT( 0 != cPrevCh, "No previous character" )
// check if character is connectable to previous character,
if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
{
nKashidaPos = aScanner.GetBegin() + nIdx - 1;
nPriorityLevel = 3;
}
}
}
// 5. Priority:
// before media Bah
if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 )
{
if ( isBaaChar ( cCh )) // Bah
{
// check if next character is Reh, Yeh or Alef Maksura
xub_Unicode cNextCh = rWord.GetChar( nIdx + 1 );
if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh ))
{
ASSERT( 0 != cPrevCh, "No previous character" )
// check if character is connectable to previous character,
if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
{
nKashidaPos = aScanner.GetBegin() + nIdx - 1;
nPriorityLevel = 4;
}
}
}
}
// 6. Priority:
// before the final form of Waw, Ain, Qaf and Fa
if ( nPriorityLevel >= 5 && nIdx > 0 )
{
if ( isWawChar ( cCh ) || // Wav (right joining)
// final form may appear in the middle of word
(( isAinChar ( cCh ) || // Ain (dual joining)
isQafChar ( cCh ) || // Qaf (dual joining)
isFeChar ( cCh ) ) // Feh (dual joining)
&& nIdx == nWordLen - 1)) // only at end of word
{
ASSERT( 0 != cPrevCh, "No previous character" )
// check if character is connectable to previous character,
if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
{
nKashidaPos = aScanner.GetBegin() + nIdx - 1;
nPriorityLevel = 5;
}
}
}
// other connecting possibilities
if ( nPriorityLevel >= 6 && nIdx > 0 )
{
// remaining right joiners
// Reh, Zain, Thal,
if ( isRehChar ( cCh ) || // Reh Zain (right joining)
// final form may appear in the middle of word
( 0x60C <= cCh && 0x6FE >= cCh // all others
&& nIdx == nWordLen - 1)) // only at end of word
{
ASSERT( 0 != cPrevCh, "No previous character" )
// check if character is connectable to previous character,
if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
{
nKashidaPos = aScanner.GetBegin() + nIdx - 1;
nPriorityLevel = 6;
}
}
}
// Do not consider Fathatan, Dammatan, Kasratan, Fatha,
// Damma, Kasra, Shadda and Sukun when checking if
// a character can be connected to previous character.
if ( !isTransparentChar ( cCh) )
cPrevCh = cCh;
++nIdx;
} // end of current word
if ( STRING_LEN != nKashidaPos )
{
aKashida.insert( aKashida.begin() + nCntKash, nKashidaPos);
nCntKash++;
}
} // end of kashida search
}
if ( nChg < rTxt.Len() )
nScript = (sal_uInt8)pBreakIt->GetBreakIter()->getScriptType( rTxt, nChg );
nLastCompression = nChg;
nLastKashida = nChg;
};
#ifdef DBG_UTIL
// check kashida data
long nTmpKashidaPos = -1;
sal_Bool bWrongKash = sal_False;
for (i = 0; i < aKashida.size(); ++i )
{
long nCurrKashidaPos = GetKashida( i );
if ( nCurrKashidaPos <= nTmpKashidaPos )
{
bWrongKash = sal_True;
break;
}
nTmpKashidaPos = nCurrKashidaPos;
}
ASSERT( ! bWrongKash, "Kashida array contains wrong data" )
#endif
// remove invalid entries from direction information arrays
aDirChg.clear();
aDirType.clear();
// Perform Unicode Bidi Algorithm for text direction information
bool bPerformUBA = UBIDI_LTR != nDefaultDir;
nCnt = 0;
while( !bPerformUBA && nCnt < CountScriptChg() )
{
if ( i18n::ScriptType::COMPLEX == GetScriptType( nCnt++ ) )
bPerformUBA = true;
}
// do not call the unicode bidi algorithm if not required
if ( bPerformUBA )
{
UpdateBidiInfo( rTxt );
// #i16354# Change script type for RTL text to CTL:
// 1. All text in RTL runs will use the CTL font
// #i89825# change the script type also to CTL (hennerdrewes)
// 2. Text in embedded LTR runs that does not have any strong LTR characters (numbers!)
for ( size_t nDirIdx = 0; nDirIdx < aDirChg.size(); ++nDirIdx )
{
const sal_uInt8 nCurrDirType = GetDirType( nDirIdx );
// nStart ist start of RTL run:
const xub_StrLen nStart = nDirIdx > 0 ? GetDirChg( nDirIdx - 1 ) : 0;
// nEnd is end of RTL run:
const xub_StrLen nEnd = GetDirChg( nDirIdx );
if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run
( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( rTxt, nStart, nEnd ) ) ) // non-strong text in embedded LTR run
{
// nScriptIdx points into the ScriptArrays:
size_t nScriptIdx = 0;
// Skip entries in ScriptArray which are not inside the RTL run:
// Make nScriptIdx become the index of the script group with
// 1. nStartPosOfGroup <= nStart and
// 2. nEndPosOfGroup > nStart
while ( GetScriptChg( nScriptIdx ) <= nStart )
++nScriptIdx;
const xub_StrLen nStartPosOfGroup = nScriptIdx ? GetScriptChg( nScriptIdx - 1 ) : 0;
const sal_uInt8 nScriptTypeOfGroup = GetScriptType( nScriptIdx );
ASSERT( nStartPosOfGroup <= nStart && GetScriptChg( nScriptIdx ) > nStart,
"Script override with CTL font trouble" )
// Check if we have to insert a new script change at
// position nStart. If nStartPosOfGroup < nStart,
// we have to insert a new script change:
if ( nStart > 0 && nStartPosOfGroup < nStart )
{
aScriptChg.insert( aScriptChg.begin() + nScriptIdx, nStart );
aScriptType.insert( aScriptType.begin() + nScriptIdx, nScriptTypeOfGroup );
++nScriptIdx;
}
// Remove entries in ScriptArray which end inside the RTL run:
while ( nScriptIdx < aScriptChg.size() && GetScriptChg( nScriptIdx ) <= nEnd )
{
aScriptChg.erase( aScriptChg.begin() + nScriptIdx );
aScriptType.erase( aScriptType.begin() + nScriptIdx );
}
// Insert a new entry in ScriptArray for the end of the RTL run:
aScriptChg.insert( aScriptChg.begin() + nScriptIdx, nEnd );
aScriptType.insert( aScriptType.begin() + nScriptIdx, i18n::ScriptType::COMPLEX );
#if OSL_DEBUG_LEVEL > 1
sal_uInt8 nScriptType;
sal_uInt8 nLastScriptType = i18n::ScriptType::WEAK;
xub_StrLen nScriptChg;
xub_StrLen nLastScriptChg = 0;
(void) nLastScriptChg;
(void) nLastScriptType;
for ( size_t i2 = 0; i2 < aScriptChg.size(); ++i2 )
{
nScriptChg = GetScriptChg( i2 );
nScriptType = GetScriptType( i2 );
ASSERT( nLastScriptType != nScriptType &&
nLastScriptChg < nScriptChg,
"Heavy InitScriptType() confusion" )
}
#endif
}
}
}
}
void SwScriptInfo::UpdateBidiInfo( const String& rTxt )
{
// remove invalid entries from direction information arrays
aDirChg.clear();
aDirType.clear();
//
// Bidi functions from icu 2.0
//
UErrorCode nError = U_ZERO_ERROR;
UBiDi* pBidi = ubidi_openSized( rTxt.Len(), 0, &nError );
nError = U_ZERO_ERROR;
ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(rTxt.GetBuffer()), rTxt.Len(), // UChar != sal_Unicode in MinGW
nDefaultDir, NULL, &nError );
nError = U_ZERO_ERROR;
long nCount = ubidi_countRuns( pBidi, &nError );
int32_t nStart = 0;
int32_t nEnd;
UBiDiLevel nCurrDir;
// counter for direction information arrays
for ( sal_uInt16 nIdx = 0; nIdx < nCount; ++nIdx )
{
ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
aDirChg.push_back( (sal_uInt16)nEnd );
aDirType.push_back( (sal_uInt8)nCurrDir );
nStart = nEnd;
}
ubidi_close( pBidi );
}
/*************************************************************************
* SwScriptInfo::NextScriptChg(..)
* 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
* STRING_LEN.
* Scripts are Asian (Chinese, Japanese, Korean),
* Latin ( English etc.)
* and Complex ( Hebrew, Arabian )
*************************************************************************/
xub_StrLen SwScriptInfo::NextScriptChg( const xub_StrLen nPos ) const
{
sal_uInt16 nEnd = CountScriptChg();
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
if( nPos < GetScriptChg( nX ) )
return GetScriptChg( nX );
}
return STRING_LEN;
}
/*************************************************************************
* SwScriptInfo::ScriptType(..)
* returns the script of the character at the input position
*************************************************************************/
sal_uInt8 SwScriptInfo::ScriptType( const xub_StrLen nPos ) const
{
sal_uInt16 nEnd = CountScriptChg();
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
if( nPos < GetScriptChg( nX ) )
return GetScriptType( nX );
}
// the default is the application language script
return (sal_uInt8)GetI18NScriptTypeOfLanguage( (sal_uInt16)GetAppLanguage() );
}
xub_StrLen SwScriptInfo::NextDirChg( const xub_StrLen nPos,
const sal_uInt8* pLevel ) const
{
sal_uInt8 nCurrDir = pLevel ? *pLevel : 62;
sal_uInt16 nEnd = CountDirChg();
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
if( nPos < GetDirChg( nX ) &&
( nX + 1 == nEnd || GetDirType( nX + 1 ) <= nCurrDir ) )
return GetDirChg( nX );
}
return STRING_LEN;
}
sal_uInt8 SwScriptInfo::DirType( const xub_StrLen nPos ) const
{
sal_uInt16 nEnd = CountDirChg();
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
if( nPos < GetDirChg( nX ) )
return GetDirType( nX );
}
return 0;
}
/*************************************************************************
* SwScriptInfo::MaskHiddenRanges(..)
* Takes a string and replaced the hidden ranges with cChar.
**************************************************************************/
sal_uInt16 SwScriptInfo::MaskHiddenRanges( const SwTxtNode& rNode, XubString& rText,
const xub_StrLen nStt, const xub_StrLen nEnd,
const xub_Unicode cChar )
{
ASSERT( rNode.GetTxt().Len() == rText.Len(), "MaskHiddenRanges, string len mismatch" )
PositionList aList;
xub_StrLen nHiddenStart;
xub_StrLen nHiddenEnd;
sal_uInt16 nNumOfHiddenChars = 0;
GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList );
PositionList::const_reverse_iterator rFirst( aList.end() );
PositionList::const_reverse_iterator rLast( aList.begin() );
while ( rFirst != rLast )
{
nHiddenEnd = *(rFirst++);
nHiddenStart = *(rFirst++);
if ( nHiddenEnd < nStt || nHiddenStart > nEnd )
continue;
while ( nHiddenStart < nHiddenEnd && nHiddenStart < nEnd )
{
if ( nHiddenStart >= nStt && nHiddenStart < nEnd )
{
rText.SetChar( nHiddenStart, cChar );
++nNumOfHiddenChars;
}
++nHiddenStart;
}
}
return nNumOfHiddenChars;
}
/*************************************************************************
* SwScriptInfo::DeleteHiddenRanges(..)
* Takes a SwTxtNode and deletes the hidden ranges from the node.
**************************************************************************/
void SwScriptInfo::DeleteHiddenRanges( SwTxtNode& rNode )
{
PositionList aList;
xub_StrLen nHiddenStart;
xub_StrLen nHiddenEnd;
GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList );
PositionList::const_reverse_iterator rFirst( aList.end() );
PositionList::const_reverse_iterator rLast( aList.begin() );
while ( rFirst != rLast )
{
nHiddenEnd = *(rFirst++);
nHiddenStart = *(rFirst++);
SwPaM aPam( rNode, nHiddenStart, rNode, nHiddenEnd );
rNode.getIDocumentContentOperations()->DeleteRange( aPam );
}
}
/*************************************************************************
* SwScriptInfo::GetBoundsOfHiddenRange(..)
* static version
**************************************************************************/
bool SwScriptInfo::GetBoundsOfHiddenRange( const SwTxtNode& rNode, xub_StrLen nPos,
xub_StrLen& rnStartPos, xub_StrLen& rnEndPos,
PositionList* pList )
{
rnStartPos = STRING_LEN;
rnEndPos = 0;
bool bNewContainsHiddenChars = false;
//
// Optimization: First examine the flags at the text node:
//
if ( !rNode.IsCalcHiddenCharFlags() )
{
bool bWholePara = rNode.HasHiddenCharAttribute( true );
bool bContainsHiddenChars = rNode.HasHiddenCharAttribute( false );
if ( !bContainsHiddenChars )
return false;
if ( bWholePara )
{
if ( pList )
{
pList->push_back( 0 );
pList->push_back( rNode.GetTxt().Len() );
}
rnStartPos = 0;
rnEndPos = rNode.GetTxt().Len();
return true;
}
}
const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo( rNode );
if ( pSI )
{
//
// Check first, if we have a valid SwScriptInfo object for this text node:
//
bNewContainsHiddenChars = pSI->GetBoundsOfHiddenRange( nPos, rnStartPos, rnEndPos, pList );
const bool bNewHiddenCharsHidePara = ( rnStartPos == 0 && rnEndPos >= rNode.GetTxt().Len() );
rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars );
}
else
{
//
// No valid SwScriptInfo Object, we have to do it the hard way:
//
Range aRange( 0, rNode.GetTxt().Len() ? rNode.GetTxt().Len() - 1 : 0 );
MultiSelection aHiddenMulti( aRange );
SwScriptInfo::CalcHiddenRanges( rNode, aHiddenMulti );
for( sal_uInt16 i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
{
const Range& rRange = aHiddenMulti.GetRange( i );
const xub_StrLen nHiddenStart = (xub_StrLen)rRange.Min();
const xub_StrLen nHiddenEnd = (xub_StrLen)rRange.Max() + 1;
if ( nHiddenStart > nPos )
break;
else if ( nHiddenStart <= nPos && nPos < nHiddenEnd )
{
rnStartPos = nHiddenStart;
rnEndPos = Min( nHiddenEnd, rNode.GetTxt().Len() );
break;
}
}
if ( pList )
{
for( sal_uInt16 i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
{
const Range& rRange = aHiddenMulti.GetRange( i );
pList->push_back( (xub_StrLen)rRange.Min() );
pList->push_back( (xub_StrLen)rRange.Max() + 1 );
}
}
bNewContainsHiddenChars = aHiddenMulti.GetRangeCount() > 0;
}
return bNewContainsHiddenChars;
}
/*************************************************************************
* SwScriptInfo::GetBoundsOfHiddenRange(..)
* non-static version
**************************************************************************/
bool SwScriptInfo::GetBoundsOfHiddenRange( xub_StrLen nPos, xub_StrLen& rnStartPos,
xub_StrLen& rnEndPos, PositionList* pList ) const
{
rnStartPos = STRING_LEN;
rnEndPos = 0;
sal_uInt16 nEnd = CountHiddenChg();
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
const xub_StrLen nHiddenStart = GetHiddenChg( nX++ );
const xub_StrLen nHiddenEnd = GetHiddenChg( nX );
if ( nHiddenStart > nPos )
break;
else if ( nHiddenStart <= nPos && nPos < nHiddenEnd )
{
rnStartPos = nHiddenStart;
rnEndPos = nHiddenEnd;
break;
}
}
if ( pList )
{
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
pList->push_back( GetHiddenChg( nX++ ) );
pList->push_back( GetHiddenChg( nX ) );
}
}
return CountHiddenChg() > 0;
}
/*************************************************************************
* SwScriptInfo::IsInHiddenRange()
**************************************************************************/
bool SwScriptInfo::IsInHiddenRange( const SwTxtNode& rNode, xub_StrLen nPos )
{
xub_StrLen nStartPos;
xub_StrLen nEndPos;
SwScriptInfo::GetBoundsOfHiddenRange( rNode, nPos, nStartPos, nEndPos );
return nStartPos != STRING_LEN;
}
#if OSL_DEBUG_LEVEL > 1
/*************************************************************************
* SwScriptInfo::CompType(..)
* returns the type of the compressed character
*************************************************************************/
sal_uInt8 SwScriptInfo::CompType( const xub_StrLen nPos ) const
{
sal_uInt16 nEnd = CountCompChg();
for( sal_uInt16 nX = 0; nX < nEnd; ++nX )
{
xub_StrLen nChg = GetCompStart( nX );
if ( nPos < nChg )
return NONE;
if( nPos < nChg + GetCompLen( nX ) )
return GetCompType( nX );
}
return NONE;
}
#endif
/*************************************************************************
* SwScriptInfo::HasKana()
* returns, if there are compressable kanas or specials
* betwenn nStart and nEnd
*************************************************************************/
sal_uInt16 SwScriptInfo::HasKana( xub_StrLen nStart, const xub_StrLen nLen ) const
{
sal_uInt16 nCnt = CountCompChg();
xub_StrLen nEnd = nStart + nLen;
for( sal_uInt16 nX = 0; nX < nCnt; ++nX )
{
xub_StrLen nKanaStart = GetCompStart( nX );
xub_StrLen nKanaEnd = nKanaStart + GetCompLen( nX );
if ( nKanaStart >= nEnd )
return USHRT_MAX;
if ( nStart < nKanaEnd )
return nX;
}
return USHRT_MAX;
}
/*************************************************************************
* SwScriptInfo::Compress()
*************************************************************************/
long SwScriptInfo::Compress( sal_Int32* pKernArray, xub_StrLen nIdx, xub_StrLen nLen,
const sal_uInt16 nCompress, const sal_uInt16 nFontHeight,
Point* pPoint ) const
{
ASSERT( nCompress, "Compression without compression?!" );
ASSERT( nLen, "Compression without text?!" );
sal_uInt16 nCompCount = CountCompChg();
// 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
sal_uInt16 nMinWidth = ( 3 * nFontHeight ) / 4;
sal_uInt16 nCompIdx = HasKana( nIdx, nLen );
if ( USHRT_MAX == nCompIdx )
return 0;
xub_StrLen nChg = GetCompStart( nCompIdx );
xub_StrLen nCompLen = GetCompLen( nCompIdx );
sal_uInt16 nI = 0;
nLen = nLen + nIdx;
if( nChg > nIdx )
{
nI = nChg - nIdx;
nIdx = nChg;
}
else if( nIdx < nChg + nCompLen )
nCompLen -= nIdx - nChg;
if( nIdx > nLen || nCompIdx >= nCompCount )
return 0;
long nSub = 0;
long nLast = nI ? pKernArray[ nI - 1 ] : 0;
do
{
sal_uInt16 nType = GetCompType( nCompIdx );
#if OSL_DEBUG_LEVEL > 1
ASSERT( nType == CompType( nIdx ), "Gimme the right type!" );
#endif
nCompLen = nCompLen + nIdx;
if( nCompLen > nLen )
nCompLen = nLen;
// are we allowed to compress the character?
if ( pKernArray[ nI ] - nLast < nMinWidth )
{
nIdx++; nI++;
}
else
{
while( nIdx < nCompLen )
{
ASSERT( SwScriptInfo::NONE != nType, "None compression?!" );
// nLast is width of current character
nLast -= pKernArray[ nI ];
nLast *= nCompress;
long nMove = 0;
if( SwScriptInfo::KANA != nType )
{
nLast /= 20000;
if( pPoint && SwScriptInfo::SPECIAL_LEFT == nType )
{
if( nI )
nMove = nLast;
else
{
pPoint->X() += nLast;
nLast = 0;
}
}
}
else
nLast /= 100000;
nSub -= nLast;
nLast = pKernArray[ nI ];
if( nMove )
pKernArray[ nI - 1 ] += nMove;
pKernArray[ nI++ ] -= nSub;
++nIdx;
}
}
if( nIdx < nLen )
{
xub_StrLen nTmpChg;
if( ++nCompIdx < nCompCount )
{
nTmpChg = GetCompStart( nCompIdx );
if( nTmpChg > nLen )
nTmpChg = nLen;
nCompLen = GetCompLen( nCompIdx );
}
else
nTmpChg = nLen;
while( nIdx < nTmpChg )
{
nLast = pKernArray[ nI ];
pKernArray[ nI++ ] -= nSub;
++nIdx;
}
}
else
break;
} while( nIdx < nLen );
return nSub;
}
/*************************************************************************
* SwScriptInfo::KashidaJustify()
*************************************************************************/
// Note on calling KashidaJustify():
// Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
// total number of kashida positions, or the number of kashida positions after some positions
// have been dropped, depending on the state of the aKashidaInvalid array.
sal_uInt16 SwScriptInfo::KashidaJustify( sal_Int32* pKernArray,
sal_Int32* pScrArray,
xub_StrLen nStt,
xub_StrLen nLen,
long nSpaceAdd ) const
{
ASSERT( nLen, "Kashida justification without text?!" )
if( !IsKashidaLine(nStt))
return STRING_LEN;
// evaluate kashida informatin in collected in SwScriptInfo
sal_uInt16 nCntKash = 0;
while( nCntKash < CountKashida() )
{
if ( nStt <= GetKashida( nCntKash ) )
break;
else
nCntKash++;
}
const xub_StrLen nEnd = nStt + nLen;
sal_uInt16 nCntKashEnd = nCntKash;
while ( nCntKashEnd < CountKashida() )
{
if ( nEnd <= GetKashida( nCntKashEnd ) )
break;
else
nCntKashEnd++;
}
sal_uInt16 nActualKashCount = nCntKashEnd - nCntKash;
for ( sal_uInt16 i = nCntKash; i < nCntKashEnd; ++i )
{
if ( nActualKashCount && !IsKashidaValid ( i ) )
--nActualKashCount;
}
if ( !pKernArray )
return nActualKashCount;
// do nothing if there is no more kashida
if ( nCntKash < CountKashida() )
{
// skip any invalid kashidas
while ( ! IsKashidaValid ( nCntKash ) && nCntKash < nCntKashEnd )
++nCntKash;
xub_StrLen nKashidaPos = GetKashida( nCntKash );
xub_StrLen nIdx = nKashidaPos;
long nKashAdd = nSpaceAdd;
while ( nIdx < nEnd )
{
sal_uInt16 nArrayPos = nIdx - nStt;
// next kashida position
++nCntKash;
while ( ! IsKashidaValid ( nCntKash ) && nCntKash < nCntKashEnd )
++nCntKash;
nIdx = nCntKash < CountKashida() && IsKashidaValid ( nCntKash ) ? GetKashida( nCntKash ) : nEnd;
if ( nIdx > nEnd )
nIdx = nEnd;
const sal_uInt16 nArrayEnd = nIdx - nStt;
while ( nArrayPos < nArrayEnd )
{
pKernArray[ nArrayPos ] += nKashAdd;
if ( pScrArray )
pScrArray[ nArrayPos ] += nKashAdd;
++nArrayPos;
}
nKashAdd += nSpaceAdd;
}
}
return 0;
}
/*************************************************************************
* SwScriptInfo::IsArabicText()
*
* Checks if the current text is 'Arabic' text. Note that only the first
* character has to be checked because a ctl portion only contains one
* script, see NewTxtPortion
*************************************************************************/
sal_Bool SwScriptInfo::IsArabicText( const XubString& rTxt, xub_StrLen nStt, xub_StrLen nLen )
{
using namespace ::com::sun::star::i18n;
static ScriptTypeList typeList[] = {
{ UnicodeScript_kArabic, UnicodeScript_kArabic, UnicodeScript_kArabic }, // 11,
{ UnicodeScript_kScriptCount, UnicodeScript_kScriptCount, UnicodeScript_kScriptCount } // 88
};
// go forward if current position does not hold a regular character:
const CharClass& rCC = GetAppCharClass();
sal_Int32 nIdx = nStt;
const xub_StrLen nEnd = nStt + nLen;
while ( nIdx < nEnd && !rCC.isLetterNumeric( rTxt, (xub_StrLen)nIdx ) )
{
++nIdx;
}
if( nIdx == nEnd )
{
// no regular character found in this portion. Go backward:
--nIdx;
while ( nIdx >= 0 && !rCC.isLetterNumeric( rTxt, (xub_StrLen)nIdx ) )
{
--nIdx;
}
}
if( nIdx >= 0 )
{
const xub_Unicode cCh = rTxt.GetChar( (xub_StrLen)nIdx );
const sal_Int16 type = unicode::getUnicodeScriptType( cCh, typeList, UnicodeScript_kScriptCount );
return type == UnicodeScript_kArabic;
}
return sal_False;
}
/*************************************************************************
* SwScriptInfo::IsKashidaValid()
*************************************************************************/
sal_Bool SwScriptInfo::IsKashidaValid ( xub_StrLen nKashPos ) const
{
for ( size_t i = 0; i < aKashidaInvalid.size(); ++i )
{
if ( aKashidaInvalid [ i ] == nKashPos )
return false;
}
return true;
}
/*************************************************************************
* SwScriptInfo::ClearKashidaInvalid()
*************************************************************************/
void SwScriptInfo::ClearKashidaInvalid ( xub_StrLen nKashPos )
{
for ( size_t i = 0; i < aKashidaInvalid.size(); ++i )
{
if ( aKashidaInvalid [ i ] == nKashPos )
{
aKashidaInvalid.erase ( aKashidaInvalid.begin() + i );
return;
}
}
}
/*************************************************************************
* SwScriptInfo::MarkOrClearKashidaInvalid()
*************************************************************************/
// bMark == true:
// marks the first valid kashida in the given text range as invalid
// bMark == false:
// clears all kashida invalid flags in the given text range
bool SwScriptInfo::MarkOrClearKashidaInvalid ( xub_StrLen nStt, xub_StrLen nLen, bool bMark, xub_StrLen nMarkCount )
{
sal_uInt16 nCntKash = 0;
while( nCntKash < CountKashida() )
{
if ( nStt <= GetKashida( nCntKash ) )
break;
else
nCntKash++;
}
const xub_StrLen nEnd = nStt + nLen;
while ( nCntKash < CountKashida() )
{
if ( nEnd <= GetKashida( nCntKash ) )
break;
else
{
if(bMark)
{
if ( IsKashidaValid ( nCntKash ) )
{
MarkKashidaInvalid ( nCntKash );
--nMarkCount;
if(!nMarkCount)
return true;
}
}
else
{
ClearKashidaInvalid ( nCntKash );
}
nCntKash++;
}
}
return false;
}
void SwScriptInfo::MarkKashidaInvalid ( xub_StrLen nKashPos )
{
aKashidaInvalid.push_back( nKashPos );
}
/*************************************************************************
* SwScriptInfo::GetKashidaPositions()
*************************************************************************/
// retrieve the kashida positions in the given text range
sal_uInt16 SwScriptInfo::GetKashidaPositions ( xub_StrLen nStt, xub_StrLen nLen,
xub_StrLen* pKashidaPosition )
{
sal_uInt16 nCntKash = 0;
while( nCntKash < CountKashida() )
{
if ( nStt <= GetKashida( nCntKash ) )
break;
else
nCntKash++;
}
const xub_StrLen nEnd = nStt + nLen;
sal_uInt16 nCntKashEnd = nCntKash;
while ( nCntKashEnd < CountKashida() )
{
if ( nEnd <= GetKashida( nCntKashEnd ) )
break;
else
{
pKashidaPosition [ nCntKashEnd - nCntKash ] = GetKashida ( nCntKashEnd );
nCntKashEnd++;
}
}
return nCntKashEnd - nCntKash;
}
void SwScriptInfo::SetNoKashidaLine ( xub_StrLen nStt, xub_StrLen nLen )
{
aNoKashidaLine.push_back( nStt );
aNoKashidaLineEnd.push_back( nStt+nLen );
}
/*************************************************************************
* SwScriptInfo::IsKashidaLine()
*************************************************************************/
// determines if the line uses kashida justification
bool SwScriptInfo::IsKashidaLine ( xub_StrLen nCharIdx ) const
{
for( size_t i = 0; i < aNoKashidaLine.size(); ++i )
{
if( nCharIdx >= aNoKashidaLine[ i ] && nCharIdx < aNoKashidaLineEnd[ i ])
return false;
}
return true;
}
/*************************************************************************
* SwScriptInfo::ClearKashidaLine()
*************************************************************************/
void SwScriptInfo::ClearNoKashidaLine ( xub_StrLen nStt, xub_StrLen nLen )
{
size_t i = 0;
while( i < aNoKashidaLine.size())
{
if( nStt + nLen >= aNoKashidaLine[ i ] && nStt < aNoKashidaLineEnd [ i ] )
{
aNoKashidaLine.erase(aNoKashidaLine.begin() + i);
aNoKashidaLineEnd.erase(aNoKashidaLineEnd.begin() + i);
}
else
++i;
}
}
/*************************************************************************
* SwScriptInfo::MarkKashidasInvalid()
*************************************************************************/
// mark the given character indices as invalid kashida positions
bool SwScriptInfo::MarkKashidasInvalid ( xub_StrLen nCnt, xub_StrLen* pKashidaPositions )
{
ASSERT( pKashidaPositions && nCnt > 0, "Where are kashidas?" )
sal_uInt16 nCntKash = 0;
xub_StrLen nKashidaPosIdx = 0;
while ( nCntKash < CountKashida() && nKashidaPosIdx < nCnt )
{
if ( pKashidaPositions [nKashidaPosIdx] > GetKashida( nCntKash ) )
{
nCntKash++;
continue;
}
if ( pKashidaPositions [nKashidaPosIdx] == GetKashida( nCntKash ) && IsKashidaValid ( nCntKash ) )
{
MarkKashidaInvalid ( nCntKash );
}
else
return false; // something is wrong
nKashidaPosIdx++;
}
return true;
}
/*************************************************************************
* SwScriptInfo::ThaiJustify()
*************************************************************************/
sal_uInt16 SwScriptInfo::ThaiJustify( const XubString& rTxt, sal_Int32* pKernArray,
sal_Int32* pScrArray, xub_StrLen nStt,
xub_StrLen nLen, xub_StrLen nNumberOfBlanks,
long nSpaceAdd )
{
ASSERT( nStt + nLen <= rTxt.Len(), "String in ThaiJustify too small" )
SwTwips nNumOfTwipsToDistribute = nSpaceAdd * nNumberOfBlanks /
SPACING_PRECISION_FACTOR;
long nSpaceSum = 0;
sal_uInt16 nCnt = 0;
for ( sal_uInt16 nI = 0; nI < nLen; ++nI )
{
const xub_Unicode cCh = rTxt.GetChar( nStt + nI );
// check if character is not above or below base
if ( ( 0xE34 > cCh || cCh > 0xE3A ) &&
( 0xE47 > cCh || cCh > 0xE4E ) && cCh != 0xE31 )
{
if ( nNumberOfBlanks > 0 )
{
nSpaceAdd = nNumOfTwipsToDistribute / nNumberOfBlanks;
--nNumberOfBlanks;
nNumOfTwipsToDistribute -= nSpaceAdd;
}
nSpaceSum += nSpaceAdd;
++nCnt;
}
if ( pKernArray ) pKernArray[ nI ] += nSpaceSum;
if ( pScrArray ) pScrArray[ nI ] += nSpaceSum;
}
return nCnt;
}
/*************************************************************************
* SwScriptInfo::GetScriptInfo()
*************************************************************************/
SwScriptInfo* SwScriptInfo::GetScriptInfo( const SwTxtNode& rTNd,
sal_Bool bAllowInvalid )
{
SwIterator<SwTxtFrm,SwTxtNode> aIter( rTNd );
SwScriptInfo* pScriptInfo = 0;
for( SwTxtFrm* pLast = aIter.First(); pLast; pLast = aIter.Next() )
{
pScriptInfo = (SwScriptInfo*)pLast->GetScriptInfo();
if ( pScriptInfo )
{
if ( !bAllowInvalid && STRING_LEN != pScriptInfo->GetInvalidity() )
pScriptInfo = 0;
else break;
}
}
return pScriptInfo;
}
/*************************************************************************
* SwParaPortion::SwParaPortion()
*************************************************************************/
SwParaPortion::SwParaPortion()
{
FormatReset();
bFlys = bFtnNum = bMargin = sal_False;
SetWhichPor( POR_PARA );
}
/*************************************************************************
* SwParaPortion::~SwParaPortion()
*************************************************************************/
SwParaPortion::~SwParaPortion()
{
}
/*************************************************************************
* SwParaPortion::GetParLen()
*************************************************************************/
xub_StrLen SwParaPortion::GetParLen() const
{
xub_StrLen nLen = 0;
const SwLineLayout *pLay = this;
while( pLay )
{
DBG_LOOP;
nLen = nLen + pLay->GetLen();
pLay = pLay->GetNext();
}
return nLen;
}
/*************************************************************************
* SwParaPortion::FindDropPortion()
*************************************************************************/
const SwDropPortion *SwParaPortion::FindDropPortion() const
{
const SwLineLayout *pLay = this;
while( pLay && pLay->IsDummy() )
pLay = pLay->GetNext();
while( pLay )
{
const SwLinePortion *pPos = pLay->GetPortion();
while ( pPos && !pPos->GetLen() )
pPos = pPos->GetPortion();
if( pPos && pPos->IsDropPortion() )
return (SwDropPortion *)pPos;
pLay = pLay->GetLen() ? NULL : pLay->GetNext();
}
return NULL;
}
/*************************************************************************
* SwLineLayout::Init()
*************************************************************************/
void SwLineLayout::Init( SwLinePortion* pNextPortion )
{
Height( 0 );
Width( 0 );
SetLen( 0 );
SetAscent( 0 );
SetRealHeight( 0 );
SetPortion( pNextPortion );
}
/*-----------------16.11.00 11:04-------------------
* HangingMargin()
* 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 atualized.
* --------------------------------------------------*/
SwTwips SwLineLayout::_GetHangingMargin() const
{
SwLinePortion* pPor = GetPortion();
sal_Bool bFound = sal_False;
SwTwips nDiff = 0;
while( pPor)
{
if( pPor->IsHangingPortion() )
{
nDiff = ((SwHangingPortion*)pPor)->GetInnerWidth() - pPor->Width();
if( nDiff )
bFound = sal_True;
}
// the last post its portion
else if ( pPor->IsPostItsPortion() && ! pPor->GetPortion() )
nDiff = nAscent;
pPor = pPor->GetPortion();
}
if( !bFound ) // actualize the hanging-flag
((SwLineLayout*)this)->SetHanging( sal_False );
return nDiff;
}
SwTwips SwTxtFrm::HangingMargin() const
{
ASSERT( HasPara(), "Don't call me without a paraportion" );
if( !GetPara()->IsMargin() )
return 0;
const SwLineLayout* pLine = GetPara();
SwTwips nRet = 0;
do
{
SwTwips nDiff = pLine->GetHangingMargin();
if( nDiff > nRet )
nRet = nDiff;
pLine = pLine->GetNext();
} while ( pLine );
if( !nRet ) // actualize the margin-flag
((SwParaPortion*)GetPara())->SetMargin( sal_False );
return nRet;
}
/*************************************************************************
* SwScriptInfo::CalcHiddenRanges()
*
* Returns a MultiSection indicating the hidden ranges.
*************************************************************************/
void SwScriptInfo::CalcHiddenRanges( const SwTxtNode& rNode, MultiSelection& rHiddenMulti )
{
const SfxPoolItem* pItem = 0;
if( SFX_ITEM_SET == rNode.GetSwAttrSet().GetItemState( RES_CHRATR_HIDDEN, sal_True, &pItem ) &&
((SvxCharHiddenItem*)pItem)->GetValue() )
{
rHiddenMulti.SelectAll();
}
const SwpHints* pHints = rNode.GetpSwpHints();
const SwTxtAttr* pTxtAttr = 0;
if( pHints )
{
MSHORT nTmp = 0;
while( nTmp < pHints->GetStartCount() )
{
pTxtAttr = pHints->GetStart( nTmp++ );
const SvxCharHiddenItem* pHiddenItem = static_cast<const SvxCharHiddenItem*>( CharFmt::GetItem( *pTxtAttr, RES_CHRATR_HIDDEN ) );
if( pHiddenItem )
{
const xub_StrLen nSt = *pTxtAttr->GetStart();
const xub_StrLen nEnd = *pTxtAttr->End();
if( nEnd > nSt )
{
Range aTmp( nSt, nEnd - 1 );
rHiddenMulti.Select( aTmp, pHiddenItem->GetValue() );
}
}
}
}
// If there are any hidden ranges in the current text node, we have
// to unhide the redlining ranges:
const IDocumentRedlineAccess& rIDRA = *rNode.getIDocumentRedlineAccess();
if ( rHiddenMulti.GetRangeCount() && IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineMode() ) )
{
sal_uInt16 nAct = rIDRA.GetRedlinePos( rNode, USHRT_MAX );
for ( ; nAct < rIDRA.GetRedlineTbl().Count(); nAct++ )
{
const SwRedline* pRed = rIDRA.GetRedlineTbl()[ nAct ];
if ( pRed->Start()->nNode > rNode.GetIndex() )
break;
xub_StrLen nRedlStart;
xub_StrLen nRedlnEnd;
pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd );
if ( nRedlnEnd > nRedlStart )
{
Range aTmp( nRedlStart, nRedlnEnd - 1 );
rHiddenMulti.Select( aTmp, false );
}
}
}
//
// We calculated a lot of stuff. Finally we can update the flags at the text node.
//
const bool bNewContainsHiddenChars = rHiddenMulti.GetRangeCount() > 0;
bool bNewHiddenCharsHidePara = false;
if ( bNewContainsHiddenChars )
{
const Range& rRange = rHiddenMulti.GetRange( 0 );
const xub_StrLen nHiddenStart = (xub_StrLen)rRange.Min();
const xub_StrLen nHiddenEnd = (xub_StrLen)rRange.Max() + 1;
bNewHiddenCharsHidePara = ( nHiddenStart == 0 && nHiddenEnd >= rNode.GetTxt().Len() );
}
rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars );
}