sl@0: // Copyright (c) 2000-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: // Bidirectional text reordering; based on the Unicode Bidirectional Reordering Algorithm. sl@0: // sl@0: // sl@0: sl@0: #include sl@0: #include "BidiCopy.h" sl@0: #include sl@0: sl@0: const TInt KBidirectionalStateOverrideStreamValueNone = 0; sl@0: const TInt KBidirectionalStateOverrideStreamValueLeftToRight = 1; sl@0: const TInt KBidirectionalStateOverrideStreamValueRightToLeft = 2; sl@0: sl@0: inline TBool IsSupplementary(TUint aChar) sl@0: /** sl@0: @param aChar The 32-bit code point value of a Unicode character. sl@0: sl@0: @return True, if aChar is supplementary character; false, otherwise. sl@0: */ sl@0: { sl@0: return (aChar > 0xFFFF); sl@0: } sl@0: sl@0: inline TBool IsHighSurrogate(TText16 aInt16) sl@0: /** sl@0: @return True, if aText16 is high surrogate; false, otherwise. sl@0: */ sl@0: { sl@0: return (aInt16 & 0xFC00) == 0xD800; sl@0: } sl@0: sl@0: inline TBool IsLowSurrogate(TText16 aInt16) sl@0: /** sl@0: @return True, if aText16 is low surrogate; false, otherwise. sl@0: */ sl@0: { sl@0: return (aInt16 & 0xFC00) == 0xDC00; sl@0: } sl@0: sl@0: inline TUint JoinSurrogate(TText16 aHighSurrogate, TText16 aLowSurrogate) sl@0: /** sl@0: Combine a high surrogate and a low surrogate into a supplementary character. sl@0: sl@0: @return The 32-bit code point value of the generated Unicode supplementary sl@0: character. sl@0: */ sl@0: { sl@0: return ((aHighSurrogate - 0xD7F7) << 10) + aLowSurrogate; sl@0: } sl@0: sl@0: TBool TextDefaultsToRightToLeft(const TDesC& aText, TBool* aFound); sl@0: sl@0: TBidirectionalState::TCategory TBidirectionalState::CharToBdCat(TChar::TBdCategory aCat) sl@0: { sl@0: return static_cast( sl@0: 1 << static_cast(aCat)); sl@0: } sl@0: sl@0: TBidirectionalState::TCategory TBidirectionalState::UintToBdCat(TUint aCat) sl@0: { sl@0: return static_cast(1 << aCat); sl@0: } sl@0: sl@0: void TBidirectionalState::TReorderContext::SetNextCategory( sl@0: TChar::TBdCategory aCat) sl@0: { sl@0: iNextCategory = CharToBdCat(aCat); sl@0: } sl@0: sl@0: void TBidirectionalState::TReorderContext::SetNextStrongCategory( sl@0: TChar::TBdCategory aCat) sl@0: { sl@0: iNextStrongCategory = CharToBdCat(aCat); sl@0: } sl@0: sl@0: sl@0: EXPORT_C void TBidirectionalState::ReverseGroups(TText* aStart,TInt aLength) sl@0: /** A utility to reverse text apart from combining characters, which remains after sl@0: their base characters. This is what is needed when drawing right-to-left text. sl@0: sl@0: @param aStart Start position of text to be reversed. sl@0: @param aLength Length of text to be reversed. */ sl@0: { sl@0: BidiCopy::ReverseCodes(aStart, aLength); sl@0: BidiCopy::DeleteUnreversedSurrogates(aStart, aLength); sl@0: BidiCopy::SubstituteMirrorImages(aStart, aLength); sl@0: BidiCopy::CorrectGroups(aStart, aLength); sl@0: BidiCopy::CorrectSurrogatePairs(aStart, aLength); sl@0: } sl@0: sl@0: sl@0: // A local helper function. Get the next character from a buffer. This sl@0: // function won't check buffer length. sl@0: // sl@0: // @param aText The text buffer to read character from. sl@0: // @param aCharacterIndex Count of characters to skip in aText. sl@0: // @return The character. sl@0: TUint GetOneCharacter(const TText16 *aText, TInt aCharacterIndex) sl@0: { sl@0: const TText16 *p = aText; sl@0: TUint c = 0xFFFF; sl@0: while (aCharacterIndex >= 0) sl@0: { sl@0: c = *p++; sl@0: ASSERT(!IsLowSurrogate(c)); sl@0: if (IsHighSurrogate(c)) sl@0: { sl@0: ASSERT(IsLowSurrogate(*p)); sl@0: c = JoinSurrogate(c, *p++); sl@0: } sl@0: --aCharacterIndex; sl@0: } sl@0: return c; sl@0: } sl@0: sl@0: sl@0: TInt TBidirectionalState::GenerateBdRunArray(const TText* aText, TInt aLength, sl@0: TBidirectionalState::TRunInfo* aRun, TInt aMaxRuns) sl@0: /** Analyse the input text for runs of characters that share the same sl@0: bidirectional class. Categories TChar::EEuropeanNumberSeparator and sl@0: TChar::ECommonNumberSeparator are kept as singletons due to a limitation in sl@0: the reordering logic. sl@0: @param aText The text to be analysed. sl@0: @param aLength The length of the text to be analysed. sl@0: @param aRun Output buffer for the runs after analysis. May be null if there sl@0: is to be no output. sl@0: @param aMaxRuns The size of the aRun array. No more than this number of runs sl@0: will be output. sl@0: @return The number of runs that are required for the full results of the sl@0: analysis. sl@0: @internalTechnology */ sl@0: { sl@0: if (aLength == 0) sl@0: { sl@0: if (aRun && 0 < aMaxRuns) sl@0: { sl@0: aRun[0].iCategory = TChar::EOtherNeutral; sl@0: aRun[0].iStart = 0; sl@0: aRun[0].iLength = 0; sl@0: } sl@0: return 1; sl@0: } sl@0: int runs = 0; sl@0: int run_start = 0; sl@0: int run_end = 1; sl@0: const TText* p = aText; sl@0: const TText* q = p + aLength; sl@0: sl@0: // get the character pointed by 'p', then move 'p' to next character, and adjust 'run_end' if need sl@0: TChar pc = ::GetOneCharacter(p, 0); sl@0: TChar::TBdCategory cur_cat = pc.GetBdCategory(); sl@0: ++p; sl@0: if (IsSupplementary(pc)) sl@0: { sl@0: ++p; sl@0: run_end = 2; // run_end points to "end" of current character sl@0: } sl@0: sl@0: while (p < q) sl@0: { sl@0: // get the character pointed by 'p' sl@0: pc = ::GetOneCharacter(p, 0); sl@0: sl@0: TChar::TBdCategory new_cat = pc.GetBdCategory(); sl@0: if (new_cat != cur_cat) sl@0: { sl@0: if (new_cat == TChar::ENonSpacingMark && sl@0: cur_cat != TChar::ELeftToRightEmbedding && sl@0: cur_cat != TChar::ELeftToRightOverride && sl@0: cur_cat != TChar::ERightToLeftEmbedding && sl@0: cur_cat != TChar::ERightToLeftOverride && sl@0: cur_cat != TChar::EPopDirectionalFormat) sl@0: new_cat = cur_cat; sl@0: else if (p < q - 1 && sl@0: (new_cat == TChar::EWhitespace || sl@0: new_cat == TChar::EEuropeanNumberSeparator || sl@0: new_cat == TChar::ECommonNumberSeparator)) sl@0: { sl@0: TChar nextChar = ::GetOneCharacter(p, 1); sl@0: TChar::TBdCategory next_cat = nextChar.GetBdCategory(); sl@0: if (new_cat == TChar::EWhitespace) sl@0: { sl@0: if ((cur_cat == TChar::ELeftToRight || sl@0: cur_cat == TChar::ERightToLeft || sl@0: cur_cat == TChar::ERightToLeftArabic) && cur_cat == next_cat) sl@0: new_cat = cur_cat; sl@0: } sl@0: else if (cur_cat == TChar::EEuropeanNumber && next_cat == TChar::EEuropeanNumber) sl@0: new_cat = TChar::EEuropeanNumber; sl@0: } sl@0: } sl@0: sl@0: if (new_cat != cur_cat || sl@0: cur_cat == TChar::EEuropeanNumberSeparator || sl@0: cur_cat == TChar::ECommonNumberSeparator) sl@0: { sl@0: if (aRun && runs < aMaxRuns) sl@0: { sl@0: aRun[runs].iCategory = cur_cat; sl@0: aRun[runs].iStart = run_start; sl@0: aRun[runs].iLength = run_end - run_start; sl@0: } sl@0: sl@0: runs++; sl@0: run_start = run_end; sl@0: } sl@0: sl@0: p++; sl@0: run_end++; sl@0: sl@0: // adjust 'p' and 'run_end' sl@0: if (IsSupplementary(pc)) sl@0: { sl@0: p++; sl@0: run_end++; sl@0: } sl@0: sl@0: cur_cat = new_cat; sl@0: } sl@0: sl@0: if (aRun && runs < aMaxRuns) sl@0: { sl@0: aRun[runs].iCategory = cur_cat; sl@0: aRun[runs].iStart = run_start; sl@0: aRun[runs].iLength = run_end - run_start; sl@0: } sl@0: sl@0: return runs + 1; sl@0: } sl@0: sl@0: sl@0: EXPORT_C TInt TBidirectionalState::ReorderText(const TText* aText,TInt aLength,TBool aParRightToLeft, sl@0: TText*& aNewText) sl@0: /** Reorders text according to the Unicode Bidirectional Reordering algorithm. sl@0: sl@0: Reorders the input text from logical order (which may be bidirectional) to sl@0: display order (strictly left to right). sl@0: sl@0: @param aText The input text in logical order. sl@0: @param aLength The length of the input text. sl@0: @param aParRightToLeft ETrue if the default directionality of the text to be sl@0: re-ordered is right-to-left. sl@0: @param aNewText Returns the re-ordered text. If the text did not need re-ordering, sl@0: or if there was an error, aText will be returned. Otherwise, ownership of sl@0: a newly allocated buffer will be returned to the caller. This buffer must sl@0: be deleted with delete[] (or CleanupArrayDeletePushL()) and not delete (or sl@0: CleanupStack::PushL()). sl@0: @return A system-wide error value if there has been an error; KErrNone if there sl@0: has not. */ sl@0: { sl@0: aNewText = const_cast(aText); sl@0: if (aLength < 2) sl@0: return KErrNone; sl@0: sl@0: int error = KErrNone; sl@0: TBidirectionalState::TRunInfo run_info; sl@0: run_info.iDirection = 0; sl@0: run_info.iIndex = 0; sl@0: run_info.iStart = 0; sl@0: run_info.iLength = aLength; sl@0: TBidirectionalState::TRunInfo* run_info_array = &run_info; sl@0: TBidirectionalState::TRunInfo* allocated_run_info_array = 0; sl@0: int runs = GenerateBdRunArray(aText, aLength, run_info_array, 1); sl@0: if (runs > 1) sl@0: { sl@0: allocated_run_info_array = new TBidirectionalState::TRunInfo[runs]; sl@0: if (allocated_run_info_array) sl@0: { sl@0: run_info_array = allocated_run_info_array; sl@0: GenerateBdRunArray(aText, aLength, run_info_array, runs); sl@0: } sl@0: else sl@0: { sl@0: // the run cannot be allocated: stick with our single l-to-r run sl@0: error = KErrNoMemory; sl@0: runs = 1; sl@0: } sl@0: } sl@0: if (error == KErrNone) sl@0: { sl@0: TBidirectionalState state; sl@0: state.ReorderLine(run_info_array, runs, ETrue, ETrue, aParRightToLeft, sl@0: TChar::EOtherNeutral, TChar::EOtherNeutral); sl@0: } sl@0: sl@0: // If there was only one run and it's left-to-right, we've finished. sl@0: if (!allocated_run_info_array && run_info.iDirection == 0) sl@0: return error; sl@0: sl@0: // Reorder the text into a new buffer. sl@0: TText* buffer = new TText[aLength]; sl@0: if (!buffer) sl@0: { sl@0: delete [] allocated_run_info_array; sl@0: return KErrNoMemory; sl@0: } sl@0: const TBidirectionalState::TRunInfo* r = run_info_array; sl@0: TText* dest = buffer; sl@0: for (int i = 0; i < runs; i++, r++) sl@0: { sl@0: const TText* source = &aText[r->iStart]; sl@0: int length = r->iLength; sl@0: Mem::Copy(dest,source,length * sizeof(TText)); sl@0: if (r->iDirection) sl@0: ReverseGroups(dest,length); sl@0: dest += length; sl@0: } sl@0: sl@0: delete [] allocated_run_info_array; sl@0: aNewText = buffer; sl@0: return KErrNone; sl@0: } sl@0: sl@0: sl@0: EXPORT_C TBidirectionalState::TBidirectionalState() sl@0: /** Standard constructor. */ sl@0: { sl@0: Reset(); sl@0: } sl@0: sl@0: sl@0: /** Reorders a line of text and updates the bidirectional state for the next line. sl@0: sl@0: @param aRunInfo An array of objects representing runs of characters with the sl@0: same bidirectional category. Any number of characters can be combined into sl@0: a run if they have the same category, except for the categories TChar::EEuropeanNumberSeparator sl@0: and TChar::ECommonNumberSeparator, which should be put into single-character sl@0: runs because the reordering logic depends on this. sl@0: @param aRuns Number of 'run info' objects. sl@0: @param aParStart Tells the function whether the line is the first line of a sl@0: paragraph. sl@0: @param aParEnd Tells the function whether the line is the last line of a paragraph. sl@0: @param aParRightToLeft ETrue if the default directionality of the text to be sl@0: re-ordered is right-to-left. sl@0: @param aNextCategory The category of the character immediately after the end sl@0: of the line. This is ignored if aParEnd is ETrue. sl@0: @param aNextStrongCategory The category of the first strong character (one sl@0: of the categories ELeftToRight, ELeftToRightEmbedding, ELeftToRightOverride, sl@0: ERightToLeft, ERightToLeftArabic, ERightToLeftEmbedding or ERightToLeftOverride) sl@0: after the end of the line. This is ignored if aParEnd is ETrue. sl@0: @param aVisualEndIsAmbiguous EFalse if the logical end of this line is at the sl@0: visual end and the logical beginning of the next line is at the visual beginning. sl@0: */ sl@0: EXPORT_C void TBidirectionalState::ReorderLine(TRunInfo* aRunInfo, TInt aRuns, sl@0: TBool aParStart, TBool aParEnd, TBool aParRightToLeft, sl@0: TChar::TBdCategory aNextCategory, TChar::TBdCategory aNextStrongCategory, sl@0: TBool& aVisualEndIsAmbiguous) sl@0: { sl@0: ReorderLine(aRunInfo, aRuns, aParStart, aParEnd, aParRightToLeft, sl@0: aNextCategory, aNextStrongCategory); sl@0: if (iStackLevel != 0) sl@0: { sl@0: aVisualEndIsAmbiguous = ETrue; sl@0: return; sl@0: } sl@0: TCategory nextCat = CharToBdCat(aNextCategory); sl@0: TCategory nextStrong = CharToBdCat(aNextStrongCategory); sl@0: const TUint KAllStrongLeftToRight = sl@0: ELeftToRight | ELeftToRightEmbedding | ELeftToRightOverride; sl@0: const TUint KAllStrongRightToLeft = sl@0: ERightToLeft | ERightToLeftArabic | ERightToLeftEmbedding | ERightToLeftOverride; sl@0: if (aParRightToLeft) sl@0: { sl@0: // Ambiguous if any of the surrounding categories are strongly left-to-right sl@0: aVisualEndIsAmbiguous = sl@0: (iPreviousStrongCategory | iPreviousCategory | nextCat | nextStrong) sl@0: & KAllStrongLeftToRight; sl@0: } sl@0: else sl@0: { sl@0: // Ambiguous if any of the surrounding categories are strongly right-to-left sl@0: aVisualEndIsAmbiguous = sl@0: (iPreviousStrongCategory | iPreviousCategory | nextCat | nextStrong) sl@0: & KAllStrongRightToLeft; sl@0: } sl@0: } sl@0: /** Reorders a line of text and updates the bidirectional state for the next line. sl@0: sl@0: @param aRunInfo An array of objects representing runs of characters with the sl@0: same bidirectional category. Any number of characters can be combined into sl@0: a run if they have the same category, except for the categories TChar::EEuropeanNumberSeparator sl@0: and TChar::ECommonNumberSeparator, which should be put into single-character sl@0: runs because the reordering logic depends on this. sl@0: @param aRuns Number of 'run info' objects. sl@0: @param aParStart Tells the function whether the line is the first line of a sl@0: paragraph. sl@0: @param aParEnd Tells the function whether the line is the last line of a paragraph. sl@0: @param aParRightToLeft ETrue if the default directionality of the text to be sl@0: re-ordered is right-to-left. sl@0: @param aNextCategory The category of the character immediately after the end sl@0: of the line. This is ignored if aParEnd is ETrue. sl@0: @param aNextStrongCategory The category of the first strong character (one sl@0: of the categories ELeftToRight, ELeftToRightEmbedding, ELeftToRightOverride, sl@0: ERightToLeft, ERightToLeftArabic, ERightToLeftEmbedding or ERightToLeftOverride) sl@0: after the end of the line. This is ignored if aParEnd is ETrue. */ sl@0: EXPORT_C void TBidirectionalState::ReorderLine(TRunInfo* aRunInfo, TInt aRuns, sl@0: TBool aParStart, TBool aParEnd, TBool aParRightToLeft, sl@0: TChar::TBdCategory aNextCategory, TChar::TBdCategory aNextStrongCategory) sl@0: { sl@0: // Reset if this is a new paragraph. sl@0: if (aParStart) sl@0: { sl@0: Reset(); sl@0: iPreviousCategory = EOtherNeutral; sl@0: if (aParRightToLeft) sl@0: { sl@0: iStack[0].iEmbeddingLevel = 1; sl@0: iPreviousStrongCategory = ERightToLeft; sl@0: } sl@0: } sl@0: sl@0: // Initialise the context object. sl@0: TReorderContext context; sl@0: context.iRunInfo = aRunInfo; sl@0: context.iRuns = aRuns; sl@0: context.iLastStrongCategory = iPreviousStrongCategory; sl@0: if (aParEnd) sl@0: context.iNextCategory = context.iNextStrongCategory = EOtherNeutral; sl@0: else sl@0: { sl@0: context.iNextCategory = CharToBdCat(aNextCategory); sl@0: context.iNextStrongCategory = CharToBdCat(aNextStrongCategory); sl@0: } sl@0: sl@0: // Initialise output data and find out what categories are present so that unnecessary steps can be skipped. sl@0: context.iCategories = iPreviousCategory | context.iNextCategory | context.iNextStrongCategory; sl@0: for (TInt i = 0; i != aRuns; ++i) sl@0: { sl@0: aRunInfo[i].iEmbeddingLevel = iStack[0].iEmbeddingLevel; sl@0: aRunInfo[i].iDirection = 0; sl@0: aRunInfo[i].iIndex = i; sl@0: aRunInfo[i].iCategory = UintToBdCat(aRunInfo[i].iCategory); sl@0: context.iCategories |= aRunInfo[i].iCategory; sl@0: } sl@0: sl@0: // Do nothing if no right-to-left material is present. sl@0: if (aRuns == 0 || sl@0: (iStackLevel == 0 && iStack[0].iEmbeddingLevel == 0 && sl@0: !(context.iCategories & (ERightToLeftGroup | EBdControlsGroup)))) sl@0: return; sl@0: sl@0: // Perform the bidirectional algorithm. sl@0: if ((context.iCategories & EBdControlsGroup) || sl@0: State().iOverrideState != ENoOverrideState) sl@0: HandleBdControls(context); sl@0: ResolveWeakTypesW1W2W3(context); sl@0: ResolveWeakTypesW4W5W6(context); sl@0: ResolveWeakTypesW7(context); sl@0: if (context.iCategories & EOtherNeutral) sl@0: ResolveNeutralTypes(context); sl@0: ResolveImplicitLevels(context); sl@0: PrepareForNextLine(context); sl@0: ReorderRuns(context); sl@0: } sl@0: sl@0: sl@0: void TBidirectionalState::PrepareForNextLine(const TReorderContext& aContext) sl@0: /** sl@0: Fold context information back into TBidirectionalState. sl@0: @internalComponent sl@0: */ sl@0: { sl@0: if (aContext.iRuns != 0) sl@0: { sl@0: iPreviousCategory = static_cast( sl@0: aContext.iRunInfo[aContext.iRuns - 1].iCategory); sl@0: iPreviousStrongCategory = aContext.iLastStrongCategory; sl@0: } sl@0: } sl@0: sl@0: sl@0: void TBidirectionalState::HandleBdControls(TReorderContext& aContext) sl@0: /** sl@0: Handle LRO, RLO, LRE, RLE and PDF. After this phase, these categories will no sl@0: longer be found. sl@0: sl@0: This corresponds to Unicode(3.2) Bidirectional Algorithm phases X1-X7. sl@0: Phase X8 is not required as the run is assumed to be all in one paragraph. sl@0: Phases X9-X10 are implicit in other functions. sl@0: sl@0: @internalComponent sl@0: */ sl@0: { sl@0: aContext.iCategories = iPreviousCategory | aContext.iNextCategory; sl@0: for (TInt i = 0; i != aContext.iRuns; ++i) sl@0: { sl@0: TRunInfo* r = aContext.iRunInfo + i; sl@0: TCategory nextCatInLine = i < aContext.iRuns - 1? sl@0: (TCategory)(r[1].iCategory) : ENoCategory; sl@0: sl@0: TBool was_pdf = FALSE; sl@0: if (r->iCategory & EBdControlsGroup) sl@0: { sl@0: if (r->iCategory == EPopDirectionalFormat) sl@0: { sl@0: if (iStackLevel > 0) sl@0: { sl@0: was_pdf = TRUE; sl@0: r->iEmbeddingLevel = State().iEmbeddingLevel; sl@0: if (nextCatInLine == State().iStartCategory) sl@0: // Ignore POP-PUSH pair with nothing between. sl@0: // This is surely wrong? Perhaps it is a hack to sl@0: // help other parts of the algorithm. Must investigate. sl@0: // TPB. sl@0: r->iCategory = r[1].iCategory = EBoundaryNeutral; sl@0: else sl@0: { sl@0: r->iCategory = Pop(); sl@0: } sl@0: } sl@0: else sl@0: r->iCategory = EBoundaryNeutral; sl@0: } sl@0: else sl@0: { sl@0: // Category is LRE, RLE, LRO or RLO. sl@0: if (nextCatInLine == EPopDirectionalFormat) sl@0: // Ignore PUSH-POP pair with nothing between. sl@0: r->iCategory = r[1].iCategory = EBoundaryNeutral; sl@0: else sl@0: r->iCategory = Push(static_cast(r->iCategory)); sl@0: } sl@0: } sl@0: sl@0: if (!was_pdf) sl@0: { sl@0: switch (State().iOverrideState) sl@0: { sl@0: case ELeftToRightOverrideState: sl@0: r->iCategory = ELeftToRight; sl@0: break; sl@0: case ERightToLeftOverrideState: sl@0: r->iCategory = ERightToLeft; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: r->iEmbeddingLevel = State().iEmbeddingLevel; sl@0: } sl@0: if (r->iCategory & EStrongGroup) sl@0: aContext.iLastStrongCategory = static_cast(r->iCategory); sl@0: aContext.iCategories |= r->iCategory; sl@0: } sl@0: } sl@0: sl@0: sl@0: void TBidirectionalState::ResolveWeakTypesW1W2W3(TReorderContext& aContext) sl@0: /** sl@0: Unicode(3.2) Bidirectional Algorithm phases W1, W2 and W3. sl@0: @internalComponent sl@0: */ sl@0: { sl@0: if (!(aContext.iCategories sl@0: & (ENonSpacingMark | ERightToLeftArabic | EEuropeanNumber))) sl@0: return; sl@0: sl@0: TRunInfo* endOfRuns = aContext.iRunInfo + aContext.iRuns; sl@0: TCategory prev_cat = iPreviousCategory; sl@0: TBool european_to_arabic_number = iPreviousStrongCategory == ERightToLeftArabic; sl@0: sl@0: aContext.iCategories = iPreviousCategory | aContext.iNextCategory; sl@0: for (TRunInfo* r = aContext.iRunInfo; r != endOfRuns; r++) sl@0: { sl@0: switch (r->iCategory) sl@0: { sl@0: case ENonSpacingMark: // non-spacing marks change to the previous category sl@0: r->iCategory = prev_cat; sl@0: break; sl@0: case ELeftToRight: sl@0: european_to_arabic_number = EFalse; sl@0: break; sl@0: case ERightToLeft: sl@0: european_to_arabic_number = EFalse; sl@0: break; sl@0: case ERightToLeftArabic: // Arabic letters change to R sl@0: european_to_arabic_number = ETrue; sl@0: r->iCategory = ERightToLeft; sl@0: break; sl@0: case EEuropeanNumber: // European numbers change to Arabic if last strong category was R sl@0: if (european_to_arabic_number) sl@0: r->iCategory = EArabicNumber; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: aContext.iCategories |= r->iCategory; sl@0: prev_cat = static_cast(r->iCategory); sl@0: } sl@0: } sl@0: /** sl@0: This phase removes categories NSM, AL, ES, ET, CS, BS, S, WS and BN, leaving sl@0: only L, R, EN, AN and ON. sl@0: @internalComponent sl@0: */ sl@0: void TBidirectionalState::ResolveWeakTypesW4W5W6(TReorderContext& aContext) sl@0: { sl@0: int i; sl@0: TRunInfo* r; sl@0: TCategory prev_cat = iPreviousCategory; sl@0: TCategory next_cat = EOtherNeutral; sl@0: sl@0: // Phase P0b. sl@0: prev_cat = iPreviousCategory; sl@0: if (aContext.iCategories & EBoundaryNeutral) sl@0: { sl@0: for (i = 0, aContext.iCategories = iPreviousCategory | aContext.iNextCategory, r = aContext.iRunInfo; sl@0: i < aContext.iRuns; sl@0: i++, aContext.iCategories |= r->iCategory, r++) sl@0: { sl@0: if (r->iCategory == EBoundaryNeutral) // runs of boundary neutrals change to EN, ET or AN if adjacent to sl@0: { // one of these, otherwise to ON sl@0: int end = i + 1; sl@0: while (end < aContext.iRuns && aContext.iRunInfo[end].iCategory == EBoundaryNeutral) sl@0: end++; sl@0: next_cat = end < aContext.iRuns ? (TCategory)(aContext.iRunInfo[end].iCategory) : aContext.iNextCategory; sl@0: TCategory c = EOtherNeutral; sl@0: if (prev_cat == EEuropeanNumber || next_cat == EEuropeanNumber) sl@0: c = EEuropeanNumber; sl@0: else if (prev_cat == EEuropeanNumberTerminator || next_cat == EEuropeanNumberTerminator) sl@0: c = EEuropeanNumberTerminator; sl@0: else if (prev_cat == EArabicNumber || next_cat == EArabicNumber) sl@0: c = EArabicNumber; sl@0: for (int j = i; j < end; j++) sl@0: aContext.iRunInfo[j].iCategory = c; sl@0: i = end - 1; sl@0: r = &aContext.iRunInfo[i]; sl@0: } sl@0: prev_cat = (TCategory)r->iCategory; sl@0: } sl@0: } sl@0: sl@0: // Phase P1. sl@0: prev_cat = iPreviousCategory; sl@0: if (aContext.iCategories & (EEuropeanNumberSeparator | ECommonNumberSeparator)) sl@0: { sl@0: for (i = 0, aContext.iCategories = iPreviousCategory | aContext.iNextCategory, r = aContext.iRunInfo; sl@0: i < aContext.iRuns; sl@0: i++, aContext.iCategories |= r->iCategory, r++) sl@0: { sl@0: next_cat = i < aContext.iRuns - 1 ? (TCategory)(r[1].iCategory) : aContext.iNextCategory; sl@0: switch (r->iCategory) sl@0: { sl@0: case EEuropeanNumberSeparator: // European separators change to EN if between two ENs, else to ON sl@0: if (prev_cat == EEuropeanNumber && next_cat == EEuropeanNumber) sl@0: r->iCategory = EEuropeanNumber; sl@0: else sl@0: r->iCategory = EOtherNeutral; sl@0: break; sl@0: case ECommonNumberSeparator: // CSs change to EN or AN if between two of the same, else to ON sl@0: if (prev_cat == EEuropeanNumber && next_cat == EEuropeanNumber) sl@0: r->iCategory = EEuropeanNumber; sl@0: else if (prev_cat == EArabicNumber && next_cat == EArabicNumber) sl@0: r->iCategory = EArabicNumber; sl@0: else sl@0: r->iCategory = EOtherNeutral; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: prev_cat = (TCategory)r->iCategory; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: Phase L1: tabs, whitespace before tabs, and trailing whitespace, is set to the base embedding level. sl@0: We ought to do this just before the final reordering, but the whitespace and segment separator sl@0: categories have disappeared by then so we use the sentinel value 255 which tells sl@0: ResolveImplicitLevels what to do. sl@0: */ sl@0: TBool demote_whitespace = TRUE; sl@0: for (i = aContext.iRuns - 1, r = &aContext.iRunInfo[i]; i >= 0; i--, r--) sl@0: { sl@0: switch (r->iCategory) sl@0: { sl@0: case EWhitespace: sl@0: break; sl@0: case ESegmentSeparator: sl@0: demote_whitespace = TRUE; sl@0: break; sl@0: default: sl@0: demote_whitespace = FALSE; sl@0: break; sl@0: } sl@0: if (demote_whitespace) sl@0: r->iEmbeddingLevel = 255; sl@0: } sl@0: sl@0: // Phases P2 and P3. sl@0: prev_cat = iPreviousCategory; sl@0: if (aContext.iCategories & (EEuropeanNumberTerminator | ESegmentSeparator | EWhitespace)) sl@0: { sl@0: for (i = 0, aContext.iCategories = iPreviousCategory | aContext.iNextCategory, r = aContext.iRunInfo; sl@0: i < aContext.iRuns; sl@0: i++, aContext.iCategories |= r->iCategory, r++) sl@0: { sl@0: next_cat = i < aContext.iRuns - 1 ? (TCategory)(r[1].iCategory) : aContext.iNextCategory; sl@0: switch (r->iCategory) sl@0: { sl@0: case EEuropeanNumberTerminator: // runs of ETs change to ENs if next to an EN, else to ON sl@0: { sl@0: int end = i + 1; sl@0: while (end < aContext.iRuns && aContext.iRunInfo[end].iCategory == EEuropeanNumberTerminator) sl@0: end++; sl@0: next_cat = end < aContext.iRuns ? (TCategory)(aContext.iRunInfo[end].iCategory) : aContext.iNextCategory; sl@0: TCategory c = EOtherNeutral; sl@0: if (prev_cat == EEuropeanNumber || next_cat == EEuropeanNumber) sl@0: c = EEuropeanNumber; sl@0: for (int j = i; j < end; j++) sl@0: aContext.iRunInfo[j].iCategory = c; sl@0: i = end - 1; sl@0: r = &aContext.iRunInfo[i]; sl@0: } sl@0: break; sl@0: case ESegmentSeparator: // S and WS change to ON sl@0: case EWhitespace: sl@0: r->iCategory = EOtherNeutral; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: prev_cat = (TCategory)r->iCategory; sl@0: } sl@0: } sl@0: } sl@0: sl@0: void TBidirectionalState::ResolveWeakTypesW7(TReorderContext& aContext) sl@0: { sl@0: if (!(aContext.iCategories & EEuropeanNumber)) sl@0: return; sl@0: sl@0: TCategory prev_strong_cat = iPreviousStrongCategory; sl@0: sl@0: aContext.iCategories = iPreviousCategory | aContext.iNextCategory; sl@0: TRunInfo* endOfRuns = aContext.iRunInfo + aContext.iRuns; sl@0: for (TRunInfo* r = aContext.iRunInfo; r != endOfRuns; r++) sl@0: { sl@0: switch (r->iCategory) sl@0: { sl@0: case ELeftToRight: sl@0: prev_strong_cat = ELeftToRight; sl@0: break; sl@0: case ERightToLeft: sl@0: prev_strong_cat = ERightToLeft; sl@0: break; sl@0: case EEuropeanNumber: sl@0: if (prev_strong_cat == ELeftToRight) sl@0: r->iCategory = ELeftToRight; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: aContext.iCategories |= r->iCategory; sl@0: } sl@0: } sl@0: sl@0: sl@0: sl@0: void TBidirectionalState::DeneutralizeRuns(TRunInfo* aStart, TRunInfo* aEnd, sl@0: TCategory aStartCategory, TCategory aEndCategory) sl@0: /** sl@0: Turn all ON (Other Neutral) into non-neutrals according to the rules N1 and N2. sl@0: @param aStart The start of the run array to be altered. sl@0: @param aEnd One past the end of the run array to be altered. sl@0: @param aStartCategory sl@0: The last non-neutral before the run, must be ELeftToRight or ERightToLeft. sl@0: @param aEndCategory sl@0: The first non-neutral after the run, must be ELeftToRight or ERightToLeft. sl@0: @internalComponent sl@0: */ { sl@0: // if sandwiched by the same category, neutrals become that. sl@0: if (aStartCategory == aEndCategory) sl@0: { sl@0: for (; aStart != aEnd; ++aStart) sl@0: aStart->iCategory = aStartCategory; sl@0: return; sl@0: } sl@0: // otherwise look at the embedding level in each case sl@0: for (; aStart != aEnd; ++aStart) sl@0: { sl@0: aStart->iCategory = aStart->iEmbeddingLevel & 1? sl@0: ERightToLeft : ELeftToRight; sl@0: } sl@0: } sl@0: sl@0: sl@0: void TBidirectionalState::ResolveNeutralTypes(TReorderContext& aContext) sl@0: /** sl@0: This phase removes the ON (Other Neutral) category, leaving only L, R, EN, and sl@0: AN; no need to update aContext.iCategories. sl@0: @internalComponent sl@0: */ sl@0: { sl@0: // Really we should find if any number intervenes, but this would require sl@0: // a BC break. sl@0: TCategory prevNonNeutral = iPreviousStrongCategory; sl@0: if (prevNonNeutral & ELeftToRightGroup) sl@0: prevNonNeutral = ELeftToRight; sl@0: else if (prevNonNeutral & ERightToLeftGroup) sl@0: prevNonNeutral = ERightToLeft; sl@0: TRunInfo *prevNonNeutralRun = aContext.iRunInfo; // one past the last non-neutral found sl@0: TRunInfo *endOfRuns = aContext.iRunInfo + aContext.iRuns; sl@0: sl@0: // All neutrals have now been changed to ON; change them to L or R depending on context. sl@0: for (TRunInfo *p = aContext.iRunInfo; p != endOfRuns; ++p) sl@0: { sl@0: TCategory nonNeutral = EOtherNeutral; sl@0: switch (p->iCategory) sl@0: { sl@0: case ELeftToRight: sl@0: nonNeutral = ELeftToRight; sl@0: break; sl@0: case ERightToLeft: sl@0: nonNeutral = ERightToLeft; sl@0: break; sl@0: case EArabicNumber: sl@0: case EEuropeanNumber: sl@0: nonNeutral = ERightToLeft; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: if (nonNeutral != EOtherNeutral) sl@0: { sl@0: if (p != prevNonNeutralRun) sl@0: DeneutralizeRuns(prevNonNeutralRun, p, sl@0: prevNonNeutral, nonNeutral); sl@0: prevNonNeutral = nonNeutral; sl@0: prevNonNeutralRun = p + 1; sl@0: } sl@0: } sl@0: DeneutralizeRuns(prevNonNeutralRun, endOfRuns, prevNonNeutral, sl@0: aContext.iNextStrongCategory); sl@0: } sl@0: sl@0: sl@0: void TBidirectionalState::ResolveImplicitLevels(TReorderContext& aContext) sl@0: /** sl@0: Phases I1 and I2. sl@0: @internalComponent sl@0: */ { sl@0: int i; sl@0: TRunInfo* r; sl@0: for (i = 0, r = aContext.iRunInfo; i < aContext.iRuns; i++, r++) sl@0: { sl@0: if (r->iEmbeddingLevel == 255) // sentinel indicating this is a tab or segment-final whitespace sl@0: r->iEmbeddingLevel = iStack[0].iEmbeddingLevel; sl@0: else switch (r->iCategory) sl@0: { sl@0: case ELeftToRight: sl@0: if (r->iEmbeddingLevel & 1) sl@0: r->iEmbeddingLevel++; sl@0: break; sl@0: case ERightToLeft: sl@0: if (!(r->iEmbeddingLevel & 1)) sl@0: r->iEmbeddingLevel++; sl@0: break; sl@0: case EEuropeanNumber: case EArabicNumber: sl@0: if (r->iEmbeddingLevel & 1) sl@0: r->iEmbeddingLevel++; sl@0: else sl@0: r->iEmbeddingLevel += 2; sl@0: break; sl@0: default: sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: void TBidirectionalState::ReorderRuns(TReorderContext& aContext) sl@0: /** sl@0: Phase L2. sl@0: @internalComponent sl@0: */ { sl@0: // Find the highest level and lowest odd level. sl@0: int i; sl@0: TRunInfo* r; sl@0: int highest = 0; sl@0: int lowest_odd = EMaxLevel; sl@0: int level = 0; sl@0: for (i = 0, r = aContext.iRunInfo; i < aContext.iRuns; i++, r++) sl@0: { sl@0: level = r->iEmbeddingLevel; sl@0: if (level > highest) sl@0: highest = level; sl@0: if ((level & 1) && level < lowest_odd) sl@0: lowest_odd = level; sl@0: } sl@0: sl@0: // From the highest level to the lowest odd level, reverse any run at that level or higher. sl@0: for (level = highest; level >= lowest_odd; level--) sl@0: { sl@0: int run_start = 0; sl@0: r = aContext.iRunInfo; sl@0: while (run_start < aContext.iRuns) sl@0: { sl@0: while (run_start < aContext.iRuns && r->iEmbeddingLevel < level) sl@0: { sl@0: run_start++; sl@0: r++; sl@0: } sl@0: int run_end = run_start; sl@0: while (run_end < aContext.iRuns && r->iEmbeddingLevel >= level) sl@0: { sl@0: r->iDirection = !r->iDirection; sl@0: run_end++; sl@0: r++; sl@0: } sl@0: TRunInfo* p = &aContext.iRunInfo[run_start]; sl@0: TRunInfo* q = &aContext.iRunInfo[run_end - 1]; sl@0: while (p < q) sl@0: { sl@0: TRunInfo temp = *p; sl@0: *p = *q; sl@0: *q = temp; sl@0: p++; sl@0: q--; sl@0: } sl@0: run_start = run_end; sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: TBidirectionalState::TCategory TBidirectionalState::Push(TCategory aStartCategory) sl@0: /** @internalComponent */ sl@0: { sl@0: TInt rightToLeftFlag = (static_cast(aStartCategory) sl@0: & ERightToLeftGroup)? 1 : 0; sl@0: TInt oldLevel = State().iEmbeddingLevel; sl@0: TInt newLevel = oldLevel + 1; sl@0: // And add an extra one if the bottom bit is not correct. sl@0: newLevel += (newLevel & 1) ^ rightToLeftFlag; sl@0: sl@0: if (EMaxExplicitLevel < newLevel) sl@0: { sl@0: if (oldLevel == 60) sl@0: ++iPushesBeyond60; sl@0: else sl@0: ++iPushesBeyond61; sl@0: return EBoundaryNeutral; sl@0: } sl@0: sl@0: ++iStackLevel; sl@0: TStackItem& state = iStack[iStackLevel]; sl@0: state.iEmbeddingLevel = static_cast(newLevel); sl@0: state.iOverrideState = static_cast(aStartCategory sl@0: & (ELeftToRightOverride | ERightToLeftOverride)); sl@0: state.iStartCategory = aStartCategory; sl@0: sl@0: return rightToLeftFlag? ERightToLeft : ELeftToRight; sl@0: } sl@0: sl@0: sl@0: TBidirectionalState::TCategory TBidirectionalState::Pop() sl@0: /** @internalComponent */ sl@0: { sl@0: __ASSERT_DEBUG(0 < iStackLevel, User::Invariant()); sl@0: TInt level = State().iEmbeddingLevel; sl@0: if (level < 60) sl@0: --iStackLevel; sl@0: else if (iPushesBeyond61 != 0) sl@0: --iPushesBeyond61; sl@0: else if (level == 61) sl@0: --iStackLevel; sl@0: else if (iPushesBeyond60) sl@0: --iPushesBeyond60; sl@0: else sl@0: --iStackLevel; sl@0: return (level & 1)? ERightToLeft : ELeftToRight; sl@0: } sl@0: sl@0: sl@0: EXPORT_C void TBidirectionalState::Reset() sl@0: /** Sets the object to its default 'start of paragraph' state. */ sl@0: { sl@0: iStackLevel = 0; sl@0: iPushesBeyond60 = 0; sl@0: iPushesBeyond61 = 0; sl@0: iStack[0].iEmbeddingLevel = 0; sl@0: iStack[0].iOverrideState = ENoOverrideState; sl@0: iStack[0].iStartCategory = EOtherNeutral; sl@0: iPreviousCategory = ELeftToRight; sl@0: iPreviousStrongCategory = ELeftToRight; sl@0: } sl@0: sl@0: sl@0: EXPORT_C TBool TBidirectionalState::IsDefault() const sl@0: /** Returns Gets the default 'start of paragraph' state. sl@0: sl@0: @return ETrue if the object is in its default 'start of paragraph' state. */ sl@0: { sl@0: return iStackLevel == 0 && sl@0: iStack[0].iEmbeddingLevel == 0 && sl@0: iStack[0].iOverrideState == ENoOverrideState && sl@0: iStack[0].iStartCategory == EOtherNeutral && sl@0: iPreviousCategory == ELeftToRight && sl@0: iPreviousStrongCategory == ELeftToRight; sl@0: } sl@0: sl@0: sl@0: EXPORT_C TBool TBidirectionalState::operator==(const TBidirectionalState& aState) const sl@0: /** Return ETrue if two bidirectional states are identical. sl@0: sl@0: @param aState A bidirectional state. sl@0: @return ETrue if two bidirectional states are identical. */ sl@0: { sl@0: if (iPreviousCategory != aState.iPreviousCategory || sl@0: iPreviousStrongCategory != aState.iPreviousStrongCategory || sl@0: iStackLevel != aState.iStackLevel) sl@0: return FALSE; sl@0: const TStackItem* p = iStack; sl@0: const TStackItem* q = aState.iStack; sl@0: for (int i = 0; i <= iStackLevel; i++, p++, q++) sl@0: { sl@0: if (p->iStartCategory != q->iStartCategory || sl@0: p->iOverrideState != q->iOverrideState || sl@0: p->iEmbeddingLevel != q->iEmbeddingLevel) sl@0: return FALSE; sl@0: } sl@0: return TRUE; sl@0: } sl@0: sl@0: sl@0: TInt TBidirectionalState::CatToNumber(TInt aCat) sl@0: /** sl@0: Finds the highest bit set in the input. Used to convert sl@0: TBidirectionalState::TCategory into TChar::TBdCategory. sl@0: @param aCat a TBidirectionalState::TCategory. sl@0: @return The equivalent TChar::TBdCategory. sl@0: @internalComponent sl@0: */ { sl@0: TInt shifts = 0; sl@0: TInt bits = 32; sl@0: TInt mask = ~0L; sl@0: while (bits != 0) sl@0: { sl@0: bits >>= 1; sl@0: mask <<= bits; sl@0: if ((aCat & mask) == 0) sl@0: { sl@0: aCat <<= bits; sl@0: shifts += bits; sl@0: } sl@0: } sl@0: return 31 - shifts; sl@0: } sl@0: sl@0: sl@0: EXPORT_C void TBidirectionalState::ExternalizeL(RWriteStream& aDest) sl@0: /** Serializes a bidirectional state to an output stream. sl@0: sl@0: @param aDest An output stream. */ sl@0: { sl@0: //+ put the prev cat, prev strong cat and stack levels in one number? sl@0: // Write the previous category and previous strong category. sl@0: aDest.WriteInt8L(CatToNumber(iPreviousCategory)); sl@0: aDest.WriteInt8L(CatToNumber(iPreviousStrongCategory)); sl@0: sl@0: // Write the number of stack levels sl@0: aDest.WriteInt8L(iStackLevel); sl@0: sl@0: /* sl@0: Write each stack level as a single number: 5 bits for the start category, 2 for the override state, sl@0: 6 for the embedding level. sl@0: */ sl@0: for (int i = 0; i <= iStackLevel; i++) sl@0: { sl@0: TInt x = CatToNumber(iStack[i].iStartCategory); sl@0: if (iStack[i].iOverrideState == ELeftToRightOverrideState) sl@0: { sl@0: x |= (KBidirectionalStateOverrideStreamValueLeftToRight << 5); sl@0: } sl@0: else if (iStack[i].iOverrideState == ERightToLeftOverrideState) sl@0: { sl@0: x |= (KBidirectionalStateOverrideStreamValueRightToLeft << 5); sl@0: } sl@0: x |= ((TInt)iStack[i].iEmbeddingLevel << 7); sl@0: aDest.WriteInt16L(x); sl@0: } sl@0: sl@0: TInt level = State().iEmbeddingLevel; sl@0: if (60 <= level) sl@0: { sl@0: aDest.WriteInt8L(iPushesBeyond60); sl@0: aDest.WriteInt8L(iPushesBeyond61); sl@0: } sl@0: } sl@0: sl@0: sl@0: EXPORT_C void TBidirectionalState::InternalizeL(RReadStream& aSource) sl@0: /** Reads a bidirectional state from an input stream, translating it from its serialized sl@0: form. sl@0: sl@0: @param aSource A source stream. */ sl@0: { sl@0: // Read the previous category and the previous strong category. sl@0: TInt x = aSource.ReadInt8L(); sl@0: iPreviousCategory = (TCategory)(1 << x); sl@0: x = aSource.ReadInt8L(); sl@0: iPreviousStrongCategory = (TCategory)(1 << x); sl@0: sl@0: // Read the number of stack levels. sl@0: iStackLevel = aSource.ReadInt8L(); sl@0: sl@0: // Read the stack levels. sl@0: for (int i = 0; i <= iStackLevel; i++) sl@0: { sl@0: x = aSource.ReadInt16L(); sl@0: iStack[i].iStartCategory = (TCategory)(1 << (x & 0x1F)); sl@0: switch ((x >> 5) & 3) sl@0: { sl@0: case KBidirectionalStateOverrideStreamValueLeftToRight: sl@0: iStack[i].iOverrideState = ELeftToRightOverrideState; sl@0: break; sl@0: case KBidirectionalStateOverrideStreamValueRightToLeft: sl@0: iStack[i].iOverrideState = ERightToLeftOverrideState; sl@0: break; sl@0: case KBidirectionalStateOverrideStreamValueNone: sl@0: default: iStack[i].iOverrideState = ENoOverrideState; break; sl@0: }; sl@0: iStack[i].iEmbeddingLevel = (TUint8)(x >> 7); sl@0: } sl@0: sl@0: TInt level = State().iEmbeddingLevel; sl@0: if (60 <= level) sl@0: { sl@0: iPushesBeyond60 = aSource.ReadInt8L(); sl@0: iPushesBeyond61 = aSource.ReadInt8L(); sl@0: } sl@0: else sl@0: { sl@0: iPushesBeyond60 = 0; sl@0: iPushesBeyond61 = 0; sl@0: } sl@0: } sl@0: sl@0: sl@0: TBidirectionalState::TBidirectionalState(TChar::TBdCategory aPrevCat, sl@0: TChar::TBdCategory aPrevStrongCat, sl@0: TBool aParRightToLeft) sl@0: /** sl@0: Constructor suitable for test code. sl@0: @internalComponent sl@0: */ sl@0: { sl@0: Reset(); sl@0: iPreviousCategory = CharToBdCat(aPrevCat); sl@0: iPreviousStrongCategory = CharToBdCat(aPrevStrongCat); sl@0: iStack[0].iEmbeddingLevel = (TUint8) (aParRightToLeft? 1 : 0); sl@0: }