///////////////////////////////////////////////////////////////////////////////
// GetHebrewCharCluster
//
// We read a single character cluster. A cluster is defined by a set of 
// table-based combining rules whereby each character in the Hebrew block
// is given a category of 'none', 'spacing', 'non-spacing', or 'dagesh'.
//
// The return value is the number of chars eaten; clusterSize is the number of 
// chars generated. In the large majority of cases, they will be equal.
//
eastl_size_t Typesetter::GetHebrewCharCluster(eastl_size_t i, eastl_size_t iCharEnd, 
                                              Char* pCharCluster, eastl_size_t& clusterSize)
{
    EA_ASSERT((i < iCharEnd) && (iCharEnd <= mLineLayout.mCharArray.size()));

    clusterSize = 0;

    for(const Char* p = &mLineLayout.mCharArray[i], *pEnd = &mLineLayout.mCharArray[iCharEnd]; 
         (p < pEnd) && (clusterSize < kMaxHebrewCharClusterSize); ++p)
    {
        const Char c = *p;

        // If the character is non-Hebrew, it can't compose with Hebrew.
        // If the character is the first and it is a diacritic, it can't compose with succeeding Hebrew.
        const bool bCharIsHebrew = IsCharHebrew(c);

        if(!bCharIsHebrew || ((clusterSize == 0) && ((1 << gHebrewCharClass[c - 0x0590]) & (kHCCFNonSpacing | kHCCFDagesh))))
        {
            if(clusterSize == 0) // If this is the first char...
            {
                if(bCharIsHebrew)
                    pCharCluster[clusterSize++] = c;
                else
                    return GetGeneralCharCluster(i, iCharEnd, pCharCluster, clusterSize);
            }
            break;
        }
        else
        {
            if(clusterSize == 0)
                pCharCluster[clusterSize++] = c;
  
            EA_ASSERT(IsCharHebrew(*pCharCluster) && IsCharHebrew(c));

            const unsigned prevCharClass = gHebrewCharClass[*pCharCluster - 0x0590];
            const unsigned curCharClass  = gHebrewCharClass[c - 0x0590];

            EA_ASSERT((prevCharClass < 4) && (curCharClass < 4));

            if((clusterSize == 0) || gHebrewClusterTable[prevCharClass][curCharClass]) // If the char is the first char or can pair with the first char...
                pCharCluster[clusterSize++] = c;
            else
            {
                EA_ASSERT(clusterSize > 0);
                break;
            }
        }
    }

    EA_ASSERT(clusterSize > 0);
    return clusterSize; // We always have a 1:1 relationship between chars in the source and chars in the generated cluster.
}
// ShapeArabic
//
void Typesetter::ShapeArabic(eastl_size_t iCharBegin, eastl_size_t iCharEnd)
{
    EA_ASSERT(iCharEnd <= mLineLayout.mCharArray.size());

    const OTF* const pOTF = mLineLayout.mAnalysisInfoArray[0].mpFont->GetOTF();

    // We more or less need to have OpenType font information in order to correctly display Arabic.
    if(pOTF && pOTF->IsScriptSupported("arab"))
    {
        const eastl_size_t iGlyphBegin = mLineLayout.GetGlyphIndexFromCharIndex(iCharBegin);

        for(eastl_size_t i = iCharBegin, charCount = 0; i < iCharEnd; i += charCount)
        {
            const AnalysisInfo* const pAnalysisInfo = &mLineLayout.mAnalysisInfoArray[i];

            Char         pCharCluster[kMaxArabicCharClusterSize];
            eastl_size_t charClusterSize = 0;

            charCount = GetGeneralCharCluster(i, iCharEnd, pCharCluster, charClusterSize);

            for(eastl_size_t c = 0, charsEaten = 0, glyphCount = 0, glyphCountPrev = 0; c < charClusterSize; c += charsEaten)
            {
                GlyphId pGlyphIdArray[kMaxArabicGlyphClusterSize];

                charsEaten = GetGlyphsForChar(pCharCluster + c, charClusterSize - c, pAnalysisInfo, pGlyphIdArray + glyphCount, glyphCount);

                AppendArabicGlyphCluster(iCharBegin, charCount, pCharCluster + c, charsEaten, 
                                        pGlyphIdArray + glyphCountPrev, glyphCount - glyphCountPrev, 
                                        pAnalysisInfo->mnBidiLevel, pOTF);

                glyphCountPrev = glyphCount;
            }
        }

        // Do OpenType substitutions.
        FeatureLookupArray featureLookupArray;

        SetupArabicGsubLookup(featureLookupArray, pOTF);
        AssignArabicCharProperties(&mLineLayout.mCharArray[iCharBegin], iCharEnd - iCharBegin, &mLineLayout.mGlyphInfoArray[iGlyphBegin]);
        DoGlyphSubstitution(mLineLayout, iGlyphBegin, featureLookupArray, pOTF);
        CompleteLineLayoutArrays(iCharBegin, iCharEnd, iGlyphBegin);
        PlaceGeneralGlyphCluster(iCharBegin, iCharEnd - iCharBegin);
    }
    else
    {
        // Generic fallback. Arabic text will look wrong, but at least something will be displayed.
        ShapeGeneral(iCharBegin, iCharEnd);
    }
}
///////////////////////////////////////////////////////////////////////////////
// ShapeGeneral
//
// This function shapes general LTR (left to right) text. It should support
// a number of scripts reasonably well, including all modern Western scripts.
// This function expects text to be entirely LTR and will not work correctly
// if there is any RTL-ordered text in it.
//
// Runs are positioned in their own coordinate system. If multiple runs will
// be strung together on the same line, the runs will have to be offset.
//
// If you have any questions about this function, as Paul Pedriana. Some of the
// details are subtle and may not be immediately obvious.
//
void Typesetter::ShapeGeneral(eastl_size_t iCharBegin, eastl_size_t iCharEnd)
{
    Char pCharCluster[kMaxGeneralCharClusterSize];

    EA_ASSERT(iCharEnd <= mLineLayout.mCharArray.size());

    for(eastl_size_t i = iCharBegin, clusterSize, charCount; i < iCharEnd; i += charCount)
    {
        #ifdef EA_DEBUG
            memset(pCharCluster, 0, sizeof(pCharCluster));
        #endif

        charCount = GetGeneralCharCluster(i, iCharEnd, pCharCluster, clusterSize);
        AppendGeneralCharCluster(i, charCount, pCharCluster, clusterSize);
    }
}