/*
* Copyright (c) 2002 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: Avkon text wrapper implementation
* This class is used by AknTextUtils and AknBidiTextUtils.
*
*/
// INCLUDE FILES
#include "AknTextWrapper.h"
#include "aknenv.h"
#include "AknBidiTextUtils.h"
#include <gdi.h>
#include <uikon.hrh>
#include <bidi.h>
#include <bidivisual.h>
// CONSTANTS
// line feed, carriage return, line separator, paragraph separator
_LIT( KSeparators, "\x000a\x000d\x2028\x2029" );
const TText KLineFeed = 0x000A;
const TText KCarriageReturn = 0x000D;
const TText KLineSeparator = 0x2028;
const TText KParagraphSeparator = 0x2029;
const TText KZeroWidthSpace = 0x200B;
const TText KFirstThaiCharacter = 0x0E01;
const TText KLastThaiCharacter = 0x0E5B;
enum
{
EWrapToArray = 0,
EChopToArrayAndClip = 1
};
const TText KAknLocVariantSeparator = 0x0001;
// Helper classes for User::QuickSort.
NONSHARABLE_CLASS(TMyKey) : public TKey
{
// From TKey
private:
TInt Compare( TInt aLeft, TInt aRight ) const;
TAny* At( TInt aIndex ) const;
};
TInt TMyKey::Compare( TInt aLeft, TInt aRight ) const
{
const TAknLocVariant* ptr = static_cast<const TAknLocVariant*>( iPtr );
TInt leftLength = ptr[aLeft].iEnd - ptr[aLeft].iStart;
TInt rightLength = ptr[aRight].iEnd - ptr[aRight].iStart;
return rightLength - leftLength;
}
TAny* TMyKey::At( TInt aIndex ) const
{
const TAknLocVariant* ptr = static_cast<const TAknLocVariant*>( iPtr );
return (TAny*)( &ptr[aIndex] );
}
NONSHARABLE_CLASS(TMySwap) : public TSwap
{
// Constructor
public:
inline TMySwap( TAknLocVariant* aVariants ) : iVariants( aVariants ) {}
// From TSwap
private:
void Swap( TInt aLeft, TInt aRight ) const;
// Data
private:
TAknLocVariant* iVariants;
};
void TMySwap::Swap( TInt aLeft, TInt aRight ) const
{
TAknLocVariant temp = iVariants[aLeft];
iVariants[aLeft] = iVariants[aRight];
iVariants[aRight] = temp;
}
// ============================ MEMBER FUNCTIONS ===============================
// -----------------------------------------------------------------------------
// TAknTextWrapper::TAknTextWrapper
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
TAknTextWrapper::TAknTextWrapper(
TDes& aStringToWrap,
const CArrayFix<TInt>* aLineWidthArray,
const CFont& aFont,
CArrayFix<TPtrC>& aWrappedArray,
TInt aLineWidth,
TInt aFlags,
AknBidiTextUtils::TParagraphDirectionality aDirectionality ) :
iSourceString( aStringToWrap ),
iLineWidthArray( aLineWidthArray ),
iFont( aFont ),
iResultArray( aWrappedArray ),
iLineWidth( aLineWidth ),
iFlags( aFlags ),
iDirectionality( aDirectionality ),
iSeparators( KSeparators() ),
iText( NULL, 0 )
{
}
// Destructor
TAknTextWrapper::~TAknTextWrapper()
{
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::WrapToArrayL
// -----------------------------------------------------------------------------
//
HBufC* TAknTextWrapper::WrapToArrayL()
{
return DoOperationL( EWrapToArray );
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::ChopToArrayAndClipL
// -----------------------------------------------------------------------------
//
HBufC* TAknTextWrapper::ChopToArrayAndClipL()
{
return DoOperationL( EChopToArrayAndClip );
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::DoOperationL
// -----------------------------------------------------------------------------
//
HBufC* TAknTextWrapper::DoOperationL( TInt aOp )
{
iResultArray.Reset();
if ( !iSourceString.Length() )
{
if ( iFlags & EReserveVisualBuffer )
{
return HBufC::NewL( 0 );
}
else
{
return NULL;
}
}
HBufC* ret = NULL;
TAknLocVariant variants[KAknMaxLocVariants];
TInt numVariants = GetLocVariants( iSourceString, variants );
iFlags &= ~EFits;
TInt i = 0;
HBufC* backupBuf = iSourceString.AllocLC();
for ( ; i < numVariants && !(iFlags & EFits); i++ )
{
delete ret;
ret = NULL;
TInt start = variants[i].iStart;
iSourceString.Copy(
backupBuf->Mid( start, variants[i].iEnd - start ) );
iFlags |= EFits;
// Clears EFits if truncated.
if ( aOp == EWrapToArray )
{
ret = DoWrapToArrayL();
}
else
{
ret = DoChopToArrayAndClipL();
}
}
CleanupStack::PopAndDestroy(); // backupBuf
return ret;
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::DoWrapToArrayL
// -----------------------------------------------------------------------------
//
HBufC* TAknTextWrapper::DoWrapToArrayL()
{
iResultArray.Reset();
// TBidiLogicalToVisual crashes with length 0 text so we check that case here
if ( !iSourceString.Length() )
{
if ( iFlags & EReserveVisualBuffer )
{
return HBufC::NewL( 0 );
}
else
{
return NULL;
}
}
TInt index( 0 );
TInt lineIndex( 0 );
TInt maxLines( KMaxTInt );
if ( iLineWidthArray && !( iFlags & EWrapAllText ) )
{
maxLines = iLineWidthArray->Count();
}
while ( index < iSourceString.Length() && iResultArray.Count() < maxLines )
{
TInt lineWidth = iLineWidth;
if ( iLineWidthArray )
{
TInt givenWidths = iLineWidthArray->Count();
TInt usedLineWidthIndex = lineIndex;
if ( lineIndex >= givenWidths )
{
usedLineWidthIndex = givenWidths - 1;
}
lineWidth = (*iLineWidthArray)[usedLineWidthIndex];
}
iText.Set( iSourceString.Right( iSourceString.Length() - index ) );
TInt fitsInLine = iFont.TextCount( iText, lineWidth );
// If the line width is constant and no characters fit in it, stop.
if ( !fitsInLine &&
(!iLineWidthArray || lineIndex >= iLineWidthArray->Count()) )
{
break;
}
// Is there an explicit line break in the part that fits in line
TInt newLine = FindLineBreak( iText.Left( fitsInLine ) );
if ( newLine != KErrNotFound )
{
iNextLineStart = newLine;
PassLineBreak(); // updates iNextLineStart
if ( iNextLineStart == iText.Length() )
{
AppendToArrayL( iText.Left( newLine ) );
break;
}
}
// All remaining text fits in line?
else if ( iText.Length() == fitsInLine )
{
AppendToArrayL( iText );
break;
}
TInt wrapIndex( 0 );
// Last line? If so, clip it if required...
if ( iFlags & EClip && lineIndex == maxLines - 1 )
{
if ( newLine != KErrNotFound )
{
iText.Set( iText.Left( newLine ) );
}
// This actually inserts ellipsis in text only if logical to
// visual conversion is not used.
wrapIndex = InsertEllipsis( iText, lineWidth );
// This flag informs logical to visual conversion to insert
// truncation character
iFlags |= EClipRequired;
iFlags &= ~EFits;
AppendToArrayL( iText.Left( wrapIndex ) );
break;
}
// Not last line, so no clipping required yet
if ( newLine != KErrNotFound )
{
wrapIndex = newLine;
}
// No explicit line break in the part that fits in line,
// find wrapping potision
else
{
if ( GetWrappingPosition( iText, fitsInLine, lineWidth, ETrue ) )
{
wrapIndex = iBreakPos;
}
else
{
// No legal wrapping position found => illegal line breaking.
// Put all that fits in line.
wrapIndex = fitsInLine;
iNextLineStart = fitsInLine;
}
PassLineBreak(); // updates iNextLineStart
}
AppendToArrayL( iText.Left( wrapIndex ) );
index += iNextLineStart;
// next line to be wrapped
lineIndex++;
}
return ConvertToVisualIfRequiredL();
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::DoChopToArrayAndClipL
// -----------------------------------------------------------------------------
//
HBufC* TAknTextWrapper::DoChopToArrayAndClipL()
{
iResultArray.Reset();
// First determine succifient amount of text lines for visual buffer.
TInt maxLines = 0;
if ( iLineWidthArray )
{
maxLines = iLineWidthArray->Count();
}
else
{
TInt length = iSourceString.Length();
const TText* text = iSourceString.Ptr();
// This counts 2 lines for lines ending to CR+LF but it does not
// really matter as we are determining a succifient number of lines.
for ( TInt i = 0 ; i < length ; i++ )
{
if ( iSeparators.Locate( text[i] ) != KErrNotFound )
{
maxLines++;
}
}
maxLines++; // add one extra for the line after the last line break.
}
// Allocate visual buffer.
HBufC* visualBuffer = HBufC::NewLC(
iSourceString.Length() + maxLines * KAknBidiExtraSpacePerLine );
TInt index( 0 ); // index in iSourceString
TInt lineIndex( 0 ); // line number
TInt visualIndex( 0 ); // index in visualBuffer
// We utilize wrapping functionality for each line to be chopped.
// These temp arrays are for that purpose.
CArrayFix<TInt>* tempLineWidthArray =
new( ELeave ) CArrayFixFlat<TInt>( 1 );
CleanupStack::PushL( tempLineWidthArray );
tempLineWidthArray->AppendL( 0 );
CArrayFix<TPtrC>* tempResultArray =
new( ELeave ) CArrayFixFlat<TPtrC>( 1 );
CleanupStack::PushL( tempResultArray );
while ( index < iSourceString.Length() && iResultArray.Count() < maxLines )
{
iText.Set( iSourceString.Right( iSourceString.Length() - index ) );
// Locate line break
iNextLineStart = FindLineBreak( iText );
if ( iNextLineStart == KErrNotFound )
{
iNextLineStart = iText.Length();
}
TInt flags( TAknTextWrapper::EClip );
if ( iFlags & EConvertToVisual )
{
flags |= EConvertToVisual;
}
// chop the line in visual buffer
TInt visualRemainingSpace =
visualBuffer->Des().MaxLength() - visualIndex;
const TText* nextLine = visualBuffer->Ptr();
nextLine += visualIndex;
TPtr ptr(
const_cast<TText*>( nextLine ),
visualRemainingSpace,
visualRemainingSpace );
ptr.Copy( iText.Left( iNextLineStart ) );
// we only wrap 1 line
(*tempLineWidthArray)[0] =
iLineWidthArray ? (*iLineWidthArray)[lineIndex] : iLineWidth;
TAknTextWrapper wrapper(
ptr,
tempLineWidthArray,
iFont,
*tempResultArray,
0,
flags,
iDirectionality );
wrapper.WrapToArrayL();
if ( !wrapper.ResultFits() )
{
iFlags &= ~EFits;
}
TInt lineLength = 0;
if ( tempResultArray->Count() )
{
lineLength = (*tempResultArray)[0].Length();
}
visualBuffer->Des().SetLength( visualIndex + lineLength );
if ( iFlags & EReserveVisualBuffer )
{
AppendToArrayL( visualBuffer->Right( lineLength ) );
}
else
{
// Cannot use AppendToArrayL here, because iResultArray is set to point to iSourceString,
// which does not contain the final visual text yet. This would screw up removing trailing spaces.
// It is copied there in the end of this method.
TPtrC currentLine = visualBuffer->Right( lineLength );
TInt trailingSpaces = 0;
TInt trailingIndex = currentLine.Length() - 1;
// Count how many spaces there are in the end of the line.
// Without removing them, text that is aligned to the end of line looks bad.
while ( trailingIndex >= 0 && currentLine[trailingIndex] == ' ' )
{
trailingIndex--;
trailingSpaces++;
}
iResultArray.AppendL( iSourceString.Mid( visualIndex, lineLength - trailingSpaces ) );
}
visualIndex += lineLength;
PassLineBreak();
index += iNextLineStart;
// next line to be wrapped
lineIndex++;
}
CleanupStack::PopAndDestroy( 2 ); // tempLineWidthArray, tempResultArray
if ( iFlags & EReserveVisualBuffer )
{
CleanupStack::Pop();
return visualBuffer;
}
else
{
iSourceString = *visualBuffer;
CleanupStack::PopAndDestroy(); // visualBuffer;
return NULL;
}
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::InsertEllipsis
// -----------------------------------------------------------------------------
//
TInt TAknTextWrapper::InsertEllipsis( const TDesC& aText, TInt aLineWidth )
{
// place ellipsis in the last possible place so that
// the line still fits
TInt count = iFont.TextCount( aText,
aLineWidth - iFont.CharWidthInPixels( KEllipsis ) );
if ( !(iFlags & EConvertToVisual) )
{
TText* text = (TText*)( aText.Ptr() );
text[ count ] = KEllipsis;
return count + 1;
}
else
{
// ellipsis will be added in logical to visual conversion
return count;
}
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::AppendToArrayL
// -----------------------------------------------------------------------------
//
void TAknTextWrapper::AppendToArrayL( const TDesC& aLine )
{
const TText* line = aLine.Ptr();
TInt index = aLine.Length() - 1;
// Remove spaces from the end of the line.
// Otherwise, text that is aligned to the end of line looks bad.
while ( index >= 0 && line[index] == ' ' )
{
index--;
}
iResultArray.AppendL( aLine.Left( index + 1 ) );
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::FindLineBreak
// -----------------------------------------------------------------------------
//
TInt TAknTextWrapper::FindLineBreak( const TDesC& aText )
{
const TText* text = aText.Ptr();
TInt length( aText.Length() );
for ( TInt i = 0 ; i < length ; i++ )
{
if ( iSeparators.Locate( text[i] ) != KErrNotFound )
{
return i;
}
}
return KErrNotFound;
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::GetWrappingPosition
// -----------------------------------------------------------------------------
//
TBool TAknTextWrapper::GetWrappingPosition(
const TDesC& aText,
TInt aFitsInLine,
TInt aLineWidth,
TBool aReplaceSoftHyphen )
{
// If there is an explicit line break character right after the fitting
// characters, then wrap there.
if ( aText.Length() > aFitsInLine &&
iSeparators.Locate( aText[aFitsInLine] ) != KErrNotFound )
{
iBreakPos = aFitsInLine;
iNextLineStart = aFitsInLine;
return ETrue;
}
TBool foundLineBreak( EFalse );
TText* text = const_cast<TText*>( iText.Ptr() );
while ( aFitsInLine > 0 )
{
// use linebreak routine to find the last possible line break
if ( !iBreaker.GetLineBreak(
aText,
1,
aFitsInLine,
EFalse,
NULL,
iBreakPos,
iNextLineStart ) )
{
// no legal wrapping positions found at all
break;
}
/////////////////////////////////////////////////////////////
// Special rule for Thai wrapping:
// Wrapping between two Thai characters is not done if the line
// contains at least one zero-width-space or usual space. In this
// situation the line is wrapped from the latest zero width space
// or space.
TInt newWrapPosition = -1;
TInt charactersToCheck = iBreakPos - 1;
TChar breakChar1 = text[charactersToCheck];
TChar breakChar2 = ' ';
if (aText.Length() > aFitsInLine)
{
// The character after the usual line break needs to be Thai character as well.
charactersToCheck += 1;
breakChar2 = text[charactersToCheck];
}
// Did the line break were made between two Thai characters.
if ( ( breakChar1 >= KFirstThaiCharacter && breakChar1 <= KLastThaiCharacter ) &&
( breakChar2 >= KFirstThaiCharacter && breakChar2 <= KLastThaiCharacter ) )
{
for (TInt ii = charactersToCheck; ii >= 0; ii--)
{
const TChar charTemp = text[ii];
if ( (charTemp == ' ' || charTemp == KZeroWidthSpace) )
{
// Space or zero-width-space found. We use it for wrapping.
newWrapPosition = ii;
break;
}
}
}
if ( newWrapPosition >= 0 )
{
// The special wrapping rule is used.
iBreakPos = newWrapPosition;
iNextLineStart = iBreakPos + 1;
foundLineBreak = ETrue;
}
/////////////////////////////////////////////////////////////
// Wrapping after hyphen (or soft hyphen) is allowed only
// if there is no space right before the hyphen.
TInt lastCharIndex( iBreakPos - 1 );
const TText lastChar = text[lastCharIndex];
if ( lastChar == '-' ||
lastChar == KHyphen ||
lastChar == KSoftHyphen )
{
if ( lastCharIndex > 0 && text[lastCharIndex - 1] == ' ' )
{
aFitsInLine = lastCharIndex;
continue;
}
}
if ( lastChar != KSoftHyphen )
{
foundLineBreak = ETrue;
break;
}
// If the chosen wrapping char was soft hyphen,
// try replace it with real hyphen.
text[lastCharIndex] = KHyphen;
// still fits in line?
if ( iFont.TextWidthInPixels(
iText.Left( lastCharIndex + 1 ) ) <= aLineWidth )
{
foundLineBreak = ETrue;
if ( !aReplaceSoftHyphen )
{
text[lastCharIndex] = KSoftHyphen;
}
break;
}
else
{
// This soft hyphen could not be used, because
// expanding it to real hyphen would have made
// the line too long to fit.
text[lastCharIndex] = KSoftHyphen;
aFitsInLine = lastCharIndex;
}
}
return foundLineBreak;
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::ConvertToVisualIfRequiredL
// -----------------------------------------------------------------------------
//
HBufC* TAknTextWrapper::ConvertToVisualIfRequiredL()
{
if ( !(iFlags & EConvertToVisual) )
{
return NULL;
}
TInt lines = iResultArray.Count();
HBufC* visualBuffer = HBufC::NewLC(
iSourceString.Length() + lines * KAknBidiExtraSpacePerLine );
CAknEnv& env = *CAknEnv::Static();
TChar truncationChar = 0xffff;
TInt sourceStringLength = iSourceString.Length();
// initialize run info array
User::LeaveIfError( env.PrepareRunInfoArray( iSourceString ) );
// get run info array
TInt count;
TBidirectionalState::TRunInfo* array = env.RunInfoArray( count );
// convert from logical to visual
TBidiLogicalToVisual converter(
iDirectionality == AknBidiTextUtils::EImplicit ?
TBidiLogicalToVisual(
iSourceString,
array,
count ) :
TBidiLogicalToVisual(
iSourceString,
iDirectionality == AknBidiTextUtils::ERightToLeft,
array,
count ) );
converter.Reorder();
// Convert each line from logical to visual form,
// and update array of wrapped lines accordingly.
for ( TInt i = 0 ; i < lines ; i++ )
{
TPtrC logicalLine = iResultArray[i];
TInt start = (TText*)logicalLine.Ptr() - (TText*)iSourceString.Ptr();
TInt length = logicalLine.Length();
TInt currentLength = visualBuffer->Length();
TInt visualLineLength = visualBuffer->Des().MaxLength() - currentLength;
TPtr visualLine(
((TText*)visualBuffer->Ptr()) + currentLength,
0,
visualLineLength );
// if last line, set clip char if required
if ( i == lines - 1 && iFlags & EClipRequired )
{
truncationChar = KEllipsis;
}
converter.GetVisualLine(
visualLine,
start,
start + length,
truncationChar );
visualBuffer->Des().SetLength(
visualBuffer->Length() + visualLine.Length() );
// update array of wrapped lines accordingly.
iResultArray.Delete( i );
if ( iFlags & EReserveVisualBuffer )
{
iResultArray.InsertL(
i, visualBuffer->Right( visualLine.Length() ) );
}
else
{
iSourceString.SetMax();
iResultArray.InsertL(
i, iSourceString.Mid( currentLength, visualLine.Length() ) );
iSourceString.SetLength( sourceStringLength );
}
}
if ( iFlags & EReserveVisualBuffer )
{
CleanupStack::Pop();
return visualBuffer;
}
else
{
iSourceString = *visualBuffer;
CleanupStack::PopAndDestroy(); // iVisualBuffer;
return NULL;
}
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::PassLineBreak
// -----------------------------------------------------------------------------
//
void TAknTextWrapper::PassLineBreak()
{
TText* text = const_cast<TText*>( iText.Ptr() );
TInt length = iText.Length();
if ( iNextLineStart < length )
{
const TText c = text[iNextLineStart];
if ( c == KLineFeed ||
c == KLineSeparator ||
c == KParagraphSeparator )
{
iNextLineStart++;
}
else if ( c == KCarriageReturn )
{
iNextLineStart++;
// after CR, also skip possible matching LF
if ( iNextLineStart < length && text[iNextLineStart] == KLineFeed )
{
iNextLineStart++;
}
}
}
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::ResultFits
// -----------------------------------------------------------------------------
//
TBool TAknTextWrapper::ResultFits()
{
return iFlags & EFits;
}
// -----------------------------------------------------------------------------
// TAknTextWrapper::GetLocVariants
// -----------------------------------------------------------------------------
//
TInt TAknTextWrapper::GetLocVariants(
const TDesC& aText, TAknLocVariant* aVariants )
{
TPtrC remaining( aText );
TInt start = 0;
TInt end = remaining.Locate( KAknLocVariantSeparator );
TInt i = 0;
while ( end >= 0 && i < KAknMaxLocVariants )
{
end += start;
aVariants[i].iStart = start;
aVariants[i].iEnd = end;
start = end + 1;
remaining.Set( aText.Mid( start ) );
end = remaining.Locate( KAknLocVariantSeparator );
i++;
}
if ( i < KAknMaxLocVariants )
{
// Handle the last variant.
aVariants[i].iStart = start;
aVariants[i].iEnd = aText.Length();
i++;
}
// Put the variants in descending character count order.
if ( i > 1 )
{
TMyKey key;
key.SetPtr( aVariants );
TMySwap swap( aVariants );
// Return value ignored.
User::QuickSort( i, key, swap );
}
return i;
}
// End of File