/*
 *		Uniscribe interface (& related classes) class implementation
 *		
 *		File:    uspi.cpp
 * 		Create:  Jan 10, 1998
 *		Author:  Worachai Chaoweeraprasit (wchao)
 *
 *		Copyright (c) 1998-2000, Microsoft Corporation. All rights reserved.
 */

#include "_common.h"

#ifndef NOCOMPLEXSCRIPTS

#include "_font.h"
#include "_edit.h"
#include "_frunptr.h"
#include "_select.h"
#include "_measure.h"
#include "_uspi.h"

CUniscribe* 	g_pusp = NULL;
int				g_cMaxScript = 0x100;


// initial dummy script properties (= SCRIPT_UNDEFINED)
static const SCRIPT_PROPERTIES 	g_propUndef 	= { LANG_NEUTRAL, FALSE, FALSE, FALSE, FALSE, 0 };
static const SCRIPT_PROPERTIES*	g_pPropUndef[1]	= { &g_propUndef };

CUniscribe::CUniscribe ()
{
	// Initialize digit substitution info
	ApplyDigitSubstitution(W32->GetDigitSubstitutionMode());

	// Get maximum number of scripts supported
	ScriptGetProperties(NULL, &g_cMaxScript);
}

// Test the OS if it does any complex script.
// REVIEW (keithcu) What if it only supports indic, but not the other ones?
BOOL IsSupportedOS()
{
	BOOL	fSupport = !OnWin95FE();
	int		rguCodePage[] = {1255, 1256, 874};
	BYTE	rgbch[] = {0xe0, 0xd3, 0xa1};
	WCHAR	rgwch[] = {0x05d0, 0x0633, 0x0e01};
	WCHAR	wch;
	int	   	i = 0;

	if (fSupport)
	{
		for (;i < 3; i++)
		{
			if (MBTWC(rguCodePage[i], 0, (LPCSTR)&rgbch[i], 1, (LPWSTR)&wch, 1, NULL) > 0 &&
				wch == rgwch[i])
				break;			// support either Arabic, Hebrew or Thai
		}
	}
	return fSupport && i < 3;
}

// Prepare information for digit substitution
// return: Native digit script (shapine engine) ID.
//
WORD CUniscribe::ApplyDigitSubstitution(BYTE bDigitSubstMode)
{
	_wesNationalDigit = 0;

	// Remember national digits script ID if substitution mode is not None
	if (bDigitSubstMode != DIGITS_NOTIMPL && bDigitSubstMode != DIGITS_NONE)
	{
		WCHAR			chZero = 0x0030;
		int				cItems;
		SCRIPT_ITEM		si[2];
		SCRIPT_CONTROL	sc = {0};
		SCRIPT_STATE	ss = {0};

		// force national digit mode
		sc.uDefaultLanguage   = GetNationalDigitLanguage(GetThreadLocale());
		ss.fDigitSubstitute   = TRUE;
		sc.fContextDigits     = FALSE;

		if (SUCCEEDED(ScriptItemize(&chZero, 1, 2, &sc, &ss, (SCRIPT_ITEM*)&si, (int*)&cItems)))
			_wesNationalDigit = si[0].a.eScript;
	}
	return _wesNationalDigit;
}


// Some locales may have its own traditional (native) digit and national standard digit
// recognised by a standard body and adopted by NLSAPI. The example is that Nepali(India)
// has its own digit but the India standard uses Hindi digit as the national digit.
//
DWORD CUniscribe::GetNationalDigitLanguage(LCID lcid)
{
	DWORD	dwDigitLang = PRIMARYLANGID(LANGIDFROMLCID(lcid));

	if (W32->OnWinNT5())
	{
		WCHAR	rgwstrDigit[20];

		if (GetLocaleInfoW(lcid, LOCALE_SNATIVEDIGITS, rgwstrDigit, ARRAY_SIZE(rgwstrDigit)))
		{
			// Steal this from Uniscribe (build 0231)

			switch (rgwstrDigit[1])
			{
				case 0x0661: dwDigitLang = LANG_ARABIC;    break;
				case 0x06F1: dwDigitLang = LANG_FARSI;     break;
				case 0x0e51: dwDigitLang = LANG_THAI;      break;
				case 0x0967: dwDigitLang = LANG_HINDI;     break;
				case 0x09e7: dwDigitLang = LANG_BENGALI;   break;
				case 0x0a67: dwDigitLang = LANG_PUNJABI;   break;
				case 0x0ae7: dwDigitLang = LANG_GUJARATI;  break;
				case 0x0b67: dwDigitLang = LANG_ORIYA;     break;
				case 0x0be7: dwDigitLang = LANG_TAMIL;     break;
				case 0x0c67: dwDigitLang = LANG_TELUGU;    break;
				case 0x0ce7: dwDigitLang = LANG_KANNADA;   break;
				case 0x0d67: dwDigitLang = LANG_MALAYALAM; break;
				case 0x0f21: dwDigitLang = LANG_TIBETAN;   break;
				case 0x0ed1: dwDigitLang = LANG_LAO;       break;
			}
		}
	}

	return dwDigitLang;
}


CUniscribe::~CUniscribe ()
{
	if (_pFSM)
	{
		delete _pFSM;
	}
}


/***** High level services *****/


// Tokenize string and run Unicode Bidi algorithm if requested.
// return : =<0 - error
//			>0  - number of complex script tokens
//
int CUniscribe::ItemizeString (
	USP_CLIENT* pc,					// in: Working structure
	WORD		uInitLevel,			// in: Initial Bidi level
	int*        pcItems,			// out: Count of items generated
	WCHAR*		pwchString,			// in: Input string
	int			cch,				// in: Number of character to itemize
	BOOL        fUnicodeBiDi,		// in: TRUE - Use UnicodeBidi
	WORD		wLangId)			// in: (optional) Dominant language preference
{
	Assert (pc && pc->si && pcItems && pwchString && cch > 0 && cch <= pc->si->cchString);

	USP_CLIENT_SI*  pc_si = pc->si;
	SCRIPT_ITEM*    psi = pc_si->psi;
	SCRIPT_CONTROL	sc = {0};
	SCRIPT_STATE	ss = {0};
	SCRIPT_CONTROL*	psc;
	SCRIPT_STATE*	pss;
	HRESULT         hr;
	int             cItems = 0;

	if (fUnicodeBiDi)
	{
		psc = &sc;
		pss = &ss;

		if (wLangId == LANG_NEUTRAL)
			wLangId = PRIMARYLANGID(LANGIDFROMLCID(GetThreadLocale()));

		// (preitemize:) set up initial state
		psc->uDefaultLanguage = wLangId;
		// Classify + - and / in Win9x legacy manner
		psc->fLegacyBidiClass = TRUE;

		// For Arabic Office's compatibility.
		// We enable fArabicNumContext if the dominant language is Arabic.
		//
		if (psc->uDefaultLanguage == LANG_ARABIC)
			pss->fArabicNumContext = uInitLevel & 1;

		pss->uBidiLevel         = uInitLevel;
		// Leave digit substitution to None since we do it ourself.
		// pss->fDigitSubstitute   = FALSE;
		// psc->fContextDigits     = FALSE;
	}
	else
	{
		psc = NULL;
		pss = NULL;
	}

	// begin real work
	hr = ScriptItemize(pwchString, cch, cch+1, psc, pss, psi, (int*)&cItems);

	return SUCCEEDED(hr) ? *pcItems = cItems : 0;
}


// Produce a shaped string (glyph array), taking care of font association and measurer's CF update
//
// Success can require 3 calls to Shape():
// 1. Returns E_PENDING (script cache doesn't contain the glyphing information)
// 2. Return USP_E_SCRIPT_NOT_IN_FONT --the HFONT doesn't contain the script needed to do the glyphing
// 3. Hopefully success, but may return again if the fallback font doesn't exist, but we quit anyway.
int CUniscribe::ShapeString (
	PLSRUN					plsrun,		// in: The first run to be shaped
	SCRIPT_ANALYSIS*        psa, 		// in: Analysis of the run to be shaped
	CMeasurer*              pme, 		// in: Measurer points to start cp of the run
	const WCHAR*            pwch, 		// in: String to be shaped
	int                     cch,		// in: Count of chars
	WORD*&                  pwgi, 		// out: Reference to glyph indices array
	WORD*                   pwlc, 		// out: Logical cluster array
	SCRIPT_VISATTR*&        psva)		// out: Reference to glyph's attribute array
{
	AssertSz (plsrun && psa && pme && pwch, "ShapeString failed: Invalid params");

	HRESULT     hr = S_OK;
	HRESULT		hrLastError = S_OK;
	HDC         hdc = NULL;
	HFONT		hOrgFont = NULL;
	int         cGlyphs;
	int			cchAdd = 0;
	CCcs	    *pccsSave = pme->Check_pccs();
	int			nAttempt = 8;	// Maximum attempt to realloc glyph buffer to shape a string

	// make sure that we have proper font cache ready to use
	if (!pme->_pccs)
		return 0;
	
	if (psa->fNoGlyphIndex)
		// If no glyph processing, hdc must be around.
        hdc = PrepareShapeDC(plsrun, pme, E_PENDING, hOrgFont);

	// prepare glyph buffer
	if (!CacheAllocGlyphBuffers(cch, cGlyphs, pwgi, psva))
		return 0;

	do
	{
		hr = ScriptShape(hdc, &pme->_pccs->_sc, pwch, cch, cGlyphs, psa, pwgi, pwlc, psva, &cGlyphs);

		if (SUCCEEDED(hr))
			break;

		// Error handling...

		switch (hr)
		{
			case E_PENDING:
			case USP_E_SCRIPT_NOT_IN_FONT:

				if (hr == hrLastError)
					nAttempt = 0;		// We encounter the same error twice.
				else
				{
					hdc = PrepareShapeDC(plsrun, pme, hr, hOrgFont);
					hrLastError = hr;
				}
				break;

			case E_OUTOFMEMORY:

				// (#6773)Indic shaping engine could produce glyphs more than we could hold.
				//
				cchAdd += 16;
				if (CacheAllocGlyphBuffers(cch + cchAdd, cGlyphs, pwgi, psva))
				{
					nAttempt--;
					break;
				}
				
			default:
				nAttempt = 0;
				//AssertSz(FALSE, "Shaping fails with invalid error or we run out of memory.");
				break;
		}

	} while (nAttempt > 0);


	// restore hdc's original font
	if (hdc && hOrgFont)
		SelectObject(hdc, hOrgFont);

	if (pme->_pccs != pccsSave)
		plsrun->SetFallback(SUCCEEDED(hr));

	return SUCCEEDED(hr) ? cGlyphs : 0;
}

// Place a string and take care of font association and measurer's CF update
//
// This is called right after ShapeString.
int CUniscribe::PlaceString(
	PLSRUN					plsrun,		// in: The first run to be shaped
	SCRIPT_ANALYSIS*        psa, 		// in: Analysis of the run to be shaped
	CMeasurer*              pme,        // in: Measurer points to start cp of the run
	const WORD*             pcwgi,      // in: Glyph indices array
	int                     cgi,		// in: Count of input glyphs
	const SCRIPT_VISATTR*   psva, 		// in: Glyph's attribute array
	int*                    pgdx,		// out: Glyph's advanced width array
	GOFFSET*                pgduv,		// out: Glyph's offset array
	ABC*                    pABC)       // out: Run's dimension
{
	AssertSz (plsrun && psa && pme && pcwgi, "PlaceString failed: Invalid params");

	HRESULT     hr = S_OK;
	HRESULT		hrLastError = S_OK;
	HDC         hdc = NULL;
	HFONT		hOrgFont = NULL;
	int			nAttempt = 1;

	pme->Check_pccs();
	pme->ApplyFontCache(plsrun->IsFallback(), plsrun->_a.eScript);

	// make sure that we have proper font cache ready to use
	if (!pme->_pccs)
		return 0;

	if (psa->fNoGlyphIndex)
		// If no glyph processing, hdc must be around.
        hdc = PrepareShapeDC(plsrun, pme, E_PENDING, hOrgFont);

	do
	{
		hr = ScriptPlace(hdc, &pme->_pccs->_sc, pcwgi, cgi, psva, psa, pgdx, pgduv, pABC);

		if (SUCCEEDED(hr))
			break;

		// Error handling...

		switch (hr)
		{
			case E_PENDING:

				if (hr == hrLastError)
					nAttempt = 0;		// We encounter the same error twice.
				else
				{
					hdc = PrepareShapeDC(plsrun, pme, hr, hOrgFont);
					hrLastError = hr;
				}
				break;

			default:
				nAttempt = 0;
				//AssertSz(FALSE, "Placing fails with invalid error.");
				break;
		}

	} while (nAttempt > 0);


	// restore hdc's original font
	if (hdc && hOrgFont)
		SelectObject(hdc, hOrgFont);

	return SUCCEEDED(hr) ? cgi : 0;
}

// Placing given string results in logical width array,
// the result array would be used to record WMF metafile.
//
int CUniscribe::PlaceMetafileString (
	PLSRUN					plsrun,		// in: The first run to be shaped
	CMeasurer*              pme,        // in: Measurer points to start cp of the run
	const WCHAR*			pwch,		// in: Input codepoint string
	int						cch,		// in: Character count
	PINT*					ppiDx)		// out: Pointer to logical widths array
{
	AssertSz (pme && pwch && ppiDx, "PlaceMetafileString failed: Invalid params");

	if (W32->OnWinNT4() || W32->OnWin9xThai())
	{
		// MET NT40 has bug in lpdx justification so i doesnt playback the lpdx very nicely.
		// Thai Win9x simply cannot handle fancy lpdx values generated by Uniscribe.
		// We workaround both cases here by metafiling no lpdx and let the system reconstructs
		// it from scratch during playback time.

		// =FUTURE= If we do line justification. We need more sophisticated work here
		// basically to reconstruct the OS preferred type of lpdx.
		//
		*ppiDx = NULL;
		return cch;
	}

	HRESULT     	hr = E_FAIL;
	PUSP_CLIENT		pc = NULL;
	int*			piLogDx;		// logical width array
	int*			piVisDx;		// visual width array
	GOFFSET*		pGoffset;		// glyph offset array
	WORD*			pwgi;			// glyph array
	SCRIPT_VISATTR*	psva;			// glyph properties array
	int				cgi = 0;
	BYTE			pbBufIn[MAX_CLIENT_BUF];
	SCRIPT_ANALYSIS	sa = plsrun->_a;
	BOOL			fVisualGlyphDx = sa.fRTL && W32->OnWin9x() && W32->OnBiDiOS();


	CreateClientStruc(pbBufIn, MAX_CLIENT_BUF, &pc, cch, cli_pcluster);
	if (!pc)
		return 0;

	PUSP_CLIENT_SSP	pcssp = pc->ssp;
		
	if (fVisualGlyphDx)
		sa.fLogicalOrder = FALSE;	// shaping result in visual order

	// Shape string
	if (cgi = ShapeString(plsrun, &sa, pme, pwch, (int)cch, pwgi, pcssp->pcluster, psva))
	{
		// Get static buffer for logical and visual width arrays
		//
		if ( (piLogDx = GetWidthBuffer(cgi + cch)) &&
			 (pGoffset = GetGoffsetBuffer(cgi)) )
		{
			piVisDx		= &piLogDx[cch];
	
			// then place it...
			if (cgi == PlaceString(plsrun, &sa, pme, pwgi, cgi, psva, piVisDx, pGoffset, NULL))
			{
				if (fVisualGlyphDx)
				{
					// Workaround BiDi Win9x's lpdx handling
					// It assumes ExtTextOut's dx array is glyph width in visual order
	
					Assert (cgi <= cch); // glyph count never exceeds character count in BiDi
					CopyMemory (piLogDx, piVisDx, min(cgi, cch)*sizeof(int));
				}
				else
				{
					// Map visual glyph widths to logical widths
					hr = ScriptGetLogicalWidths(&sa, cch, cgi, piVisDx, pcssp->pcluster, psva, piLogDx);
				}
			}

			// result
			*ppiDx = piLogDx;
		}
	}

	if (pc && pbBufIn != (BYTE*)pc)
		FreePv(pc);

	return SUCCEEDED(hr) ? cgi : 0;
}



/***** Helper functions *****/


// Retrieve the BidiLevel FSM
const CBiDiFSM* CUniscribe::GetFSM ()
{
	if (!_pFSM)
	{
		_pFSM = new CBiDiFSM(this);
		if (_pFSM && !_pFSM->Init())
		{
			delete _pFSM;
		}
	}
	return _pFSM;
}


// Prepare the shapeable font ready to dc for a given script
//
// USP_E_SCRIPT_NOT_IN_FONT - complex scripts font association
// E_PENDING 				- prepare dc with current font selected
//
HDC CUniscribe::PrepareShapeDC (
	PLSRUN			plsrun,		// in: The first run to be shaped
    CMeasurer*		pme,        // in: Measurer points to start cp of the run
    HRESULT			hrReq,      // in: Error code to react
	HFONT&			hOrgFont)	// in/out: Original font of the shape DC
{
    Assert (pme);
	
	HDC		hdc = NULL;
	HFONT	hOldFont;

	switch (hrReq)
	{
		case USP_E_SCRIPT_NOT_IN_FONT:
			{
				pme->ApplyFontCache(fTrue, plsrun->_a.eScript);
#ifdef DEBUG
				if (pme->_pccs)
					Tracef(TRCSEVWARN, "USP_E_SCRIPT_NOT_IN_FONT: charset %d applied", pme->_pccs->_bCharSet);
#endif
			}
			
		default:
			if (pme->_pccs)
			{
				hdc = pme->_pccs->_hdc;
				hOldFont = (HFONT)SelectObject(hdc, pme->_pccs->_hfont);

				if (!hOrgFont)
					hOrgFont = hOldFont;
			}
	}

    return hdc;
}

const SCRIPT_PROPERTIES* CUniscribe::GeteProp (WORD eScript)
{
	if (!_ppProp)
	{
		if (!SUCCEEDED(ScriptGetProperties(&_ppProp, NULL)) || !_ppProp)
			_ppProp = g_pPropUndef;
	}
	if (_ppProp == g_pPropUndef || eScript >= (WORD)g_cMaxScript)
		eScript = 0;

	return _ppProp[eScript];
}

// Figure proper charset to use for complex script.
// The resulted charset can be either actual or virtual (internal) GDI charset used by given script
BOOL CUniscribe::GetComplexCharRep(
	const SCRIPT_PROPERTIES* 	psp,				// Uniscribe script's properties 			
	BYTE						iCharRepDefault,	// -1 format's charset
	BYTE&						iCharRepOut)		// out: Charset to use
{
	Assert(psp);

	BYTE	iCharRep = !psp->fCDM
					 ? CharRepFromCharSet(psp->bCharSet)
					 : GetCDMCharRep(iCharRepDefault);
	BOOL 	fr = psp->fComplex && !psp->fControl;

	if (fr)
	{
		if (iCharRep == ANSI_INDEX || iCharRep == DEFAULT_INDEX)
			iCharRep = CharRepFromLID(psp->langid);

		if (IsBiDiCharRep(iCharRep))
			_iCharRepRtl = iCharRep;	// Cache the last found BiDi charset

		iCharRepOut = iCharRep;
	}
	return fr;
}

// Figure out the charset to use for CDM run
//
BYTE CUniscribe::GetCDMCharRep(
	BYTE iCharRepDefault)
{
	if (!_iCharRepCDM)
	{
		_iCharRepCDM = (iCharRepDefault == VIET_INDEX ||
						W32->GetPreferredKbd(VIET_INDEX) ||
						GetLocaleCharRep() == VIET_INDEX || GetACP() == 1258)
					 ? VIET_INDEX : DEFAULT_INDEX;
	}
	return _iCharRepCDM;
}

BYTE CUniscribe::GetRtlCharRep(
    CTxtEdit*       ped,
	CRchTxtPtr*		prtp)		// ptr to the numeric run
{
	CFormatRunPtr	rp(prtp->_rpCF);

	rp.AdjustBackward();

	BYTE	iCharRep = ped->GetCharFormat(rp.GetFormat())->_iCharRep;

	if (!IsBiDiCharRep(iCharRep))
	{
		iCharRep = _iCharRepRtl;	// Use the last found BiDi charset

		if (!IsBiDiCharRep(iCharRep))
		{
			// try default charset
			DWORD	dwCharFlags;
	
			iCharRep = ped->GetCharFormat(-1)->_iCharRep;
	
			if (!IsBiDiCharRep(iCharRep))
			{
				// Then the system charset
				iCharRep = CharRepFromCodePage(GetACP());
				if (!IsBiDiCharRep(iCharRep))
				{
					// Then the content
					dwCharFlags = ped->GetCharFlags() & (FARABIC | FHEBREW);
	
					if (dwCharFlags == FARABIC)
						iCharRep = ARABIC_INDEX;

					else if(dwCharFlags == FHEBREW)
						iCharRep = HEBREW_INDEX;

					else
					{
						// And last chance with the first found loaded BiDi kbd
						if (W32->GetPreferredKbd(HEBREW_INDEX))
							iCharRep = HEBREW_INDEX;
						else
							// Even if we can't find Arabic, we have to assume it here.
							iCharRep = ARABIC_INDEX;
					}
				}
			}
		}
	}
	Assert(IsBiDiCharRep(iCharRep));
	return iCharRep;
}


// Substitute digit shaper in plsrun if needed
//
void CUniscribe::SubstituteDigitShaper (
	PLSRUN		plsrun,
	CMeasurer*	pme)
{
	Assert(plsrun && pme);

	CTxtEdit*	ped = pme->GetPed();
	WORD		wScript;

	if (GeteProp(plsrun->_a.eScript)->fNumeric)
	{
		wScript = plsrun->_pCF->_wScript;		// reset it before

		switch (W32->GetDigitSubstitutionMode())
		{
		case DIGITS_CTX:
			{
				if (ped->IsRich())
				{
					// Context mode simply means the charset of the kbd for richtext.
					if (!IsBiDiCharRep(ped->GetCharFormat(pme->_rpCF.GetFormat())->_iCharRep))
						break;
				}
				else
				{
					// Digit follows directionality of preceding run for plain text
					CFormatRunPtr	rp(pme->_rpCF);
					Assert(rp.IsValid());

					if (rp.PrevRun())
					{
						if (!IsBiDiCharRep(ped->GetCharFormat(rp.GetFormat())->_iCharRep))
							break;
					}
					else
					{
						// No preceding run, looking for the paragraph direction
						if (!pme->Get_pPF()->IsRtl())
							break;
					}
				}
				// otherwise, fall thru...
			}
		case DIGITS_NATIONAL:
			wScript = _wesNationalDigit;
		default:
			break;
		}

		// Update all linked runs
		while (plsrun)
		{
			plsrun->_a.eScript = wScript;		// assign proper shaping engine to digits
			plsrun = plsrun->_pNext;
		}
	}
}
	

/***** Uniscribe entry point *****/


// memory allocator
//
BOOL CUniscribe::CreateClientStruc (
	BYTE*           pbBufIn,
	LONG            cbBufIn,
	PUSP_CLIENT*    ppc,
	LONG            cchString,
	DWORD           dwMask)
{
	Assert(ppc && pbBufIn);

	if (!ppc)
		return FALSE;

	*ppc = NULL;

	if (cchString == 0)
		cchString = 1;		// simplify caller's logic

	LONG        i;
	LONG        cbSize;
	PBYTE       pbBlock;

	// ScriptItemize's
	//
	PVOID       pvString;
	PVOID       pvsi;

	// ScriptBreak's
	//
	PVOID       pvsla;

	// ScriptShape & Place's
	//
	PVOID       pvwgi;
	PVOID		pvsva;
	PVOID		pvcluster;
	PVOID		pvidx;
	PVOID		pvgoffset;

	// subtable ptrs
	//
	PUSP_CLIENT_SI      pc_si;
	PUSP_CLIENT_SB      pc_sb;
	PUSP_CLIENT_SSP		pc_ssp;


#define RQ_COUNT    	12

	BUF_REQ     brq[RQ_COUNT] =
	{
		// table and subtable blocks
		//
		{ sizeof(USP_CLIENT),                                                             1, (void**)ppc},
		{ sizeof(USP_CLIENT_SI),    dwMask & cli_Itemize    ? 1                         : 0, (void**)&pc_si},
		{ sizeof(USP_CLIENT_SB),    dwMask & cli_Break      ? 1                         : 0, (void**)&pc_sb},
		{ sizeof(USP_CLIENT_SSP),   dwMask & cli_ShapePlace ? 1                         : 0, (void**)&pc_ssp},

		// data blocks
		//
		{ sizeof(WCHAR),            dwMask & cli_string     ? cchString + 1             : 0, &pvString},
		{ sizeof(SCRIPT_ITEM),      dwMask & cli_psi        ? cchString + 1             : 0, &pvsi},
		{ sizeof(SCRIPT_LOGATTR),   dwMask & cli_psla       ? cchString + 1             : 0, &pvsla},
		{ sizeof(WORD),				dwMask & cli_pwgi       ? GLYPH_COUNT(cchString+1)	: 0, &pvwgi},
		{ sizeof(SCRIPT_VISATTR),   dwMask & cli_psva       ? GLYPH_COUNT(cchString+1)	: 0, &pvsva},
		{ sizeof(WORD),				dwMask & cli_pcluster   ? cchString + 1				: 0, &pvcluster},
		{ sizeof(int),				dwMask & cli_pidx       ? GLYPH_COUNT(cchString+1)	: 0, &pvidx},
		{ sizeof(GOFFSET),			dwMask & cli_pgoffset   ? GLYPH_COUNT(cchString+1)	: 0, &pvgoffset},
	};

	// count total buffer size in byte (WORD aligned)
	//
	for (i=0, cbSize=0; i < RQ_COUNT; i++)
	{
		cbSize += ALIGN(brq[i].size * brq[i].c);
	}

	// allocate the whole buffer at once
	//
	if (cbSize > cbBufIn)
	{
		pbBlock = (PBYTE)PvAlloc(cbSize, 0);
	}
	else
	{
		pbBlock = pbBufIn;
	}

	if (!pbBlock)
	{
		//
		// memory management failed!
		//
		TRACEERRORSZ("Allocation failed in CreateClientStruc!\n");
		*ppc = NULL;
		return FALSE;
	}

	
	// clear the main table
	ZeroMemory (pbBlock, sizeof(USP_CLIENT));


	// assign ptrs in buffer request structure
	//
	for (i=0; i < RQ_COUNT; i++)
	{
		if (brq[i].c > 0)
		{
			*brq[i].ppv = pbBlock;
			pbBlock += ALIGN(brq[i].size * brq[i].c);
		}
		else
		{
			*brq[i].ppv = NULL;
		}
	}

	Assert(((PBYTE)(*ppc)+cbSize == pbBlock));

	// fill in data block ptrs in subtable
	//
	if (pc_si)
	{
		pc_si->pwchString   = (WCHAR*)          pvString;
		pc_si->cchString    =                   cchString;
		pc_si->psi          = (SCRIPT_ITEM*)    pvsi;
	}

	if (pc_sb)
	{
		pc_sb->psla         = (SCRIPT_LOGATTR*) pvsla;
	}

	if (pc_ssp)
	{
		pc_ssp->pwgi		= (WORD*) 			pvwgi;
		pc_ssp->psva		= (SCRIPT_VISATTR*)	pvsva;
		pc_ssp->pcluster	= (WORD*) 			pvcluster;
		pc_ssp->pidx		= (int*)			pvidx;
		pc_ssp->pgoffset	= (GOFFSET*)		pvgoffset;
	}

	// fill in subtable ptrs in header table
	//
	(*ppc)->si              = (PUSP_CLIENT_SI)  pc_si;
	(*ppc)->sb              = (PUSP_CLIENT_SB)  pc_sb;
	(*ppc)->ssp             = (PUSP_CLIENT_SSP) pc_ssp;

	return TRUE;
}


///////	CBidiFSM class implementation
//
//		Create: Worachai Chaoweeraprasit(wchao), Jan 29, 1998
//
CBiDiFSM::~CBiDiFSM ()
{
	FreePv(_pStart);
}

INPUT_CLASS CBiDiFSM::InputClass (
	const CCharFormat*	pCF,
	CTxtPtr*			ptp,
	LONG				cchRun) const
{
	if (!_pusp->IsValid() || !pCF || pCF->_wScript == SCRIPT_WHITE)
		return chGround;

	const SCRIPT_PROPERTIES* psp = _pusp->GeteProp(pCF->_wScript);
	BYTE 	iCharRep = pCF->_iCharRep;

	if (psp->fControl)
	{
		if (cchRun == 1)
			switch (ptp->GetChar())				// single-char run
			{
				case LTRMARK: return chLTR;		// \ltrmark
				case RTLMARK: return chRTL;		// \rtlmark
			}
		return chGround;
	}

	if(IsSymbolOrOEMCharRep(iCharRep) || IsFECharRep(iCharRep) || pCF->_dwEffects & CFE_RUNISDBCS)
		return chLTR;

	BOOL fBiDiCharSet = IsBiDiCharSet(psp->bCharSet);
	if (psp->fNumeric)
		// Numeric digits
		return (fBiDiCharSet || IsBiDiCharRep(iCharRep)) ? digitRTL : digitLTR;

	// RTL if it's RTL script or its format charset is RTL and NOT a simplified script
	return (fBiDiCharSet || pCF->_wScript && IsBiDiCharRep(iCharRep)) ? chRTL : chLTR;
}

// The FSM generates run's embedding level based on given base level and puts it
// in CFormatRun. LsFetchRun is the client using this result.
//
#ifdef DEBUG
//#define DEBUG_LEVEL
#endif

#ifdef DEBUG_LEVEL
void DebugLevel (CBiDiFSMCell* pCell)
{
	Tracef(TRCSEVNONE, "%d,", pCell->_level._value);
}
#else
#define DebugLevel(x)
#endif

HRESULT CBiDiFSM::RunFSM (
	CRchTxtPtr*			prtp,				// in: text pointer to start run
	LONG				cRuns,				// in: number of FSM run
	LONG				cRunsStart,			// in: number of start run
	BYTE				bBaseLevel) const	// in: base level
{
	Assert (prtp->_rpCF.IsValid() && cRuns > 0);

	CRchTxtPtr				rtp(*prtp);
	const CCharFormat*      pCF;
	LONG					cchRun;
	LONG					cRunsAll = cRuns + cRunsStart;
	CBiDiFSMCell*           pCell;
	USHORT                  ucState = bBaseLevel ? S_X * NUM_FSM_INPUTS : 0;
	BOOL					fNext = TRUE;


	// loop thru FSM
	for (; fNext && cRunsAll > 0; cRunsAll--, fNext = !!rtp.Move(cchRun))
	{
		cchRun = rtp.GetCchLeftRunCF();
	
		pCF = rtp.GetPed()->GetCharFormat(rtp._rpCF.GetFormat());
	
		ucState += InputClass(pCF, &rtp._rpTX, cchRun);

		pCell = &_pStart[ucState];

		// set level to FSM runs
		if (cRunsAll <= cRuns)
			rtp._rpCF.SetLevel (pCell->_level);

		DebugLevel(pCell);

		ucState = pCell->_uNext;	// next state
	}

	return S_OK;
}

// Construct the BiDi embedding level FSM (FSM details see bidifsm2.html)
// :FSM's size = NUM_FSM_INPUTS * NUM_FSM_STATES * sizeof(CBiDiFSMCell) = 6*5*4 = 120 bytes
//
BOOL CBiDiFSM::Init()
{
	CBiDiFSMCell*   pCell;
	int             i;

	// Build the Bidi FSM

	_nState = NUM_FSM_STATES;
	_nInput = NUM_FSM_INPUTS;

	pCell = (CBiDiFSMCell*)PvAlloc(NUM_FSM_STATES * NUM_FSM_INPUTS * sizeof(CBiDiFSMCell), 0);

	if (!pCell)
		return FALSE;	// unable to create FSM!

	_pStart = pCell;


	CBiDiLevel		lvlZero		= {0,0};
	CBiDiLevel		lvlOne  	= {1,0};
	CBiDiLevel		lvlTwo  	= {2,0};
	CBiDiLevel		lvlTwoStart	= {2,1};


	// State A(0): LTR char in LTR para
	//
	for (i=0; i < NUM_FSM_INPUTS; i++, pCell++)
	{
		switch (i)
		{
			case chLTR:
				SetFSMCell(pCell, &lvlZero, 0); break;
			case chRTL:
				SetFSMCell(pCell, &lvlOne, S_B * NUM_FSM_INPUTS); break;
			case digitLTR:
				SetFSMCell(pCell, &lvlZero, 0); break;
			case digitRTL:
				SetFSMCell(pCell, &lvlTwo, S_C * NUM_FSM_INPUTS); break;
			case chGround:
				SetFSMCell(pCell, &lvlZero, 0); break;
		}
	}
	// State B(1): RTL char in LTR para
	//
	for (i=0; i < NUM_FSM_INPUTS; i++, pCell++)
	{
		switch (i)
		{
			case chLTR:
				SetFSMCell(pCell, &lvlZero, 0); break;
			case chRTL:
				SetFSMCell(pCell, &lvlOne, S_B * NUM_FSM_INPUTS); break;
			case digitLTR:
				SetFSMCell(pCell, &lvlZero, 0); break;
			case digitRTL:
				SetFSMCell(pCell, &lvlTwo, S_C * NUM_FSM_INPUTS); break;
			case chGround:
				SetFSMCell(pCell, &lvlZero, 0); break;
		}
	}
	// State C(2): RTL number run in LTR para
	//
	for (i=0; i < NUM_FSM_INPUTS; i++, pCell++)
	{
		switch (i)
		{
			case chLTR:
				SetFSMCell(pCell, &lvlZero, 0); break;
			case chRTL:
				SetFSMCell(pCell, &lvlOne, S_B * NUM_FSM_INPUTS); break;
			case digitLTR:
				SetFSMCell(pCell, &lvlZero, 0); break;
			case digitRTL:
				SetFSMCell(pCell, &lvlTwo, S_C * NUM_FSM_INPUTS); break;
			case chGround:
				SetFSMCell(pCell, &lvlZero, 0); break;
		}
	}
	// State X(1): RTL char in RTL para
	//
	for (i=0; i < NUM_FSM_INPUTS; i++, pCell++)
	{
		switch (i)
		{
			case chLTR:
				SetFSMCell(pCell, &lvlTwo, S_Y * NUM_FSM_INPUTS); break;
			case chRTL:
				SetFSMCell(pCell, &lvlOne, S_X * NUM_FSM_INPUTS); break;
			case digitLTR:
				SetFSMCell(pCell, &lvlTwo, S_Y * NUM_FSM_INPUTS); break;
			case digitRTL:
				SetFSMCell(pCell, &lvlTwo, S_Z * NUM_FSM_INPUTS); break;
			case chGround:
				SetFSMCell(pCell, &lvlOne, S_X * NUM_FSM_INPUTS); break;
		}
	}
	// State Y(2): LTR char in RTL para
	//
	for (i=0; i < NUM_FSM_INPUTS; i++, pCell++)
	{
		switch (i)
		{
			case chLTR:
				SetFSMCell(pCell, &lvlTwo, S_Y * NUM_FSM_INPUTS); break;
			case chRTL:
				SetFSMCell(pCell, &lvlOne, S_X * NUM_FSM_INPUTS); break;
			case digitLTR:
				SetFSMCell(pCell, &lvlTwo, S_Y * NUM_FSM_INPUTS); break;
			case digitRTL:
				SetFSMCell(pCell, &lvlTwoStart, S_Z * NUM_FSM_INPUTS); break;
			case chGround:
				SetFSMCell(pCell, &lvlOne, S_X * NUM_FSM_INPUTS); break;
		}
	}
	// State Z(2): RTL number in RTL para
	//
	for (i=0; i < NUM_FSM_INPUTS; i++, pCell++)
	{
		switch (i)
		{
			case chLTR:
				SetFSMCell(pCell, &lvlTwoStart, S_Y * NUM_FSM_INPUTS); break;
			case chRTL:
				SetFSMCell(pCell, &lvlOne, S_X * NUM_FSM_INPUTS); break;
			case digitLTR:
				SetFSMCell(pCell, &lvlTwoStart, S_Y * NUM_FSM_INPUTS); break;
			case digitRTL:
				SetFSMCell(pCell, &lvlTwo, S_Z * NUM_FSM_INPUTS); break;
			case chGround:
				SetFSMCell(pCell, &lvlOne, S_X * NUM_FSM_INPUTS); break;
		}
	}

	AssertSz(&pCell[-(NUM_FSM_STATES * NUM_FSM_INPUTS)] == _pStart, "Bidi FSM incomplete constructed!");

	return TRUE;
}


///////	CCallbackBufferBase class implementation
//

void* CBufferBase::GetPtr(int cel)
{
	if (_cElem < cel)
	{
		cel += celAdvance;

		_p = PvReAlloc(_p, cel * _cbElem);
		if (!_p)
			return NULL;
		ZeroMemory(_p, cel * _cbElem);
		_cElem = cel;
	}
	return _p;
}

void CBufferBase::Release()
{
	if (_p)
		FreePv(_p);
}

#endif // NOCOMPLEXSCRIPTS