sl@0: // Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // sl@0: sl@0: #include sl@0: #include "BidiTextImp.h" sl@0: #include "BidiCopy.h" sl@0: #include "BidiCompact.h" sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: _LIT(KBidiPanicCategory,"Bidi"); sl@0: static const TInt KLineSeparator = 0x2028; sl@0: static const TInt KParagraphSeparator = 0x2029; sl@0: static const TInt KCodeCR = 0x000D; sl@0: static const TInt KCodeLF = 0x000A; sl@0: static const TInt KCodeEllipsis = 0x2026; sl@0: sl@0: void DeleteTRunInfo(void* aRunInfo) sl@0: { sl@0: delete[] reinterpret_cast(aRunInfo); sl@0: } sl@0: sl@0: void BidiPanic(TInt aError) sl@0: { sl@0: User::Panic(KBidiPanicCategory, aError); sl@0: } sl@0: sl@0: sl@0: // One page-full of TRunInfos sl@0: const TInt KMaxRunInfoArraySize = 4*1024 / sizeof(TBidirectionalState::TRunInfo); sl@0: const TInt KBidiTlsHandle = 0x101F633D; sl@0: sl@0: sl@0: /* sl@0: * Ref-counted TLS for the shared run info array used by the SetText() method. sl@0: */ sl@0: NONSHARABLE_CLASS(CBidiTextTls) : public CObject sl@0: { sl@0: public: sl@0: static CBidiTextTls* NewL(); sl@0: static CBidiTextTls* GetTls(); sl@0: ~CBidiTextTls(); sl@0: inline TUint MaxArraySize(); sl@0: inline TBidirectionalState::TRunInfo* RunArray(); sl@0: sl@0: private: sl@0: CBidiTextTls(); sl@0: void ConstructL(TUint aMaxArraySize); sl@0: sl@0: private: sl@0: TBidirectionalState::TRunInfo* iRunInfoArray; sl@0: TUint iMaxArraySize; sl@0: }; sl@0: sl@0: sl@0: sl@0: CBidiTextTls::CBidiTextTls() sl@0: : iRunInfoArray(NULL), sl@0: iMaxArraySize(0) sl@0: { sl@0: } sl@0: sl@0: sl@0: CBidiTextTls::~CBidiTextTls() sl@0: { sl@0: UserSvr::DllFreeTls(KBidiTlsHandle); sl@0: sl@0: if (iRunInfoArray) sl@0: { sl@0: delete [] iRunInfoArray; sl@0: } sl@0: } sl@0: sl@0: sl@0: TUint CBidiTextTls::MaxArraySize() sl@0: { sl@0: return iMaxArraySize; sl@0: } sl@0: sl@0: sl@0: TBidirectionalState::TRunInfo* CBidiTextTls::RunArray() sl@0: { sl@0: return iRunInfoArray; sl@0: } sl@0: sl@0: sl@0: /** sl@0: * Helper function provided to simplify reading the TLS data and improve the sl@0: * readability of the code sl@0: */ sl@0: CBidiTextTls* CBidiTextTls::GetTls() sl@0: { sl@0: return reinterpret_cast(UserSvr::DllTls(KBidiTlsHandle)); sl@0: } sl@0: sl@0: sl@0: CBidiTextTls* CBidiTextTls::NewL() sl@0: { sl@0: CBidiTextTls* self = new (ELeave) CBidiTextTls; sl@0: CleanupClosePushL(*self); sl@0: self->ConstructL(KMaxRunInfoArraySize); sl@0: CleanupStack::Pop(self); sl@0: return self; sl@0: } sl@0: sl@0: sl@0: void CBidiTextTls::ConstructL(TUint aMaxArraySize) sl@0: { sl@0: iMaxArraySize = aMaxArraySize; sl@0: iRunInfoArray = new (ELeave) TBidirectionalState::TRunInfo[aMaxArraySize]; sl@0: User::LeaveIfError(UserSvr::DllSetTls(KBidiTlsHandle, this)); sl@0: } sl@0: sl@0: sl@0: EXPORT_C RRunInfoArray::RRunInfoArray() sl@0: : iTls(NULL) sl@0: { sl@0: } sl@0: sl@0: sl@0: /** sl@0: Creates the run array if necessary and increases the reference count on it. sl@0: RRunInfoArray::OpenL() must be called prior to calling TBidiText::SetText(). sl@0: */ sl@0: EXPORT_C void RRunInfoArray::OpenL() sl@0: { sl@0: if(!iTls) sl@0: { sl@0: iTls = CBidiTextTls::GetTls(); sl@0: if(iTls) sl@0: { sl@0: iTls->Open(); // Increase ref count sl@0: } sl@0: else sl@0: { sl@0: iTls = CBidiTextTls::NewL(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: Decreases the reference count on the run array. The run array will be deleted sl@0: if the reference count reaches zero. The client application must ensure that sl@0: there is a matching call to Close() for every call to OpenL() or memory will sl@0: be leaked. sl@0: */ sl@0: EXPORT_C void RRunInfoArray::Close() sl@0: { sl@0: if(iTls) sl@0: { sl@0: iTls->Close(); sl@0: iTls = NULL; sl@0: } sl@0: } sl@0: sl@0: sl@0: /** sl@0: @return Pointer to the run array buffer sl@0: @internalComponent sl@0: */ sl@0: TBidirectionalState::TRunInfo* RRunInfoArray::RunArray() const sl@0: { sl@0: return iTls ? iTls->RunArray() : NULL; sl@0: } sl@0: sl@0: sl@0: /** sl@0: @return Number of bytes needed to hold the TBidiTextImp member variables, plus the sl@0: text data allocated off the end of the TBidiTextImp object. sl@0: @internalComponent sl@0: */ sl@0: TInt TBidiTextImp::RequiredBytes(TInt aLength, TInt aMaxLines, TInt aBdRunArraySize) sl@0: { sl@0: // size of TBidiTextImp class sl@0: TInt bytes = TBidiTextImp::AlignedSizeOf(); sl@0: // size of text for logical and visual orderings. sl@0: // This includes aMaxLines - 1 line breaks with surrounding sl@0: // zero-width joiners, and a truncation character (possibly sl@0: // a surrogate pair) plus a zero-width joiner. sl@0: bytes += sizeof(TText) * (aLength * 2 + aMaxLines * 3); sl@0: // size of line length array sl@0: bytes += sizeof(TInt16*) * aMaxLines; sl@0: // alignment sl@0: bytes = (bytes + 3) & 0xFFFFFFFC; sl@0: // array of TRunInfoCompact sl@0: bytes += sizeof(TRunInfoCompact) * aBdRunArraySize; sl@0: sl@0: return bytes; sl@0: } sl@0: sl@0: sl@0: /** sl@0: @return A TBidiTextImp object of sufficient size to hold the amount of text data specified sl@0: by the the arguments. sl@0: @param aLength The number of characters in the text. sl@0: @param aMaxLines The maximum number of lines sl@0: @param aBdRunArraySize The size of the bidi run array. sl@0: @internalComponent sl@0: */ sl@0: TBidiTextImp* TBidiTextImp::NewL(TInt aLength, TInt aMaxLines, TInt aBdRunArraySize) sl@0: { sl@0: const TInt bytes = RequiredBytes(aLength, aMaxLines, aBdRunArraySize); sl@0: TInt8* mem = static_cast(User::AllocL(bytes)); sl@0: TBidiTextImp* me = reinterpret_cast(mem); sl@0: sl@0: me->iTextLengthAndFlags = aLength; sl@0: me->iVisualOrderedTextLength = -1; sl@0: me->iWrappingWidth = 0xFFFF; sl@0: me->iBidiRunArrayLength = aBdRunArraySize; sl@0: me->iLines = static_cast(aMaxLines); sl@0: me->iTruncationCharPlane = 0; sl@0: me->iTruncationChar16 = KCodeEllipsis; sl@0: me->SetAllocatedTextDataBytes(bytes - TBidiTextImp::AlignedSizeOf() - (sizeof(TRunInfoCompact) * aBdRunArraySize)); sl@0: return me; sl@0: } sl@0: sl@0: /** sl@0: @return Position of logically-ordered text portion of the heap cell. sl@0: @internalComponent sl@0: */ sl@0: TText* TBidiTextImp::LogicalText() sl@0: { sl@0: return reinterpret_cast( sl@0: reinterpret_cast(this) sl@0: + TBidiTextImp::AlignedSizeOf()); sl@0: } sl@0: sl@0: /** sl@0: @return Position of visually-ordered text portion of the heap cell. sl@0: @internalComponent sl@0: */ sl@0: TText* TBidiTextImp::VisualText() sl@0: { sl@0: TInt bytes = TBidiTextImp::AlignedSizeOf(); sl@0: bytes += sizeof(TText) * TextLength(); sl@0: return reinterpret_cast( sl@0: reinterpret_cast(this) + bytes); sl@0: } sl@0: sl@0: /** sl@0: Returns a pointer to the array containing the width in pixels of each and every line. sl@0: @return Position of the array of line widths portion of the heap cell. sl@0: @internalComponent sl@0: */ sl@0: TInt16* TBidiTextImp::LineWidthArray() sl@0: { sl@0: TInt bytes = TBidiTextImp::AlignedSizeOf(); sl@0: bytes += sizeof(TText) * (TextLength() * 2 + iLines + 2); sl@0: return reinterpret_cast( sl@0: reinterpret_cast(this) + bytes); sl@0: } sl@0: sl@0: /** sl@0: @return Position of the array of runs portion of the heap cell. sl@0: @internalComponent sl@0: */ sl@0: TRunInfoCompact* TBidiTextImp::BidiRunArray() sl@0: { sl@0: TInt bytes = TBidiTextImp::AlignedSizeOf(); sl@0: bytes += sizeof(TText) * (TextLength() * 2 + iLines + 2); sl@0: bytes += sizeof(TInt16*) * iLines; sl@0: bytes = (bytes + 3) & 0xFFFFFFFC; sl@0: return reinterpret_cast( sl@0: reinterpret_cast(this) + bytes); sl@0: } sl@0: sl@0: /** sl@0: Report if the current character is an explicit line break. Both sl@0: aText[0] and aText[1] must be part of the string. sl@0: @return Size of line break. sl@0: @internalComponent sl@0: */ sl@0: TInt SizeLineBreak(const TText* aText, const TText* aTextEnd) sl@0: { sl@0: if (aText == aTextEnd ) sl@0: return 0; sl@0: sl@0: if (*aText == KLineSeparator || *aText == KParagraphSeparator sl@0: || *aText == KCodeLF) sl@0: return 1; sl@0: if (aText[0] == KCodeCR) sl@0: { sl@0: // first check for space before checking for LF sl@0: if (aText+1 < aTextEnd ) sl@0: { sl@0: return aText[1] == KCodeLF? 2 : 1; sl@0: } sl@0: else sl@0: return 1; sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: /** sl@0: Find the next line break character. sl@0: @internalComponent sl@0: */ sl@0: const TText* FindEndOfThisLine(const TText* aStart, const TText* aEnd) sl@0: { sl@0: while (aStart != aEnd && *aStart != KLineSeparator sl@0: && *aStart != KParagraphSeparator && *aStart != KCodeLF sl@0: && *aStart != KCodeCR) sl@0: ++aStart; sl@0: return aStart; sl@0: } sl@0: sl@0: /** sl@0: Count number of lines in text. sl@0: @internalComponent sl@0: */ sl@0: TInt NumberOfLines(const TText* aStart, const TText* aEnd) sl@0: { sl@0: TInt num = 0; sl@0: while (aStart != aEnd) sl@0: { sl@0: aStart = FindEndOfThisLine(aStart, aEnd); sl@0: aStart += SizeLineBreak(aStart, aEnd); sl@0: ++num; sl@0: } sl@0: return num; sl@0: } sl@0: sl@0: /** Returns the directionality of a given language. sl@0: @param aLanguage Language. sl@0: @return The directionality of the given language. */ sl@0: EXPORT_C TBidiText::TDirectionality TBidiText::ScriptDirectionality( sl@0: TLanguage aLanguage) sl@0: { sl@0: const TUint32 DirectionalityBitmap[] = sl@0: { sl@0: 0, sl@0: // Arabic, Farsi, Hebrew sl@0: 0x02040020, sl@0: // Urdu sl@0: 0x40000000 sl@0: }; sl@0: TUint index = aLanguage; sl@0: if (index < sizeof(DirectionalityBitmap) * 8) sl@0: { sl@0: index >>= 5; sl@0: TInt bit = aLanguage & 31; sl@0: return (DirectionalityBitmap[index] >> bit) & 1? sl@0: ERightToLeft : ELeftToRight; sl@0: } sl@0: return ELeftToRight; sl@0: } sl@0: sl@0: sl@0: /** Reports the implicit directionality of a piece of text. sl@0: sl@0: @param aText The text to be examined. sl@0: @param aFound If non-null, returns ETrue if there were any strongly directional sl@0: characters and EFalse if there were none. If a piece of text is spread over sl@0: several descriptors, They need to be queried in sequence until one returns sl@0: ETrue in aFound. sl@0: @return The directionality implicit in aText. 131 */ sl@0: EXPORT_C TBidiText::TDirectionality TBidiText::TextDirectionality( sl@0: const TDesC& aText, TBool* aFound) sl@0: { sl@0: return BidiCopy::ImplicitDirectionalityIsRightToLeft( sl@0: aText.Ptr(), aText.Length(), aFound)? sl@0: ERightToLeft : ELeftToRight; sl@0: } sl@0: sl@0: /** Creates a bidirectional text object with directionality determined by sl@0: aDirectionality. Use this for text that has come from user input. sl@0: sl@0: @param aText The text in logical order. sl@0: @param aMaxLines sl@0: The maximum number of lines that this text will need to be split into. Must sl@0: be at least 1, but should not be too large, as each potential line takes an sl@0: extra 8 bytes of memory. sl@0: @param aDirectionality Direction to use. sl@0: @return The newly constructed object. sl@0: */ sl@0: EXPORT_C TBidiText* TBidiText::NewL(const TDesC& aText, TInt aMaxLines, sl@0: TBidiText::TDirectionality aDirectionality) sl@0: { sl@0: __ASSERT_ALWAYS(0 < aMaxLines, BidiPanic(EBidiPanic_InvalidMaxline)); sl@0: const TText* text = aText.Ptr(); sl@0: const TInt length = aText.Length(); sl@0: TInt linesInOriginalText = NumberOfLines(text, text + length); sl@0: if (aMaxLines < linesInOriginalText) sl@0: aMaxLines = linesInOriginalText; sl@0: sl@0: const TInt arraySize = TBidirectionalState::GenerateBdRunArray(text, length, 0, 0); sl@0: TBidirectionalState::TRunInfo* runInfoArray = new(ELeave) TBidirectionalState::TRunInfo[arraySize]; sl@0: TCleanupItem ci(DeleteTRunInfo, runInfoArray); sl@0: CleanupStack::PushL(ci); sl@0: TBidirectionalState::GenerateBdRunArray(text, length, runInfoArray, arraySize); sl@0: TBidirectionalState state; sl@0: state.ReorderLine(runInfoArray, arraySize, ETrue, ETrue, aDirectionality, sl@0: TChar::EOtherNeutral, TChar::EOtherNeutral); sl@0: TInt compactArraySize = TRunInfoCompact::Convert(0, aText, runInfoArray, arraySize); sl@0: sl@0: // size of TBidiTextImp class sl@0: TBidiTextImp* me = TBidiTextImp::NewL(length, aMaxLines, compactArraySize); sl@0: me->SetRightToLeftDirectionality(aDirectionality != ELeftToRight); sl@0: sl@0: TRunInfoCompact::Convert(me->BidiRunArray(), aText, runInfoArray, arraySize); sl@0: CleanupStack::PopAndDestroy(runInfoArray); sl@0: sl@0: Mem::Copy(me->LogicalText(), text, length * sizeof(TText)); sl@0: return me; sl@0: } sl@0: sl@0: /** Creates a bidirectional text object with directionality determined by the text sl@0: itself. Use this for text that has been obtained from a resource file. sl@0: sl@0: @param aText The text in logical order. sl@0: @param aMaxLines The maximum number of lines that this text will need to be sl@0: split into. Must be at least 1, but should not be too large, as each potential sl@0: line takes an extra 8 bytes of memory. sl@0: @return The newly constructed object. */ sl@0: EXPORT_C TBidiText* TBidiText::NewL(const TDesC& aText, TInt aMaxLines) sl@0: { sl@0: return NewL(aText, aMaxLines, TextDirectionality(aText)); sl@0: } sl@0: sl@0: sl@0: /** Creates a bidirectional text object with enough room for up to aReservedMaxLength sl@0: number of characters. The number of characters that will actually fit (when calling sl@0: SetText()) might be slightly less than aReservedMaxLength, as each change between a sl@0: left-to-right and a right-to-left sub-string (and the other way around) needs about sl@0: two characters worth of memory. sl@0: sl@0: @param aReservedMaxLength The maximum number of characters. sl@0: @param aMaxLines The maximum number of lines that this text will need to be sl@0: split into. Must be at least 1, but should not be too large, as each potential sl@0: line takes an extra 8 bytes of memory. sl@0: @return The newly constructed object. */ sl@0: EXPORT_C TBidiText* TBidiText::NewL(TInt aReservedMaxLength, TInt aMaxLines) sl@0: { sl@0: __ASSERT_ALWAYS(0 < aReservedMaxLength, BidiPanic(EBidiPanic_InvalidReservedMaxLength)); sl@0: __ASSERT_ALWAYS(0 < aMaxLines, BidiPanic(EBidiPanic_InvalidMaxline)); sl@0: sl@0: const TInt compactArraySize = 1; // Always at least one needed sl@0: sl@0: TBidiTextImp* me = TBidiTextImp::NewL(aReservedMaxLength, aMaxLines, compactArraySize); sl@0: me->SetTextLength(0); // no text yet, just reserved memory sl@0: return me; sl@0: } sl@0: sl@0: /** Sets the text of the bidirectional text object with directionality determined sl@0: by the text itself. Use this for text that has been obtained from a resource file. sl@0: sl@0: @param aText The text in logical order. sl@0: @return The number of characters that didn't fit in the available buffer. sl@0: */ sl@0: EXPORT_C TInt TBidiText::SetText(const TDesC& aText, RRunInfoArray& aRunInfoArray) sl@0: { sl@0: return SetText(aText, TextDirectionality(aText), aRunInfoArray); sl@0: } sl@0: sl@0: sl@0: /** Sets the text of the bidirectional text with directionality determined by sl@0: aDirectionality. Use this for text that has come from user input. sl@0: sl@0: @param aText The text in logical order. sl@0: @param aDirectionality Direction to use. sl@0: @return The number of characters that didn't fit in the available buffer. sl@0: @panic Bidi EBidiPanic_RunArrayNull The call to RRunInfoArray::OpenL() has not sl@0: been made prior to this call to TBidiText::SetText() sl@0: */ sl@0: EXPORT_C TInt TBidiText::SetText(const TDesC& aText, sl@0: TDirectionality aDirectionality, sl@0: RRunInfoArray& aRunInfoArray) sl@0: { sl@0: TBidirectionalState::TRunInfo* const runArray = aRunInfoArray.RunArray(); sl@0: __ASSERT_ALWAYS(runArray, BidiPanic(EBidiPanic_RunArrayNull)); sl@0: sl@0: TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: const TInt maxLines = me->iLines; sl@0: const TText* text = aText.Ptr(); sl@0: TInt length = aText.Length(); sl@0: sl@0: TInt requiredArraySize = TBidirectionalState::GenerateBdRunArray(text, length, 0, 0); sl@0: const TInt actualArraySize = aRunInfoArray.iTls->MaxArraySize(); sl@0: sl@0: if (requiredArraySize > actualArraySize) sl@0: { sl@0: // Handle the case where we do not have enough space in the run array sl@0: // to cope with the input text. The text will be truncated to ensure sl@0: // we don't overrun the buffer and the number of excess characters sl@0: // returned as a negative number. sl@0: requiredArraySize = actualArraySize; sl@0: TBidirectionalState::GenerateBdRunArray(text, length, runArray, requiredArraySize); sl@0: sl@0: length = 0; sl@0: for (TInt index = 0; index < requiredArraySize; index++) sl@0: { sl@0: length += runArray[index].iLength; sl@0: } sl@0: } sl@0: else sl@0: { sl@0: TBidirectionalState::GenerateBdRunArray(text, length, runArray, requiredArraySize); sl@0: } sl@0: sl@0: sl@0: sl@0: TBidirectionalState state; sl@0: state.ReorderLine(runArray, sl@0: requiredArraySize, sl@0: ETrue, sl@0: ETrue, sl@0: aDirectionality, sl@0: TChar::EOtherNeutral, sl@0: TChar::EOtherNeutral); sl@0: const TInt compactArraySize = TRunInfoCompact::Convert(0, sl@0: aText, sl@0: runArray, sl@0: requiredArraySize); sl@0: sl@0: // Calculate number of bytes needed to keep text data sl@0: TInt requiredBytes = sizeof(TText) * (length * 2 + maxLines * 3); // size of text for logical & visual orderings. sl@0: requiredBytes += sizeof(TInt16*) * maxLines; // size of line length array sl@0: requiredBytes = (requiredBytes + 3) & 0xFFFFFFFC; // alignment sl@0: sl@0: TInt textLength = length; sl@0: const TInt excessData = Max(0, requiredBytes - me->AllocatedTextDataBytes()); sl@0: TInt excessChars = 0; sl@0: if(excessData) sl@0: { sl@0: // Calculate how much text data that can be fitted into the available bytes, sl@0: // given the bytes needed for run array data sl@0: excessChars = excessData / (sizeof(TText) * 2); sl@0: textLength -= excessChars; sl@0: } sl@0: else if (aText.Length() > length) sl@0: { sl@0: excessChars = aText.Length() - length; sl@0: } sl@0: sl@0: me->SetTextLength(textLength); sl@0: me->SetRightToLeftDirectionality(aDirectionality != ELeftToRight); sl@0: me->iVisualOrderedTextLength = -1; sl@0: me->iBidiRunArrayLength = static_cast(compactArraySize); sl@0: sl@0: TRunInfoCompact::Convert(me->BidiRunArray(), aText, runArray, requiredArraySize); sl@0: Mem::Copy(me->LogicalText(), text, textLength * sizeof(TText)); sl@0: sl@0: return excessChars; sl@0: } sl@0: sl@0: /** Sets the character that will be added at the end of the text if the whole text sl@0: cannot fit into the space specified. sl@0: sl@0: @param aTruncateWith The truncation char. */ sl@0: EXPORT_C void TBidiText::SetTruncationChar(TChar aTruncateWith) sl@0: { sl@0: TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: me->iTruncationCharPlane = static_cast(aTruncateWith >> 16); sl@0: me->iTruncationChar16 = static_cast(aTruncateWith); sl@0: } sl@0: sl@0: TInt RemoveTrailingSpaces(const MLineBreaker* aBreaker, sl@0: const TText* aInput, TInt aMinPos, TInt aEndPos) sl@0: { sl@0: // Ignore space characters at the end of the line. sl@0: // Don't bother to ignore spaces made of surrogate pairs: sl@0: // more processing than it's worth. sl@0: TUint dummy1, dummy2; sl@0: while (aEndPos != aMinPos && MLineBreaker::ESpLineBreakClass sl@0: == aBreaker->LineBreakClass(aInput[aEndPos - 1], dummy1, dummy2)) sl@0: { sl@0: --aEndPos; sl@0: } sl@0: return aEndPos; sl@0: } sl@0: sl@0: /** Prepares the visually-ordered text according to the wrapping width and font sl@0: specified. Text cannot be drawn until this has been done. sl@0: @param aWrappingWidth sl@0: The maximum width of the text in pixels. Note that this distance should be sl@0: slightly less than the available width to allow for characters such as "W" sl@0: which can have side-bearings that leak into the margins. sl@0: @param aFont The font that will provide the character metrics. sl@0: @param aBreaker sl@0: An object for breaking the lines. May be NULL for default behaviour. sl@0: @param aMaxLines sl@0: Number of lines to restrict wrapping to. The truncation character will be sl@0: used if the text is too long for this number of lines. The number of lines sl@0: wrapped to may not be greater than the figure passed to NewL. sl@0: */ sl@0: EXPORT_C void TBidiText::WrapText(TInt aWrappingWidth, const CFont& aFont, sl@0: const MLineBreaker* aBreaker, TInt aMaxLines) sl@0: { sl@0: TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: me->iWrappingWidth = aWrappingWidth; sl@0: sl@0: TInt16* lineWidths = me->LineWidthArray(); sl@0: TText* output = me->VisualText(); sl@0: sl@0: TInt numLines = 0; sl@0: DoWrapText(aWrappingWidth, aFont, aBreaker, aMaxLines, output, numLines, lineWidths); sl@0: me->iVisualOrderedTextLength = output - me->VisualText(); sl@0: } sl@0: sl@0: /** Calculate the minimum size needed to draw the current text, given the specified sl@0: wrapping width, font, and line gap. Calling this method will not rewrap the object's sl@0: text. sl@0: sl@0: @param aWrappingWidth sl@0: The maximum width of the text in pixels. Note that this distance should be sl@0: slightly less than the available width to allow for characters such as "W" sl@0: which can have side-bearings that leak into the margins. sl@0: @param aFont The font that will provide the character metrics. sl@0: @param aLineGap The number of empty pixels between two lines of text. sl@0: Note that this is not the same as the baseline spacing, which is the font sl@0: height plus the line gap. sl@0: @param aMaxLines sl@0: Number of lines to restrict wrapping to. The truncation character will be sl@0: used if the text is too long for this number of lines. The number of lines sl@0: wrapped to may be greater than the figure passed to NewL, and that figure sl@0: will be used if the number of lines is specified as -1. If 0 (zero) is specified sl@0: no limit is applied. sl@0: @param aBreaker sl@0: An object for breaking the lines. May be NULL for default behaviour. sl@0: */ sl@0: EXPORT_C TSize TBidiText::MinimumSize(TInt aWrappingWidth, const CFont& aFont, TInt aLineGap, sl@0: TInt aMaxLines, const MLineBreaker* aBreaker) const sl@0: { sl@0: __ASSERT_ALWAYS(0 <= aWrappingWidth, BidiPanic(EBidiPanic_InvalidWrappingWidth)); sl@0: __ASSERT_ALWAYS(0 <= aLineGap, BidiPanic(EBidiPanic_InvalidLineGap)); sl@0: __ASSERT_ALWAYS(-1 <= aMaxLines, BidiPanic(EBidiPanic_InvalidMaxline)); sl@0: sl@0: TInt numLines = 0; sl@0: TText* output = NULL; sl@0: const TInt minWidth = DoWrapText(aWrappingWidth, sl@0: aFont, sl@0: aBreaker, sl@0: (aMaxLines = 0 ? KMaxTInt : aMaxLines), sl@0: output, sl@0: numLines, sl@0: NULL); sl@0: const TInt minHeight = (aFont.FontMaxHeight() + aLineGap) * numLines - aLineGap; sl@0: return TSize(minWidth, minHeight); sl@0: } sl@0: sl@0: sl@0: TInt TBidiText::DoWrapText(TInt aWrappingWidth, const CFont& aFont, const MLineBreaker* aBreaker, sl@0: TInt aMaxLines, TText*& aOutputText, TInt& aNumLines, TInt16* aLineWidthArray) const sl@0: { sl@0: MLineBreaker defaultBreaker; sl@0: if (!aBreaker) sl@0: aBreaker = &defaultBreaker; sl@0: sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: if (me->iLines < aMaxLines) sl@0: aMaxLines = me->iLines; sl@0: sl@0: const TRunInfoCompact* runArray = me->BidiRunArray(); sl@0: const TRunInfoCompact* runArrayEnd = runArray + me->iBidiRunArrayLength; sl@0: sl@0: const TText* input = me->LogicalText(); sl@0: const TInt inputLength = me->TextLength(); sl@0: TPtrC textDes(input, inputLength); sl@0: const TText* output = me->VisualText(); sl@0: sl@0: TRunInfoCompact::TReorderingContext context; sl@0: context.iSource = input; sl@0: context.iTruncation = 0xFFFF; sl@0: context.iJoinsAtEnd = EFalse; sl@0: sl@0: TInt start = 0; sl@0: CFont::TMeasureTextInput measureInput; sl@0: measureInput.iMaxAdvance = aWrappingWidth; sl@0: measureInput.iEndInputChar = FindEndOfThisLine(input, input + inputLength) - input; sl@0: CFont::TMeasureTextOutput measureOutput; sl@0: TBool truncated; sl@0: sl@0: TInt widestLineWidth = 0; sl@0: TBool bLastLine = EFalse; sl@0: for (aNumLines = 0; aNumLines != aMaxLines && start < inputLength; ++aNumLines) sl@0: { sl@0: truncated=EFalse; sl@0: context.iJoinsAtStart = context.iJoinsAtEnd; sl@0: if(aNumLines != 0 && aOutputText) sl@0: *(aOutputText++) = KLineSeparator; sl@0: sl@0: measureInput.iStartInputChar = start; sl@0: TInt advance = aFont.MeasureText(textDes, &measureInput, &measureOutput); sl@0: TInt breakPos = measureOutput.iChars; sl@0: TInt endOfLine = breakPos; sl@0: // truncationCharWidth is the width of any truncation character on this sl@0: // line only. sl@0: TInt truncationCharWidth = 0; sl@0: if (endOfLine == measureInput.iEndInputChar) sl@0: { sl@0: //handle the dangling lines here sl@0: TInt sizeLineBreak = SizeLineBreak(input + endOfLine, input + inputLength); sl@0: if((measureInput.iEndInputChar < inputLength - sizeLineBreak) && (aNumLines == aMaxLines - 1)) sl@0: bLastLine = ETrue; sl@0: } sl@0: else if (aNumLines == aMaxLines - 1) sl@0: { sl@0: bLastLine = ETrue; sl@0: } sl@0: else sl@0: { // Not last line, so find a legal line break. sl@0: aBreaker->GetLineBreak(textDes, sl@0: start + 1, sl@0: measureOutput.iChars, sl@0: EFalse, sl@0: 0, sl@0: breakPos, sl@0: endOfLine); sl@0: } sl@0: sl@0: if (bLastLine) sl@0: { sl@0: // Last line, so re-measure leaving enough room for sl@0: // truncation character. sl@0: context.iTruncation = me->TruncationChar(); sl@0: truncationCharWidth = aFont.CharWidthInPixels(context.iTruncation); sl@0: measureInput.iMaxAdvance = aWrappingWidth - truncationCharWidth; sl@0: advance = aFont.MeasureText(textDes, &measureInput, &measureOutput) + truncationCharWidth; sl@0: breakPos = RemoveTrailingSpaces(aBreaker, input, start, measureOutput.iChars); sl@0: truncated=ETrue; sl@0: bLastLine = EFalse; sl@0: } sl@0: sl@0: // if the break position has changed, we need to remeasure sl@0: if (breakPos != measureOutput.iChars) sl@0: { sl@0: const TInt oldEnd = measureInput.iEndInputChar; sl@0: measureInput.iEndInputChar = breakPos; sl@0: advance = aFont.MeasureText(textDes, &measureInput, &measureOutput) + truncationCharWidth; sl@0: measureInput.iEndInputChar = oldEnd; sl@0: truncated=ETrue; sl@0: } sl@0: sl@0: //width may be greater than advance sl@0: advance = Max(advance,measureOutput.iBounds.Width()); sl@0: sl@0: if(widestLineWidth < advance) sl@0: widestLineWidth = advance; sl@0: sl@0: if(aLineWidthArray) sl@0: *(aLineWidthArray++) = static_cast(advance); sl@0: sl@0: context.iStart = start; sl@0: context.iEnd = breakPos; sl@0: if (truncated) sl@0: { sl@0: context.iJoinsAtEnd = breakPos < inputLength? sl@0: TRunInfoCompact::JoinBefore(input, breakPos) : EFalse; sl@0: } sl@0: else sl@0: { sl@0: context.iJoinsAtEnd = endOfLine < inputLength? sl@0: TRunInfoCompact::JoinBefore(input, endOfLine) : EFalse; sl@0: } sl@0: if (aOutputText) sl@0: { sl@0: for (const TRunInfoCompact* p = runArray; p != runArrayEnd; ++p) sl@0: aOutputText = p->Reorder(aOutputText, context); sl@0: } sl@0: // Set 'start' to the beginning of the next line... sl@0: start = endOfLine; sl@0: sl@0: // ...move it past any line break... sl@0: const TInt sizeOfLineBreak = SizeLineBreak(input + start, input + inputLength); sl@0: if (sizeOfLineBreak != 0) sl@0: { sl@0: start += sizeOfLineBreak; sl@0: // ...and find the end of this next line. sl@0: const TText* endLine = FindEndOfThisLine(input + start, input + inputLength); sl@0: measureInput.iEndInputChar = endLine - input; sl@0: } sl@0: } sl@0: sl@0: return widestLineWidth; sl@0: } sl@0: sl@0: sl@0: /** Prepares the visually-ordered text according to the wrapping width and font sl@0: specified. Text cannot be drawn until this has been done. sl@0: sl@0: @param aWrappingWidth The maximum width of the text in pixels. Note that this sl@0: distance should be slightly less than the available width to allow for characters sl@0: such as "W" which can have side-bearings that leak into the margins. sl@0: @param aFont The font that will provide the character metrics. sl@0: @param aBreaker An object for breaking the lines. May be NULL for default behaviour. */ sl@0: EXPORT_C void TBidiText::WrapText(TInt aWrappingWidth, const CFont& aFont, sl@0: const MLineBreaker* aBreaker) sl@0: { sl@0: WrapText(aWrappingWidth, aFont, aBreaker, KMaxTInt); sl@0: } sl@0: sl@0: /** Returns the original logically-ordered text supplied in the constructor. sl@0: @return The original logically-ordered text supplied in the constructor. */ sl@0: EXPORT_C TPtrC TBidiText::Text() const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: const TText* text = me->LogicalText(); sl@0: return TPtrC(text, me->TextLength()); sl@0: } sl@0: sl@0: /** Returns the text as prepared for display, provided that WrapText has been called. sl@0: If WrapText has not been called, a panic will result. sl@0: sl@0: @return The text as prepared for display */ sl@0: EXPORT_C TPtrC TBidiText::DisplayText() const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: __ASSERT_ALWAYS(me->iVisualOrderedTextLength >= 0, BidiPanic(EBidiPanic_InvalidVisualOrderedTextLength)); sl@0: const TText* text = me->VisualText(); sl@0: return TPtrC(text, me->iVisualOrderedTextLength); sl@0: } sl@0: sl@0: /** Returns the wrapping width previously supplied to WrapText. sl@0: sl@0: @return The wrapping. */ sl@0: EXPORT_C TInt TBidiText::WrappingWidth() const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: return me->iWrappingWidth; sl@0: } sl@0: sl@0: /** Returns the directionality of the text. sl@0: sl@0: @return The directionality. */ sl@0: EXPORT_C TBidiText::TDirectionality TBidiText::Directionality() const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: return me->HasRightToLeftDirectionality() ? ERightToLeft : ELeftToRight; sl@0: } sl@0: sl@0: /** Returns the truncation character used. sl@0: sl@0: @return The truncation character. */ sl@0: EXPORT_C TChar TBidiText::TruncationChar() const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: return me->TruncationChar(); sl@0: } sl@0: sl@0: /** Reports the number of lines in the text to be drawn. sl@0: sl@0: WrapText must have been called already. sl@0: @return sl@0: The number of lines in the text which would be drawn by DrawText. sl@0: */ sl@0: EXPORT_C TInt TBidiText::NumberOfLinesInDisplayText() const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: if (me->iVisualOrderedTextLength <0) sl@0: { sl@0: return 0; sl@0: } sl@0: const TText* text = me->VisualText(); sl@0: const TText* textEnd = text + me->iVisualOrderedTextLength; sl@0: return NumberOfLines(text, textEnd); sl@0: } sl@0: sl@0: /** Returns the text as prepared for display, provided that WrapText has been called. sl@0: If WrapText has not been called, a panic will result. sl@0: @param aLine Line number to retrieve. sl@0: @param aWidth Returns the width in pixels of the line retrieved. sl@0: @return The text as prepared for display. */ sl@0: EXPORT_C TPtrC TBidiText::LineOfDisplayText(TInt aLine, TInt& aWidthInPixels) const sl@0: { sl@0: const TBidiTextImp* me = TBidiTextImp::Imp(this); sl@0: __ASSERT_ALWAYS(me->iVisualOrderedTextLength >= 0, BidiPanic(EBidiPanic_InvalidVisualOrderedTextLength)); sl@0: __ASSERT_ALWAYS(0 <= aLine && aLine < me->iLines, BidiPanic(EBidiPanic_InvalidLineNumber)); sl@0: aWidthInPixels = me->LineWidthArray()[aLine]; sl@0: const TText* text = me->VisualText(); sl@0: const TText* textEnd = text + me->iVisualOrderedTextLength; sl@0: for (; aLine != 0; --aLine) sl@0: { sl@0: text = FindEndOfThisLine(text, textEnd); sl@0: text += SizeLineBreak(text, textEnd); sl@0: } sl@0: const TText* endOfLine = FindEndOfThisLine(text, textEnd); sl@0: return TPtrC(text, endOfLine - text); sl@0: } sl@0: sl@0: /** Draws all of the text. WrapText must have been called already. sl@0: sl@0: @param aGc The graphics context to draw the text to. The graphics context's sl@0: font is assumed to have been set to the same font that was passed to the previous sl@0: call to WrapText. sl@0: @param aLeft The left extreme of the baseline. Note that this should not be sl@0: at the very edge of the available space, or characters such as "W" with left sl@0: side bearings may be truncated. sl@0: @param aBaseLineSpacing The spacing between each line. If 0, only the first sl@0: line is drawn. sl@0: @param aAlignment How to position the text horizontally. */ sl@0: EXPORT_C void TBidiText::DrawText(CGraphicsContext& aGc, const TPoint& aLeft, sl@0: TInt aBaseLineSpacing, CGraphicsContext::TTextAlign aAlignment) const sl@0: { sl@0: TPoint origin; sl@0: origin.iY = aLeft.iY; sl@0: TInt lines = aBaseLineSpacing == 0? 1 : NumberOfLinesInDisplayText(); sl@0: TInt wrappingWidth = WrappingWidth(); sl@0: for (TInt i = 0; i != lines; ++i) sl@0: { sl@0: TInt width; sl@0: TPtrC textLine = LineOfDisplayText(i, width); sl@0: origin.iX = aLeft.iX; sl@0: if (aAlignment != CGraphicsContext::ELeft) sl@0: { sl@0: TInt excess = wrappingWidth - width; sl@0: origin.iX += aAlignment != CGraphicsContext::ECenter? sl@0: excess : excess >> 1; sl@0: } sl@0: aGc.DrawText(textLine, origin); sl@0: origin.iY += aBaseLineSpacing; sl@0: } sl@0: } sl@0: sl@0: /** Draws all of the text. Alignment is taken from the directionality of the text. sl@0: WrapText must have been called already. sl@0: sl@0: @param aGc The graphics context to draw the text to. The graphics context's sl@0: font is assumed to have been set to the same font that was passed to the previous sl@0: call to WrapText. sl@0: @param aLeft The left extreme of the baseline. Note that this should not be sl@0: at the very edge of the available space, or characters such as "W" with left sl@0: side bearings may be truncated. sl@0: @param aBaseLineSpacing The spacing between each line. If 0, only the first sl@0: line is drawn. */ sl@0: EXPORT_C void TBidiText::DrawText(CGraphicsContext& aGc, const TPoint& aLeft, sl@0: TInt aBaseLineSpacing) const sl@0: { sl@0: DrawText(aGc, aLeft, aBaseLineSpacing, sl@0: Directionality() == ELeftToRight? sl@0: CGraphicsContext::ELeft : CGraphicsContext::ERight); sl@0: } sl@0: sl@0: /** Draws the first line of the text. WrapText must have been called already. Alignment sl@0: is taken from the directionality of the text. sl@0: sl@0: @param aGc The graphics context to draw the text to. The graphics context's sl@0: font is assumed to have been set to the same font that was passed to the previous sl@0: call to WrapText. sl@0: @param aLeft The left extreme of the baseline. Note that this should not be sl@0: at the very edge of the available space, or characters such as "W" with left sl@0: side bearings may be truncated. */ sl@0: EXPORT_C void TBidiText::DrawText(CGraphicsContext& aGc, const TPoint& aLeft) const sl@0: { sl@0: DrawText(aGc, aLeft, 0); sl@0: } sl@0: