/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: rtrender.cpp,v 1.1.2.5 2004/07/09 13:05:43 pankajgupta Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */


#define XXXMEH_FOR_SMIL2_INTEROP 1 // XXXMEH - so we can support <text src="data:,Write this text" .../>

#define INITGUID
#include "rtrender.ver"
#include <stdio.h>

#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#if defined(_AIX)
#include <strings.h>
inline int strnicmp( const char *s1, const char *s2, int n ) { return strncasecmp(s1,s2,n); }
#endif  // _AIX
#endif // _UNIX

#include "hxtypes.h"
#include "hxassert.h"
#include "hxcom.h"
#if defined(_WINDOWS)
#include <windows.h>
#include "platform\\win\\resource.h"
#elif defined (_MACINTOSH) || defined(_MAC_UNIX)
#include "platform/mac/maclibrary.h" // for MulDiv
#include "platform/mac/cresload.h"
extern FSSpec g_DLLFSpec;
CResourceLoader* CResourceLoader::zm_theResourceLoader = NULL;
#endif

#include "hxwintyp.h"
#include "chxxtype.h"
#include "hxevent.h"
#include "hxcomm.h"
#include "chxpckts.h"
#include "hxfiles.h"
#include "hxcore.h"
#include "hxprefs.h"
#include "hxrendr.h"
#include "hxplugn.h"
#include "hxwin.h"
#include "hxengin.h"
#include "hxupgrd.h"
#include "addupcol.h"

#include "hxurl.h" //for class CHXURL

#include "hxstrutl.h"  //for strncasecmp()

#include "hxtick.h" //for HX_GET_BETTERTICKCOUNT()

#include "hxstack.h"
#include "hxslist.h"
#include "rt_types.h" //for _CHAR, RED_GREEN_OR_BLUE, COLORTYPE

#include "fontdefs.h"
#include "txtattrb.h"
#include "txtcntnr.h"
#include "textline.h"

#include "txtwindw.h" //for class TextWindow.
#include "hxvsurf.h" /* for class HXBitmapInfoHeader */
#include "parsing.h"  //for parsing helper functions.
#include "atocolor.h" //for string-to-COLORTYPE conversion functions.
#include "atotime.h" //for live time comparison: IsTimeAMoreRecentThanTimeB()
#include "rt_string.h" //for stringCompare()
#include "textprsr.h" //for base class TextParser
#include "c_rtrndr.h" //for class RealTextRenderer
#include "rtwin.h"  //for OnPaint(..) function.
#include "hxhyper.h" //for GoToURL(..) function.
#include "hxmon.h"	// for IHXRegistry
#include "hxthread.h"

#include "rtrender.h"

#include "timeval.h"	//for TimeVal, MILLISECOND, ...etc.

#include "hxver.h"


#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static char HX_THIS_FILE[] = __FILE__;
#endif

#if defined(_DEBUG) && defined(RT_OUTPUT_LOGFILE)
#define LOG(FPARGS) \
{ \
    if (m_logfile && (m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)) \
    {\
	fprintf FPARGS; \
	fflush(m_logfile); \
    } \
}
#else
#define LOG(FPARGS)
#endif

#define INITIAL_FRAME_RATE	20 //draws per second
//millisec between time syncs:
#define INITIAL_GRANULARITY	(1000/INITIAL_FRAME_RATE)
#define MAX_ALLOWED_FRAMERATE	30 //maximum "frames" (draws) per second.
#define MAX_SMOOTH_FRAMERATE	25 //maximum fps that is a divisor of 1000.
#define MAX_SMOOTH_FRAMERATE_2	20 //next-highest fps that's divisor of 1000.
#define MIN_ALLOWED_FRAMERATE	10 //minimum "frames" (draws) per second.

//This is the time between time synchs for windows that have no scrollrate
// or crawlrate:
#define NO_MOTION_GRANULARITY	100  //millisec.

#define PLAIN_TEXT_GRANULARITY	200  //millisec

#if !defined(DEFAULT_DURATION_MSEC)
#define DEFAULT_DURATION_MSEC	60000
#endif

#ifdef _WINDOWS
extern HINSTANCE g_hInstance;
#endif

INT32 g_nRefCount_rtre = 0;

const UINT32	STATUS_CALLBACK_INTERVAL = 100;	//100ms interval

/****************************************************************************
 * 
 *  Function:
 * 
 *	HXCreateInstance()
 * 
 *  Purpose:
 * 
 *	Function implemented by all plugin DLL's to create an instance of 
 *	any of the objects supported by the DLL. This method is similar to 
 *	Window's CoCreateInstance() in its purpose, except that it only 
 *	creates objects from this plugin DLL.
 *
 *	NOTE: Aggregation is never used. Therefore and outer unknown is
 *	not passed to this function, and you do not need to code for this
 *	situation.
 * 
 */
STDAPI HXCreateInstance
(
    IUnknown**  /*OUT*/	ppIUnknown
)
{
    *ppIUnknown = (IUnknown*)(IHXPlugin*)new CRealTextRenderer();
    if (*ppIUnknown)
    {
	(*ppIUnknown)->AddRef();
	return HXR_OK;
    }
    return HXR_OUTOFMEMORY;
}

/****************************************************************************
 * 
 *  Function:
 * 
 *	CanUnload()
 * 
 *  Purpose:
 * 
 *	Function implemented by all plugin DLL's if it returns HXR_OK 
 *	then the pluginhandler can unload the DLL
 *
 */
STDAPI CanUnload(void)
{
    return (g_nRefCount_rtre ? HXR_FAIL : HXR_OK);
}

const char* CRealTextRenderer::zm_pName		  = "RealText";
const char* CRealTextRenderer::zm_pDescription    = "Helix RealText Renderer Plugin";
const char* CRealTextRenderer::zm_pCopyright      = HXVER_COPYRIGHT;
const char* CRealTextRenderer::zm_pMoreInfoURL    = HXVER_MOREINFO;
const char* CRealTextRenderer::zm_pStreamMimeTypes[]  = 
	{ "application/vnd.rn-realtext" /* <-- beta2 and beyond */,
	  "application/x-pn-realtext" /* <-- beta1 */,
#ifdef XXXMEH_FOR_SMIL2_INTEROP
          "text/plain",
#endif
          NULL};


/************************************************************************
 *  Method:
 *    IHXPlugin::InitPlugin
 *  Purpose:
 *    Initializes the plugin for use. This interface must always be
 *    called before any other method is called. This is primarily needed 
 *    so that the plugin can have access to the context for creation of
 *    IHXBuffers and IMalloc.
 */
STDMETHODIMP CRealTextRenderer::InitPlugin(IUnknown* /*IN*/ pContext)
{
    HX_RESULT retVal = HXR_OK;

    m_pContext = pContext;
    m_pContext->AddRef();

    m_pContext->QueryInterface(IID_IHXCommonClassFactory,
	    (void**)&m_pCommonClassFactory);

    // Get the pref interface from the context:
    m_pContext->QueryInterface(IID_IHXPreferences,
	    (void**)&m_pPreferences);

    m_pContext->QueryInterface(IID_IHXHyperNavigate,
	    (void **)&m_pHyperNavigate);

    m_pContext->QueryInterface(IID_IHXStatusMessage,
	    (void**)&m_pStatusMessage);

    m_pContext->QueryInterface(IID_IHXScheduler,
	    (void**)&m_pScheduler);

    if (m_pCommonClassFactory)
    {
        HX_RELEASE(m_pValues);
        m_pCommonClassFactory->CreateInstance(CLSID_IHXValues,
                                              (void**) &m_pValues);
    }

    if (!m_pMutex)
    {
#ifdef THREADS_SUPPORTED
	retVal = HXMutex::MakeMutex(m_pMutex);
#else  // THREADS_SUPPORTED
	retVal = HXMutex::MakeStubMutex(m_pMutex);
#endif  // THREADS_SUPPORTED
    }
    
    return retVal;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetPluginInfo
 *  Purpose:
 *    Returns the basic information about this plugin. Including:
 *
 *    bLoadMultiple	whether or not this plugin DLL can be loaded
 *			multiple times. All File Formats must set
 *			this value to TRUE.
 *    pDescription	which is used in about UIs (can be NULL)
 *    pCopyright	which is used in about UIs (can be NULL)
 *    pMoreInfoURL	which is used in about UIs (can be NULL)
 */
STDMETHODIMP CRealTextRenderer::GetPluginInfo
(
    REF(BOOL)        /*OUT*/ bLoadMultiple,
    REF(const char*) /*OUT*/ pDescription,
    REF(const char*) /*OUT*/ pCopyright,
    REF(const char*) /*OUT*/ pMoreInfoURL,
    REF(ULONG32)     /*OUT*/ ulVersionNumber
)
{
    bLoadMultiple = TRUE;   // Must be true for file formats.


    pDescription    = zm_pDescription;
    pCopyright	    = zm_pCopyright;
    pMoreInfoURL    = zm_pMoreInfoURL;
    ulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetRendererInfo
 *  Purpose:
 *    If this object is a file format object this method returns
 *    information vital to the instantiation of file format plugins.
 *    If this object is not a file format object, it should return
 *    HXR_UNEXPECTED.
 */
STDMETHODIMP CRealTextRenderer::GetRendererInfo
(
    REF(const char**) /*OUT*/ pStreamMimeTypes,
    REF(UINT32)      /*OUT*/ unInitialGranularity
)
{
    pStreamMimeTypes = zm_pStreamMimeTypes;
    // OnTimeSync() 20 times/sec (50==1000msec/20)
    unInitialGranularity = INITIAL_GRANULARITY;

    m_ulGranularity = unInitialGranularity;
    m_ulFrameRate = INITIAL_FRAME_RATE;

    return HXR_OK;
}

CRealTextRenderer::CRealTextRenderer()
    : RealTextRenderer(&m_txtWin)
    , m_bPriorPacketWasLostOrWeJustSeeked(FALSE)
    , m_lRefCount(0)
    , m_pContext(NULL)
    , m_pStream(NULL)
    , m_pPlayer(NULL)
    , m_pHeader(NULL)
    , m_pLastPacket(NULL)
    , m_pScheduler(NULL)
    , m_PendingHandle(0)
    , m_bPendingCallback(FALSE)
    , m_bInCallback(FALSE)
    , m_pOptimizedScheduler(NULL)
    , m_ulLastTime(0)
    , m_ulLastTimeSyncPlayerTimeIeNoOffset(0)
    , m_lTimeOffset(0)
    , m_lCumulativeUpdatedTimeOffset(0)
    , m_bTimeOffsetHasBeenInitialized(FALSE)
    , m_ulDuration(0L)
    , m_ulStreamStartTime(0L)
    , m_ulLastTimeSyncWorkTime(0L)
    , m_ulTimeAtPriorTimeSyncEntrance(0L)
    , m_ulTimeAtPriorTimeSyncExit(0L)
    , m_pMISUS(NULL)
    , m_pMISUSSite(NULL)
    , m_pCommonClassFactory(0)
    , m_pHyperNavigate(NULL)
    , m_pPreferences(NULL)
    , m_pStatusMessage(NULL)
    , m_pValues(NULL)
    , m_ulGranularity(INITIAL_GRANULARITY)
    , m_ulFrameRate(m_ulFrameRate)
    , m_bIsPaused(FALSE)
    , m_bNoPacketsArrivedYet(TRUE)
    , m_bSeekJustFinished(FALSE)
    , m_ulTimeOfPriorDraw(TIME_INVALID)
    , m_ulTimeOfCurrentDraw(TIME_INVALID)
    , m_ulTimeOfLastNonScrollWindowDraw(TIME_INVALID)
    , m_ulCurFrameTime(0)
    , m_bNoPacketsYetSinceLastSeek(FALSE)
    , m_bLastDrawWasAFullWindowRefresh(TRUE)
    , m_pEvent(NULL)
    , m_bNeedToSetHyperlinkHandCursor(FALSE)
    , m_bStatusMsgWillNeedErasing(FALSE)
#if defined(_WINDOWS)
    , m_hWnd(NULL)
    , m_hPreHyperlinkCursor(NULL)
    , m_hHyperlinkCursor(NULL)
#elif defined(_MACINTOSH) || defined(_MAC_UNIX)
    , m_CurrentCursor(CURSOR_ARROW)
#elif defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
    , m_hHyperlinkCursor(0)
    , m_bHandActivated(FALSE)
#endif
    , m_bInSeekMode(FALSE)
    , m_ulLastSeekedToTime(0)
    , m_bEndStreamJustOccurred(FALSE)
    , m_bLatePacketNotYetDrawn(FALSE)
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    , m_logfile(0)
#endif
#endif
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
    , m_pVisualInfo(NULL)
    , m_nBitsPerPixel(24)
    , m_lastWindow(0)
    , m_Pixmap((Pixmap)0)
    , m_pPixmapDisplay((Display*)NULL)
#endif
    , m_pMutex(NULL)
{
    g_nRefCount_rtre++;
#if defined(_MACINTOSH) || defined(_MAC_UNIX)
    const short HAND_CURSOR = 1313;
    m_pResourceLoader = CResourceLoader::CreateInstance(g_DLLFSpec);
    m_hHyperlinkCursor = (CursHandle)m_pResourceLoader->LoadResource('CURS', HAND_CURSOR);
#endif
    m_lastKnownSize.cx = 0;
    m_lastKnownSize.cy = 0;

    m_PlayState = Stopped;
};


CRealTextRenderer::~CRealTextRenderer()
{
    g_nRefCount_rtre--;
    // We're done with these...
    // the optimized scheduler has presidence so we check it first
    if (m_bPendingCallback && m_pOptimizedScheduler)
    {
	m_pOptimizedScheduler->Remove(m_PendingHandle);
	m_bPendingCallback = FALSE;
	m_PendingHandle = 0;
    }
    HX_RELEASE(m_pOptimizedScheduler);

    if (m_bPendingCallback && m_pScheduler)
    {
	m_pScheduler->Remove(m_PendingHandle);
	m_bPendingCallback = FALSE;
	m_PendingHandle = 0;
    }

    HX_RELEASE(m_pScheduler);
    HX_RELEASE(m_pHeader);
    HX_RELEASE(m_pLastPacket);
    HX_RELEASE(m_pContext);
    HX_RELEASE(m_pPlayer);
    HX_RELEASE(m_pCommonClassFactory);
    HX_RELEASE(m_pHyperNavigate);
    HX_RELEASE(m_pPreferences);
    HX_RELEASE(m_pStatusMessage);
    HX_RELEASE(m_pValues);

    HX_ASSERT(!m_pMISUSSite);

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	fclose(m_logfile);
    }
#endif
#endif

#if defined(_MACINTOSH) || defined(_MAC_UNIX)
    if (m_hHyperlinkCursor)
    {
    	m_pResourceLoader->UnloadResource((Handle)m_hHyperlinkCursor);
    	m_hHyperlinkCursor = NULL;
    	
    	HX_RELEASE(m_pResourceLoader);
    }
    
#endif

#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
    if (m_pVisualInfo)
    {
	XFree(m_pVisualInfo);
	m_pVisualInfo = NULL;
    }

    if (m_hHyperlinkCursor  && m_pPixmapDisplay) 
    {
	XLockDisplay(m_pPixmapDisplay);
	XFreeCursor(m_pPixmapDisplay, m_hHyperlinkCursor);
	XUnlockDisplay(m_pPixmapDisplay);
    }
#endif

    HX_DELETE(m_pMutex);
}


// *** IUnknown methods ***

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::QueryInterface
//  Purpose:
//	Implement this to export the interfaces supported by your 
//	object.
//
STDMETHODIMP CRealTextRenderer::QueryInterface(REFIID riid, void** ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown))
    {
	AddRef();
	*ppvObj = this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXPlugin))
    {
	AddRef();
	*ppvObj = (IHXPlugin*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXRenderer))
    {
	AddRef();
	*ppvObj = (IHXRenderer*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXSiteUser))
    {
	AddRef();
	*ppvObj = (IHXSiteUser*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXSiteUserSupplier))
    {
	if (m_pMISUS)
	{
	    return m_pMISUS->QueryInterface(IID_IHXSiteUserSupplier,ppvObj);
	}
	else
	{
	    *ppvObj = NULL;
	    return HXR_UNEXPECTED;
	}
    }
    else if (IsEqualIID(riid, IID_IHXStatistics))
    {
	AddRef();
	*ppvObj = (IHXStatistics*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXCallback))
    {
	AddRef();
	*ppvObj = (IHXCallback*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXUpdateProperties))
    {
	AddRef();
	*ppvObj = (IHXUpdateProperties*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXValues))
    {
	AddRef();
	*ppvObj = (IHXValues*)this;
	return HXR_OK;
    }

    *ppvObj = NULL;
    return HXR_NOINTERFACE;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::AddRef
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) CRealTextRenderer::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::Release
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) CRealTextRenderer::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;
    return 0;
}

// *** IHXRenderer methods ***

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::StartStream
//  Purpose:
//	Called by client engine to inform the renderer of the stream it
//	will be rendering. The stream interface can provide access to
//	its source or player. This method also provides access to the 
//	primary client controller interface.
//
STDMETHODIMP CRealTextRenderer::StartStream
(
    IHXStream*	    pStream,
    IHXPlayer*	    pPlayer
)
{
    // Save for later use!
    m_pStream  = pStream;
    m_pPlayer  = pPlayer;

    if (m_pStream ) m_pStream->AddRef();
    if (m_pPlayer ) m_pPlayer->AddRef();

    HX_RESULT hxr =
    m_pCommonClassFactory->CreateInstance(CLSID_IHXMultiInstanceSiteUserSupplier,
	    							(void**)&m_pMISUS);
    if (SUCCEEDED(hxr))
    {
        m_pMISUS->SetSingleSiteUser((IUnknown*)(IHXSiteUser*)this);
    }

    generatePreFix();

    return HXR_OK;
}

void CRealTextRenderer::RemoveCallback(CallbackHandle &hCallback)
{
    if (hCallback != (CallbackHandle)NULL)
    {
	if (m_pOptimizedScheduler != NULL)
	{
	    m_pOptimizedScheduler->Remove(hCallback);
	}
	else if (m_pScheduler)
	{
	    m_pScheduler->Remove(hCallback);
	}
	
	hCallback = NULL;
    }
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::EndStream
//  Purpose:
//	Called by client engine to inform the renderer that the stream
//	is was rendering is closed.
//
STDMETHODIMP CRealTextRenderer::EndStream()
{
    m_pMutex->Lock();

    // Removing callback within mutex after OEM Gold release of 2/2002 
    // is safe (no danger of deadlock) since since the scheduler mutex 
    // is not held across Func call.
    // Removing callback outside the mutex could result in incorrectly
    // removing a different callback that reused this handle.
    RemoveCallback(m_PendingHandle);

    HX_RELEASE(m_pStream);

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"\nCRealTextRenderer::EndStream()\n");
	}
	fflush(m_logfile);
    }
#endif
#endif

    //XXXEH1208- we only want to set this in the draw thread, namely
    // in SchedulerCallback(), in case we've still got a pending draw
    // in that thread when EndStream() gets called:  (right???)
    m_bEndStreamJustOccurred = TRUE;

    if (m_bPendingCallback)
    {
	m_bPendingCallback = FALSE;
	SchedulerCallback();
    }

    m_PlayState = Stopped;
    

    //Fixes seeking to very end of SMIL-frozen stream and m_PlayState never
    // getting reset from "Seeking" because draw thread (SchedulerCallback)
    // never gets called again to do so (in this case) so repaint never
    // gets done:
    if(m_txtWin.isLiveSource()  ||  //(Always set to Stopped if is live)
	    m_ulTimeOfCurrentDraw >= m_ulDuration + m_lTimeOffset)
    {
	m_PlayState = Stopped;

	LOG((m_logfile,"\n    CRealTextRenderer::EndStream(): "
	    "setting m_PlayState to Stopped\n"));
    }

    m_pMutex->Unlock();

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnHeader
//  Purpose:
//	Called by client engine when a header for this renderer is 
//	available. The header will arrive before any packets.
//
STDMETHODIMP CRealTextRenderer::OnHeader(IHXValues* pHeader)
{
    HX_RESULT hxr = HXR_OK;

    ULONG32 ulLengthHeader, retVal;
    // Keep this for later use...
    m_pHeader = pHeader;
    m_pHeader->AddRef();

    IHXBuffer* pOpaqueData = 0;
    ULONG32 ulStreamNumber = 0;
    ULONG32 ulMaxBitRate = 0;
    ULONG32 ulAvgBitRate = 0;
    ULONG32 ulMaxPacketSize = 0;
    ULONG32 ulAvgPacketSize = 0;
    ULONG32 ulStartTime = 0;
    ULONG32 ulPreroll = 0;
    ULONG32 ulPresentationDuration = 0;
    IHXBuffer* pStreamName = 0;
    IHXBuffer* pMimeType = 0;
    ULONG32 ulMinMajorVer = 0L;
    ULONG32 ulMinMinorVer = 0L;
    ULONG32 ulContentMajorVer = 0L;
    ULONG32 ulContentMinorVer = 0L;
    BOOL bAutoUpgradeNeeded=FALSE;

    pHeader->GetPropertyBuffer ("OpaqueData",       pOpaqueData);
    pHeader->GetPropertyULONG32("StreamNumber",     ulStreamNumber);
    pHeader->GetPropertyULONG32("MaxBitRate",       ulMaxBitRate);
    pHeader->GetPropertyULONG32("AvgBitRate",       ulAvgBitRate);
    pHeader->GetPropertyULONG32("MaxPacketSize",    ulMaxPacketSize);
    pHeader->GetPropertyULONG32("AvgPacketSize",    ulAvgPacketSize);
    pHeader->GetPropertyULONG32("StartTime",        ulStartTime);
    pHeader->GetPropertyULONG32("Preroll",          ulPreroll);
    pHeader->GetPropertyULONG32("Duration",         ulPresentationDuration);

    //This is "timeZero" of the encoder; we can no longer assume 0 is when
    // the encoder started (in fact, it is guaranteed NOT to be 0 by rtlive).
    // If we get 0 here, then it's an older encoder so we'll have to force
    // an auto-upgrade because text simply will not show up if the timelines
    // of the renderer and the rtlive app are not in sync.  Note: ulMinMajor
    // and ulMinMinor version stuff, below, will tell us to auto-upgrade in
    // this case, unless they don't exist, either, in which case we're OK
    // because that means an older encoder is sending 0-based timeline text:
    ULONG32 ulStartTimeHigh = 0L;
    pHeader->GetPropertyULONG32("RTLiveStartTimeHigh", ulStartTimeHigh);
    pHeader->GetPropertyULONG32("RTLiveStartTimeLow", m_ulStreamStartTime);
    m_ulStreamStartTime |= ulStartTimeHigh<<0x10;


    ULONG32 ulRTFileFormatVersion = 0;
    //The following is sent by the RT file format to the
    // RT renderer to notify it what "parsing" version to
    // use. This is done via the stream header properties
    // "RTMarkupParsing[Major|Minor]Version" (which are ULONG32s).
    // This must be done so that the ff and the renderer
    // are in sync as far as how they deal with new tags,
    // e.g., if the file format ignores a tag it doesn't
    // recognize but the renderer is newer and recognizes
    // the tag, the renderer should behave as does the ff
    // and ignore the tag otherwise the renderer might
    // display the text in a different place than the ff
    // thinks it is and the next packet sent by the ff
    // will tell the renderer to draw in a place that
    // might overlap the prior packet's text due to this
    // discrepancy.
    pHeader->GetPropertyULONG32("RTMarkupParsingMajorVersion",
	    m_ulRTMarkupParsingMajorVersion);
    pHeader->GetPropertyULONG32("RTMarkupParsingMinorVersion",
	    m_ulRTMarkupParsingMinorVersion);

    //These (put together) are the minimum version that the renderer
    // must know about.  If the renderer is older, it must fire auto-upgrade:
    // Note: this is the file format/rtlive version, NOT the content or
    // parsing version:
    pHeader->GetPropertyULONG32("MinRTMajorVersion", ulMinMajorVer);
    pHeader->GetPropertyULONG32("MinRTMinorVersion", ulMinMinorVer);

    //These (put together) are the version that the rt file's author has
    // declared for the file contents.  If omitted from the file, it is 0.0:
    // Note: this will allow us to properly deal with future changes to
    // the header and other tags in the rt file, namely: force auto-upgrade:
    pHeader->GetPropertyULONG32("RTMajorContentVersion", ulContentMajorVer);
    pHeader->GetPropertyULONG32("RTMinorContentVersion", ulContentMinorVer);
    
    //If renderer is older than ff, we should have been notified to
    // auto update!!!
    BOOL bRendererIsOlderThanFileFormat = FALSE;
    if(m_ulRTMarkupParsingMajorVersion >
	    REAL_TEXT_MARKUP_PARSING_MAJOR_VERSION  ||
	    (m_ulRTMarkupParsingMajorVersion ==
	    REAL_TEXT_MARKUP_PARSING_MAJOR_VERSION  &&
	    m_ulRTMarkupParsingMinorVersion >
	    REAL_TEXT_MARKUP_PARSING_MINOR_VERSION) )
    {
	//The renderer is older than the file format so we
	// won't be able to play the stream until we get the
	// latest rtrender dll:
	bRendererIsOlderThanFileFormat = TRUE;
    }
    //And, if renderer is too old to handle content version, it should
    // force an auto-upgrade as well:
    BOOL bRendererIsTooOldToRenderContent = FALSE;
    if((ulContentMajorVer >
	    REAL_TEXT_CONTENT_MAJOR_VERSION)  ||
	    (ulContentMajorVer ==
	    REAL_TEXT_CONTENT_MAJOR_VERSION  &&
	    ulContentMinorVer >
	    REAL_TEXT_CONTENT_MINOR_VERSION) )
    {
	//The renderer is older than the file format so we
	// won't be able to play the stream until we get the
	// latest rtrender dll:
	bRendererIsTooOldToRenderContent = TRUE;
    }

    //Now, if certain properties did not exist (returned 0) or
    // if we're older than the file format AND we're older than
    // the ulMinMajorVer.ulMinMinorVer, we should force an auto-upgrade:
    if(bRendererIsTooOldToRenderContent  ||
	    (bRendererIsOlderThanFileFormat  &&
		(0!=ulMinMajorVer  ||  0!=ulMinMinorVer)) )
    {
	if(bRendererIsTooOldToRenderContent  ||
		(ulMinMajorVer >
		REAL_TEXT_MARKUP_PARSING_MAJOR_VERSION  ||
		(ulMinMajorVer ==
		REAL_TEXT_MARKUP_PARSING_MAJOR_VERSION  &&
		ulMinMinorVer >
		REAL_TEXT_MARKUP_PARSING_MINOR_VERSION)) )
	{
	    HX_ASSERT(m_pContext);
            AddToAutoUpgradeCollection(zm_pStreamMimeTypes[0], m_pContext);

	    hxr = HXR_FAIL;
	    bAutoUpgradeNeeded = TRUE;
	}
     }

    pHeader->GetPropertyCString("StreamName",       pStreamName);
    pHeader->GetPropertyCString("MimeType",         pMimeType);

    ULONG32 ulTotalSourceFileSizeInBytes = (ULONG32)(((double)ulAvgBitRate /
	    8.0/*bits/byte*/) * 60.0/*seconds*/) / 1.2 /*1.2==FF's
						      HEADER_OVERHEAD_FACTOR*/;
    ULONG32 ulTmp = 0;
    if (HXR_OK == pHeader->GetPropertyULONG32("SourceFileSize", ulTmp)  &&
	    ulTmp > 0)
    {
	ulTotalSourceFileSizeInBytes = ulTmp;
    }
    RealTextRenderer::setSourceFileSize(ulTotalSourceFileSizeInBytes);
    ULONG32 ulMaxPlainTextBytesToBeSentByFF = (UINT32)-1;
    if (HXR_OK == pHeader->GetPropertyULONG32("MaxPlainTextBytesToBeSent",ulTmp)
	    &&  ulTmp > 0)
    {
	ulMaxPlainTextBytesToBeSentByFF = ulTmp;
    }
    RealTextRenderer::setMaxPlainTextBytesToBeSentByFF(
	    ulMaxPlainTextBytesToBeSentByFF);

    if (pMimeType)
    {
	const char* pszMimeType = (const char*)pMimeType->GetBuffer();
	if (pszMimeType  &&  0==stricmp(pszMimeType, "text/plain") )
	{
	    RealTextRenderer::setIsTextPlainStreamMimeType(TRUE);
	}
    }

    //XXXEH- this seems to work even though m_ulDuration hasn't been set
    // yet; do we really want to do this, or wait until after it's set?
    m_txtWin.SetLatestSentTimeToStopRendering(m_ulDuration);

    ulLengthHeader = 0;
    if (pOpaqueData) // /This will be NULL if text/plain, which has no header.
    {
	ulLengthHeader = pOpaqueData->GetSize();
 
	retVal = RealTextRenderer::OnHeader(pOpaqueData->GetBuffer(),
		ulLengthHeader);
    }
    else // /We still need to call OnHeader() to init m_txtWin properly:
    {
	char* pszPlainTextHeader = new char[32];
	if (pszPlainTextHeader)
	{
	    strcpy(pszPlainTextHeader, " "); /* Flawfinder: ignore */
	    ulLengthHeader = strlen(pszPlainTextHeader);
 
	    retVal = RealTextRenderer::OnHeader(pszPlainTextHeader,
		    ulLengthHeader);
	    delete [] pszPlainTextHeader;
	}
    }

    if(ulPresentationDuration)
    {
	m_txtWin.m_ulDuration = ulPresentationDuration;
    }
    else
    {
	m_txtWin.m_ulDuration = DEFAULT_DURATION_MSEC;
    }
    //XXXXXEH-bp: make sure we know this value before we use it:
    if(m_txtWin.isLiveSource())
    {
	m_txtWin.m_ulDuration = TIME_INVALID;
    }
    m_ulDuration = m_txtWin.m_ulDuration;

    if (RealTextRenderer::isTextPlainStreamMimeType())
    {
	m_ulGranularity = PLAIN_TEXT_GRANULARITY;
    }
    //Adjust the granularity of time synchs based on the rate(s) of motion,
    // i.e., we want time synchs in concert with the scrollrate and
    // crawlrate, if any, so that the renderer gets enough timesyncs to draw
    // with the smoothest motion.  For example, if the scroll rate is 20
    // pixels per second and crawl rate is 0, then we want to draw every
    // 50 millisec so that we move the text exactly 1 pixel each draw.
    // If the scroll rate is 20 and the crawl rate is 30, we'd draw every
    // 33 milliseconds to handle the higher crawl rate:
    else if (!m_txtWin.getScrollRate()  &&  !m_txtWin.getCrawlRate())
    {
	m_ulGranularity = NO_MOTION_GRANULARITY;
    }
    else
    {
	LONG32 lHighestRate = m_txtWin.getScrollRate();
	if(lHighestRate < m_txtWin.getCrawlRate())
	{
	    lHighestRate = m_txtWin.getCrawlRate();
	}
	m_ulFrameRate = lHighestRate;
	if(m_ulFrameRate > MAX_ALLOWED_FRAMERATE)
	{
	    //If framerate is, say 75, we want to use
	    // 25 (which is a divisor of both 75 and 1000)
	    // and not use 30 (which is not):
	    if(m_ulFrameRate % MAX_SMOOTH_FRAMERATE == 0)
	    {
		m_ulFrameRate = MAX_SMOOTH_FRAMERATE;
	    }
	    //If framerate is, say 40, we want to use
	    // 20 (which is a divisor of both 40 and 1000)
	    // and not use 30 (which is not):
	    else if(m_ulFrameRate % MAX_SMOOTH_FRAMERATE_2 == 0)
	    {
		m_ulFrameRate = MAX_SMOOTH_FRAMERATE_2;
	    }
	    else
	    {
		m_ulFrameRate = MAX_ALLOWED_FRAMERATE;
	    }
	}
	if(m_ulFrameRate < MIN_ALLOWED_FRAMERATE)
	{
	    m_ulFrameRate = MIN_ALLOWED_FRAMERATE;
	}
	m_ulGranularity = (1000 / m_ulFrameRate);
    }

    m_pStream->SetGranularity(m_ulGranularity);
   
    //Since this variable is initialized to zero, we need to
    // re-set it to the wallclock-based timeline's "zero" if
    // live which equals the start time of the encoder.  Fixes
    // PR 23344:
    if (m_txtWin.isLiveSource())
    {
	m_txtWin.setTimeOfLastTimeSync(m_ulStreamStartTime);
    }

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK  &&
	    !m_logfile) //Only create a log for the first stream.
    {
	char slashChar = '\\';
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
	slashChar = '/';
#endif
#if defined(_MACINTOSH) || defined(_MAC_UNIX)
	m_logfile = fopen("rt_0.log", "w");
#endif
#if defined(_WINDOWS)
	char logfilename[32]; /* Flawfinder: ignore */
	sprintf(logfilename,"c:%crt_%i.log", slashChar, /* Flawfinder: ignore */
		m_pStream->GetStreamNumber());
	m_logfile=fopen(logfilename,"w");
#endif
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
	char logfilename[32]; /* Flawfinder: ignore */
	sprintf(logfilename,"/tmp/rt_%i.log", /* Flawfinder: ignore */
		m_pStream->GetStreamNumber());
	m_logfile=fopen(logfilename,"w");
#endif

	if(m_logfile)
	{
	    fprintf(m_logfile,"Stream number %lu\n", ulStreamNumber);
	    fprintf(m_logfile,"Max bit rate %lu\n", ulMaxBitRate);
	    fprintf(m_logfile,"Avg bit rate %lu\n", ulAvgBitRate);
	    fprintf(m_logfile,"Max packet size %lu\n", ulMaxPacketSize);
	    fprintf(m_logfile,"Avg packet size %lu\n", ulAvgPacketSize);
	    fprintf(m_logfile,"Start time %lu ms\n", ulStartTime);
	    fprintf(m_logfile,"Preroll %lu ms\n", ulPreroll);
	    fprintf(m_logfile,"Duration %lu ms\n", ulPresentationDuration);
	    fprintf(m_logfile,"Stream name length: %lu\n",
		    pStreamName? pStreamName->GetSize():0 );
	    fprintf(m_logfile,"Header opaque data length: %lu\n",
		    pOpaqueData? pOpaqueData->GetSize():0 );
            if (pOpaqueData)
            {
	        fprintf(m_logfile,"Header pOpaqueData: {{{%s}}}\n",
		        pOpaqueData->GetBuffer() );
            }
	    fprintf(m_logfile, "\nSetting granularity for OnTimeSync() "
		    "calls to: %lu\n", m_ulGranularity);
//XXXXXEH-bp: make sure we know this value before we use it:
	    if(m_txtWin.isLiveSource())
	    {
		fprintf(m_logfile,"RTLiveStartTime %lu ms\n",
			m_ulStreamStartTime);
	    }
	    fprintf(m_logfile,"RTMarkupParsingMajorVersion."
		    "RTMarkupParsingMinorVersion "
		    "%lu.%lu\n", m_ulRTMarkupParsingMajorVersion,
		    m_ulRTMarkupParsingMinorVersion);
	    fprintf(m_logfile,"REAL_TEXT_MARKUP_PARSING_MAJOR_VERSION."
		    "REAL_TEXT_MARKUP_PARSING_MINOR_VERSION "
		    "%lu.%lu\n", REAL_TEXT_MARKUP_PARSING_MAJOR_VERSION,
		    REAL_TEXT_MARKUP_PARSING_MINOR_VERSION);
	    fprintf(m_logfile,"MinRTMajorVersion.MinRTMinorVersion "
		    "%lu.%lu\n", ulMinMajorVer, ulMinMinorVer);
	    if(bAutoUpgradeNeeded)
	    {
		fprintf(m_logfile,"\nAuto upgrade being called\n");
	    }

	    if(!m_ulStreamStartTime)
	    {
		//If m_ulStreamStartTime was not set yet in OnHeader, then,
		// if it's live, the live text encoder was using a 0-based
		// timeline so we're OK.  This value is only really used by
		// live streams because it is set to 0 for non-live.  This is
		// used as "timeZero" when calculating the amt of scroll
		// and/or crawl at any time in the stream:
		if(m_txtWin.isLiveSource())
		{
		    fprintf(m_logfile, "\n*** Live Text app is using an old,"
			    " pre-wallclock-sync rtlive library\n");
		}
	    }
	}
    }
#endif
#endif

    if(pOpaqueData)
    {
        pOpaqueData->Release();
    }	
    if(pStreamName)
    {
        pStreamName->Release();
    }
    if(pMimeType)
    {
        pMimeType->Release();
    }
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	fflush(m_logfile);
    }
#endif
#endif

    HXxSize rtxFileSpecifiedSize;
    rtxFileSpecifiedSize.cx = m_txtWin.getWidth();
    rtxFileSpecifiedSize.cy = m_txtWin.getHeight();

    m_txtWin.setVisibleWindowWidth(m_txtWin.getWidth());
    m_txtWin.setVisibleWindowHeight(m_txtWin.getHeight());

    // Set the initial properties
    if (m_pValues)
    {
        m_pValues->SetPropertyULONG32("backgroundColor",
                                      m_txtWin.getBackgroundColor());
        m_pValues->SetPropertyULONG32("backgroundOpacity",
                                      m_txtWin.getBackgroundOpacity());
        m_pValues->SetPropertyULONG32("mediaOpacity",
                                      m_txtWin.getMediaOpacity());
        m_pValues->SetPropertyULONG32("chromaKeyTolerance",
                                      m_txtWin.getChromaKeyTolerance());
        m_pValues->SetPropertyULONG32("chromaKeyOpacity",
                                      m_txtWin.getChromaKeyOpacity());
    }

    // /This is a good time to check the user setting, if any, for text size:
    // /XXXEH- It's resource-intensive to do the following, so only do it
    // sparingly.  For now, we'll just call it once, but we'll probably want
    // to add support for the user changing the pt size pref during playback:
    adjustForUserTextSizeSetting();

    return hxr;
}


/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPacket
//  Purpose:
//	Called by client engine when a packet for this renderer is 
//	due.
//
STDMETHODIMP CRealTextRenderer::OnPacket(IHXPacket* pPacket, 
					 LONG32 lTimeOffset)
{
    ULONG32 ulLength;

    if (!pPacket) return HXR_OK;

    if (m_bInSeekMode)
    {
	return HXR_OK;
    }

    m_lTimeOffset = lTimeOffset;
    m_bTimeOffsetHasBeenInitialized = TRUE;
    HX_ASSERT(m_lTimeOffset == lTimeOffset);
    
    if (pPacket->IsLost())
    {
        //Since live packets get resent (and we'll eventually get this lost
	// one), we don't want to put extraneous text in the rendering:
	if(!m_txtWin.isLiveSource())
	{
	    char szLostData[] ={"<FONT color=black bgcolor=red>   ...   </FONT>"};
	    OnData(szLostData, strlen(szLostData), TRUE /*=from OnPacket()*/);
	    m_bPriorPacketWasLostOrWeJustSeeked = TRUE;
	}

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile  &&
		m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"CRealTextRenderer::OnPacket(): Packet was lost."
		    " (lTimeOffset=%ld).\n", lTimeOffset );
	    
	    fflush(m_logfile);
	}
#endif
#endif

	return HXR_OK;
    }

    if(m_bNoPacketsYetSinceLastSeek)
    {
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile  &&
		m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"    m_bNoPacketsYetSinceLastSeek=TRUE\n");	    
	    fflush(m_logfile);
	}
#endif
#endif
	m_bNoPacketsYetSinceLastSeek = FALSE;
	//reset() clears the TextAttributeStacks, too, but I think
	// that's OK because the stacks keep their m_default vals intact
	// and re-push them if a pop() when empty is tried:
	/*XXXXXEH- don't need to do this because we'll never get here if
	// we've exceeded the duration and m_PlayState==playing:
#error: if you reactivate this code, make sure to use safe time comparison
  function "IsTimeAMoreRecentThanTimeB(...)" being sure to pass in the
	(defaultable) 3rd parameter: m_txtWin.isLiveSource():
	if(ulNewTime <= m_ulDuration)
	*/
	{
	    m_txtWin.reset();
	}

	flush(); //clears ::ListOfPacketDataIDsReceived.

	//Now, just kill everything after a seek (and before any new packets
	// arrive) since packets are resent anyway.  For code that handles
	// keeping around prior text, see revision 122 or earlier of this
	// file, in this function:
	m_txtWin.clear_all();
    }

    // Release the last packet if we had one...
    if (m_pLastPacket)
    {
        HX_RELEASE(m_pLastPacket);
    }

    // Keep this one for later use...
    m_pLastPacket = pPacket;
    if (m_pLastPacket)
    {
	m_pLastPacket->AddRef();

	IHXBuffer* pBuffer = m_pLastPacket->GetBuffer();

    	LONG32 lRealPacketTime = pPacket->GetTime();

	if(m_txtWin.isLiveSource()  &&  m_bNoPacketsArrivedYet)
	{
	    m_ulCurFrameTime = lRealPacketTime; //set first frame time.
	}

	m_bNoPacketsArrivedYet = FALSE;

	UINT32 ulPktTimeInPlayerTimeIeNoOffset = pPacket->GetTime();

	//Adjust the time based on the offset handling overflow
	// correctly:
	if (lTimeOffset < 0)
	{
	    ulPktTimeInPlayerTimeIeNoOffset += (UINT32) (-lTimeOffset);
	}
	else
	{
	    if (ulPktTimeInPlayerTimeIeNoOffset < (UINT32)lTimeOffset)
	    {
		ulPktTimeInPlayerTimeIeNoOffset = 0;
	    }
	    else
	    {
		ulPktTimeInPlayerTimeIeNoOffset -= (UINT32)lTimeOffset;
	    }
	}

	BOOL bDoRedrawNow = FALSE;
	//(PR22705): Now, if we're not live, compare the packet time (in
	// player time) to the time of last time sync (in player time); if
	// the packet time is earlier, then we need to make sure a draw
	// occurs to include this new text:
	if (!m_txtWin.isLiveSource()  &&
		ulPktTimeInPlayerTimeIeNoOffset <=
		m_ulLastTimeSyncPlayerTimeIeNoOffset)
	{
	    m_bLatePacketNotYetDrawn = TRUE;
	    // Do the following in case we don't get a time synch between
	    // draws, e.g., if the playback stops and then the rt window is
	    // covered and then uncovered, we'll get a paint message but
	    // no time sync associated with it, so we don't want to
	    // DO_CPU_FRIENDLY_SCROLLING in this case:
	    SetSomeonesBeginOrEndTimeWasCrossedOverSinceLastDraw(TRUE);
	    //In case we're receiving this packet late and after our
	    // duration has expired (and thus the draw scheduler has
	    // quit), we should still give it a chance to draw by
	    // forcing a redraw:
	    if (ShouldKickStartScheduler())
	    {
		if (m_pMISUSSite)
		{
		    m_ulTimeOfPriorDraw = m_ulTimeOfCurrentDraw;
		    m_ulTimeOfCurrentDraw = m_ulCurFrameTime;
		    if(m_ulTimeOfCurrentDraw > m_ulDuration+m_lTimeOffset  &&
			    !m_txtWin.isLiveSource())
		    {
			//Never draw at time greater than duration or
			// we might scroll too far...etc.:
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
			if(m_logfile)
			{
			    if(m_txtWin.getDebugFlags()&
				    RT_RENDER_DEBUG_FLAGS_MASK)
			    {
				fprintf(m_logfile, 
					"    -setting m_ulTimeOfCurrentDraw"
					" to m_ulDuration(%lu) +"
					" m_lTimeOffset(%lu)=%lu\n",
					m_ulDuration, m_lTimeOffset,
					m_ulDuration + m_lTimeOffset);
			    }
			    fflush(m_logfile);
			}
#endif
#endif
			m_ulTimeOfCurrentDraw = m_ulDuration + m_lTimeOffset;
		    }
		    bDoRedrawNow = TRUE;
		}
	    }
	}

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile  &&
		m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"CRealTextRenderer::OnPacket()..."
		    " Got Packet of stream %i of "
		    "size %ld of time %ld "
		    "(m_ulLastTimeSyncPlayerTimeIeNoOffset=%ld,"
		    "lTimeOffset=%ld, "
		    "m_bLatePacketNotYetDrawn=%sE).\n{{{%s}}}\n",
	    pPacket->GetStreamNumber(),pBuffer->GetSize(),
	    lRealPacketTime, m_ulLastTimeSyncPlayerTimeIeNoOffset,
	    lTimeOffset,
	    m_bLatePacketNotYetDrawn?"TRU":"FALS",
	    pBuffer->GetBuffer() );
	    
	    fflush(m_logfile);
	}
#endif
#endif


	ulLength = pBuffer->GetSize();

	{
	    //Flush the Font, Bold, Italics, Underline, Strikethrough,
	    // and indentAmt stacks because the packet header tells
	    // us what's in them for the packet's text:
	    m_txtWin.flushFontStacks();
	    m_txtWin.flushTickerStacks();
	    m_txtWin.flushBIUSandBlinkStacks();
	    m_txtWin.flushIndentAmtStack();
	    RealTextRenderer::OnData(pBuffer->GetBuffer(),
		    ulLength, TRUE /*=from OnPacket()*/);
	    pBuffer->Release();
	}

	// /Fixes PR 39718:
	// when a packet arrives right after a seek but prior to other
	// streams' buffering completion, the rt window was being cleared
	// and the new data here was not being drawn so the user would
	// see blank rt windows during seek buffering.  Moving the redraw
	// request to after "OnData" call, above, makes sure the new text
	// gets drawn prior to the long wait for other streams' buffering:
	if (bDoRedrawNow)
	{
	    // Redraw our data by damaging the whole window:
	    CHXxRect rect(
	    	    m_txtWin.getUpperLeftX(),
	    	    m_txtWin.getUpperLeftY(),
		    (m_txtWin.getUpperLeftX()) +
			    m_txtWin.getWindowWidth(),
		    (m_txtWin.getUpperLeftY()) +
			    m_txtWin.getWindowHeight());
	    m_pMISUSSite->DamageRect(rect);
	    m_pMISUSSite->ForceRedraw();
	}
    }

    m_bPriorPacketWasLostOrWeJustSeeked = FALSE;

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnTimeSync
//  Purpose:
//	Called by client engine to inform the renderer of the current
//	time relative to the streams synchronized time-line. The 
//	renderer should use this time value to update its display or
//	render it's stream data accordingly.
//
STDMETHODIMP CRealTextRenderer::OnTimeSync(ULONG32 ulTime)
{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    ULONG32 ulPrevTime = m_ulLastTime;
#endif
#endif

    UINT32 ulAdjustedTime = 0;
    //Note: m_lTimeOffset is the offset passed in by the most-recent
    // OnPacket() call:
    if(m_lTimeOffset<0  &&  ulTime<(UINT32)(-m_lTimeOffset))
    {
	ulAdjustedTime = 0;
    }
    else
    {
	ulAdjustedTime = ulTime + m_lTimeOffset;
    }

    // /Next, we need to adjust for any pausing period that may have happened
    // relative to the main timeline:
    ulAdjustedTime += m_lCumulativeUpdatedTimeOffset;

    if (m_PlayState != Playing)
    {
	// we just started playback
	m_PlayState = Playing;
	m_ulLastTimeSyncWorkTime = 0;
	m_ulLastTime = 0;
	m_ulLastTimeSyncPlayerTimeIeNoOffset = 0L;
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"    switching to \"Playing\" at time %lu "
		    "in OnTimeSync()...\n",
		    HX_GET_BETTERTICKCOUNT());
	    fflush(m_logfile);
	}
#endif
#endif
    }

    //Keep track of how long this OnTimeSync() call takes to process so we
    // don't hog the CPU if it takes too long:
    m_ulTimeAtPriorTimeSyncEntrance = HX_GET_BETTERTICKCOUNT();
    if(0 == m_ulTimeAtPriorTimeSyncExit) //first time here:
    {
	m_ulTimeAtPriorTimeSyncExit = m_ulTimeAtPriorTimeSyncEntrance;
    }
    ULONG32 ulTimeUsedUpByAllOthers =
	    m_ulTimeAtPriorTimeSyncEntrance - m_ulTimeAtPriorTimeSyncExit;

    ULONG32 ulTimeAllowedForThisRendering =
	    //We'll skip this draw if other processes are using too much CPU,
	    // but let's not be TOO accomodating, i.e., only if others used
	    // 2 * m_ulGranularity or more do we force a skip of this:
	    (2 * m_ulGranularity) -  ulTimeUsedUpByAllOthers;
    if((2 * m_ulGranularity) < ulTimeUsedUpByAllOthers)
    {
	ulTimeAllowedForThisRendering = 0L;
    }

    // Keep track of time of last rendering:
    m_ulLastTime = ulAdjustedTime;
    //Also, keep track of last time sync time (without offset since we may
    // not have the valid offset yet) in case we later get a packet that
    // should have arrived before this time sync and needs to be drawn
    // (related to PR 22705):
    m_ulLastTimeSyncPlayerTimeIeNoOffset = ulTime;

    TextWindow* pTW = GetTextWindow();
    if(pTW)
    {
	pTW->setTimeOfLastTimeSync(ulAdjustedTime);
    }

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnTimeSync() at: %lu, "
		"adjusted to: %lu, m_lTimeOffset=%ld\n", ulTime,
		ulAdjustedTime, m_lTimeOffset);
	fprintf(m_logfile,"  (Adjusted) Time since last time sync: %ld\n",
		ulAdjustedTime-ulPrevTime);        
	fprintf(m_logfile,"  last work time is: %lu\n",
		m_ulLastTimeSyncWorkTime);
	fflush(m_logfile);
    }
#endif
#endif

    //Don't need to use safe time compare "IsTimeAMoreRecentThanTimeB()"
    // if live because of the "|| m_txtWin.isLiveSource()" which moots it:
    if((ulAdjustedTime <= m_ulDuration+m_lTimeOffset  ||
	    m_txtWin.isLiveSource()  ||
	    //Helps fix for PR 22048 and 22705; if first timeSync() is later
	    // than our duration, draw anyway as if we're at the duration:
	    m_bIsFirstTimeSynch
	    )  &&
	    ShouldKickStartScheduler())
    {
	//Jump start the drawing scheduler:
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"    Jump starting the drawing scheduler.\n");
	    fflush(m_logfile);
	}
#endif
#endif
	m_pMutex->Lock();
	SchedulerCallback();
	m_pMutex->Unlock();
    }

    //[We used to check if ulTime was greater than duration, here; if it was
    // then we would set the play state to Stopped, but the SchedulerCallback
    // above might not be finished, yet, on the draw thread and it would end up
    // not drawing when it saw that the state was Stopped.  This check got moved
    // to inside SchedulerCallback(), right after it has done the draw.]

    //NOTE: Drawing is now done inside the ::SchedulerCallback() code.
    
    m_ulTimeAtPriorTimeSyncExit = HX_GET_BETTERTICKCOUNT();
    m_ulLastTimeSyncWorkTime = m_ulTimeAtPriorTimeSyncExit -
	    m_ulTimeAtPriorTimeSyncEntrance;

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPreSeek
//  Purpose:
//	Called by client engine to inform the renderer that a seek is
//	about to occur. The render is informed the last time for the 
//	stream's time line before the seek, as well as the first new
//	time for the stream's time line after the seek will be completed.
//
STDMETHODIMP CRealTextRenderer::OnPreSeek(ULONG32 ulOldTime, ULONG32 ulNewTime)
{
    m_pMutex->Lock();
    m_bInSeekMode   = TRUE;
    m_bEndStreamJustOccurred = FALSE;
    m_PlayState = Seeking;

    m_ulLastSeekedToTime = ulNewTime;

    LOG((m_logfile,"\nCRealTextRenderer::OnPreSeek(): "
		"setting m_PlayState to Seeking\n"));

    //Set this to the new time we're seeking to rather than TIME_INVALID
    // so, in the case where we're seeking past our duration, the next
    // Draw() won't call OnPaint with TIME_INVALID and thus OnPaint won't
    // re-paint the background but skip the foreground (text) painting
    // thinking erroneously that we're drawing prior to any packets arriving:
    if(m_bTimeOffsetHasBeenInitialized)
    {
	m_ulTimeOfCurrentDraw = ulNewTime + m_lTimeOffset;
    }
    else
    {
	m_ulTimeOfCurrentDraw = 0L;
    }
    //The following are done both here and in the "PreSeek" code in
    // SchedulerCallback() (draw thread) so that, in the case where we
    // seek beyond the end of the stream, we still reset the following even
    // though SchedulerCallback() never gets called again:
    m_ulTimeOfPriorDraw = TIME_INVALID;
    m_ulTimeOfLastNonScrollWindowDraw = TIME_INVALID;
    //Fixes seeking to a spot past end of rt stream (to where stream is
    // frozen) and a covering/uncovering of the window results in a draw
    // that is done at a time past the duration, thus scrolling moving
    // text out of the window:
    if(m_ulTimeOfCurrentDraw > m_ulDuration + m_lTimeOffset)
    {
	m_ulTimeOfCurrentDraw = m_ulDuration + m_lTimeOffset;
    }

    m_ulCurFrameTime = (ulNewTime < m_ulDuration? ulNewTime:m_ulDuration) +
	    m_lTimeOffset;
    if(m_lTimeOffset < 0L  &&  ulNewTime < (ULONG32)(-m_lTimeOffset))
    {
	m_ulCurFrameTime = 0L;
    }
    if(!m_bTimeOffsetHasBeenInitialized)
    {
	m_ulCurFrameTime = 0L;
    }

    //We have to set this here so that hyperlink-mouse-hit-test code will
    // work even after a seek beyond our duration:
    //XXXEH1208- do I want to add m_lTimeOffset to m_ulDuration here?
    m_txtWin.setTimeOfLastTimeSync(ulNewTime > m_ulDuration?
	    m_ulDuration:ulNewTime);

    m_bSeekJustFinished = TRUE;

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnPreSeek(oldTime:=%lu, newTime:=%lu)\n",
		ulOldTime, ulNewTime);
    }
#endif
#endif

    //Next OnTimeSync --> OnTimeSynch() will be the first post-seek one:
    m_bIsFirstTimeSynch = TRUE;
    m_pMutex->Unlock();

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPostSeek
//  Purpose:
//	Called by client engine to inform the renderer that a seek has
//	just occured. The render is informed the last time for the 
//	stream's time line before the seek, as well as the first new
//	time for the stream's time line after the seek.
//
STDMETHODIMP CRealTextRenderer::OnPostSeek(ULONG32 ulOldTime,
					   ULONG32 ulNewTime)
{
    m_pMutex->Lock();
    m_ulCurFrameTime = ulNewTime + m_lTimeOffset;
    if(m_lTimeOffset < 0L  &&  ulNewTime < (ULONG32)(-m_lTimeOffset))
    {
	m_ulCurFrameTime = 0L;
    }

    // /Final fix for PR 95127: move this block out from the if(m_lTime...)
    // block above; we want to do the following code under all seek
    // circumstances.  It turns out that it doesn't hurt to do so in cases
    // where it isn't needed, and it helps prevent doubling of text in cases
    // where this plain-text stream has an explicit durutaion (dur or end in
    // the SMIL) and is seeked during the time it is active (not frozen):
    {
	// /To help fix renderer bug due to rtff fix of PR 95127:
	// if we're seeking back to before our start time and if it's
	// plain text, then erase all plain text received so far
	// (otherwise new packets will just get appended to the old
	// (same) text:
	if (RealTextRenderer::isPlainText())
	{
	    m_ulPlainTextDataLen = 0;
	    if (m_pPlainTextData)
	    {
		delete [] m_pPlainTextData;
		m_pPlainTextData = NULL;
	    }
	    m_ulPlainTextDataBufferSize = 0;
	}
    }
    if(!m_bTimeOffsetHasBeenInitialized)
    {
	m_ulCurFrameTime = 0L;
    }

    m_bInSeekMode   = FALSE;
    m_bEndStreamJustOccurred = FALSE;

    m_bNoPacketsYetSinceLastSeek = TRUE;

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnPostSeek(oldTime:=%lu, newTime:=%lu)\n",
		ulOldTime, ulNewTime);
    }
#endif
#endif
    m_pMutex->Unlock();

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPause
//  Purpose:
//	Called by client engine to inform the renderer that a pause has
//	just occured. The render is informed the last time for the 
//	stream's time line before the pause.
//
STDMETHODIMP CRealTextRenderer::OnPause(ULONG32 ulTime)
{
    m_pMutex->Lock();
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnPause() at %lu. "
	    " Setting m_PlayState to Paused\n", ulTime);
    }
#endif
#endif

    m_PlayState = Paused;

    m_pMutex->Unlock();

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnBegin
//  Purpose:
//	Called by client engine to inform the renderer that a begin or
//	resume has just occured. The render is informed the first time 
//	for the stream's time line after the resume.
//
STDMETHODIMP CRealTextRenderer::OnBegin(ULONG32 ulTime)
{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnBegin() at %lu\n", ulTime);
    }
#endif
#endif

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnBuffering
//  Purpose:
//	Called by client engine to inform the renderer that buffering
//	of data is occuring. The render is informed of the reason for
//	the buffering (start-up of stream, seek has occured, network
//	congestion, etc.), as well as percentage complete of the 
//	buffering process.
//
STDMETHODIMP CRealTextRenderer::OnBuffering(ULONG32 ulFlags,
	UINT16 unPercentComplete)
{
    m_pMutex->Lock();
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnBuffering();"
	    " Setting m_PlayState to Paused\n");
    }
#endif
#endif

    m_PlayState = Paused;

    m_pMutex->Unlock();

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::GetDisplayType
//  Purpose:
//	Called by client engine to ask the renderer for it's preferred
//	display type. When layout information is not present, the 
//	renderer will be asked for it's prefered display type. Depending
//	on the display type a buffer of additional information may be 
//	needed. This buffer could contain information about preferred
//	window size.
//
STDMETHODIMP CRealTextRenderer::GetDisplayType
(
    REF(HX_DISPLAY_TYPE)   ulFlags,
    REF(IHXBuffer*)	    pBuffer
)
{
    ulFlags = HX_DISPLAY_WINDOW | HX_DISPLAY_SUPPORTS_RESIZE 
	    | HX_DISPLAY_SUPPORTS_FULLSCREEN;

    return HXR_OK;
}

/************************************************************************
 *	Method:
 *	    IHXRenderer::OnEndofPackets
 *	Purpose:
 *	    Called by client engine to inform the renderer that all the
 *	    packets have been delivered. However, if the user seeks before
 *	    EndStream() is called, renderer may start getting packets again
 *	    and the client engine will eventually call this function again.
 */
STDMETHODIMP CRealTextRenderer::OnEndofPackets(void)
{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
    {
	fprintf(m_logfile,"OnEndofPackets()\n");
    }
#endif
#endif

    if(m_pTextWindow)
    {
	//In case the last line is not yet centered because it doesn't
	// know it's a complete line yet, make sure it is centered (noting
	// that this function will only center text that hasn't yet been
	// centered and that is between a <center> and a </center> tag):
	m_pTextWindow->CenterPriorLine();
    }
    
    return HXR_OK;
}


/************************************************************************
 *	Method:
 *	    IHXStatistics::Init
 *	Purpose:
 *	    Pass registry ID to the caller
 *
 */
STDMETHODIMP
CRealTextRenderer::InitializeStatistics
(
    UINT32	/*IN*/ ulRegistryID
)
{
    HX_RESULT rc = HXR_OK;

    IHXRegistry* pRegistry;
    if (HXR_OK == m_pContext->QueryInterface(IID_IHXRegistry,
	    (void**)&pRegistry))
    {
	char	    szRegistryEntry[MAX_DISPLAY_NAME] = {0}; /* Flawfinder: ignore */
	IHXBuffer*	    pszRegistryName = NULL;

	// Get the current registry key name
	if (HXR_OK ==
		pRegistry->GetPropName(ulRegistryID, pszRegistryName))
	{
	    IHXBuffer* pValue = new CHXBuffer();
	    pValue->AddRef();

	    SafeSprintf(szRegistryEntry, MAX_DISPLAY_NAME, "%s.name",
		    pszRegistryName->GetBuffer());

	    pValue->Set((const UCHAR*)zm_pName, strlen(zm_pName) + 1);
	    pRegistry->AddStr(szRegistryEntry, pValue);

	    HX_RELEASE(pValue);
	    HX_RELEASE(pszRegistryName);
	}

	HX_RELEASE(pRegistry);
    }

    return rc;
}

/************************************************************************
 *	Method:
 *	    IHXStatistics::Update
 *	Purpose:
 *	    Notify the client to update its statistics stored in the registry
 *
 */
STDMETHODIMP 
CRealTextRenderer::UpdateStatistics()
{
    return HXR_OK;
}



STDMETHODIMP
CRealTextRenderer::AttachSite(IHXSite* /*IN*/ pSite)
{
    HX_ASSERT(!m_pMISUSSite);
    if (m_pMISUSSite)
	return HXR_UNEXPECTED;
    m_pMISUSSite = pSite;

    HX_ASSERT(m_pMISUSSite);
    if (!m_pMISUSSite)
	return HXR_UNEXPECTED;
    m_pMISUSSite->AddRef();

    IHXInterruptSafe* pIHXInterruptSafe = NULL;
    if (HXR_OK == m_pMISUSSite->QueryInterface(IID_IHXInterruptSafe,
	    (void**)&pIHXInterruptSafe))
    {
	// Get the pref to use the optimized scheduler or not
	BOOL bUseOptimized = TRUE;
	IHXBuffer* pPrefBuffer;

	if (m_pPreferences->ReadPref("UseOptimizedScheduler",
		pPrefBuffer) == HXR_OK)
	{
	    bUseOptimized = *(pPrefBuffer->GetBuffer()) == '1';
	    HX_RELEASE(pPrefBuffer);
	}

	if (pIHXInterruptSafe->IsInterruptSafe() && bUseOptimized)
	{
	    HX_RELEASE(m_pOptimizedScheduler);
	    if (HXR_OK !=
		    m_pContext->QueryInterface(IID_IHXOptimizedScheduler,
		    (void **) &m_pOptimizedScheduler))
	    {
		// just for good luck
		m_pOptimizedScheduler = NULL;
	    }
	}
    }
    HX_RELEASE(pIHXInterruptSafe);

    HXxSize rtxFileSpecifiedSize;
    rtxFileSpecifiedSize.cx = m_txtWin.getWidth();
    rtxFileSpecifiedSize.cy = m_txtWin.getHeight();
    m_pMISUSSite->SetSize(rtxFileSpecifiedSize);
    
    return HXR_OK;
}

STDMETHODIMP
CRealTextRenderer::DetachSite()
{
    HX_RELEASE(m_pMISUSSite);

    // We're done with these...
    if (m_pMISUS) m_pMISUS->ReleaseSingleSiteUser();
    HX_RELEASE(m_pMISUS);

    return HXR_OK;
}

STDMETHODIMP_(BOOL)
CRealTextRenderer::NeedsWindowedSites() 
{ 
    return FALSE; 
};


STDMETHODIMP
CRealTextRenderer::HandleEvent(HXxEvent* /*IN*/ pEvent)
{
    pEvent->handled = FALSE;
    pEvent->result  = 0;

#ifdef _WINDOWS
    m_hWnd = (HWND)pEvent->window;
#endif

#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
    // XEvent* xevent = (XEvent*)pEvent->param1;
    // what is pEvent->window?
#endif

    switch (pEvent->event)
    {
	case HX_MOUSE_ENTER:
	case HX_MOUSE_LEAVE:
	case HX_MOUSE_MOVE:
	{
	    pEvent->handled = TRUE;
	    m_pEvent = pEvent;
	    HXxPoint* mousePt = (HXxPoint*) pEvent->param1;
	    OnMouseMove (0, (INT16)mousePt->x, (INT16)mousePt->y);
	}
	break;

	case HX_PRIMARY_BUTTON_UP:
	{
	    HXxPoint* mousePt = (HXxPoint*) pEvent->param1;
	    OnLbuttonUp (0, (INT16)mousePt->x, (INT16)mousePt->y);
	    pEvent->handled = TRUE;
	}
	break;

#ifdef _WINDOWS
	case WM_SETCURSOR:
	{
	    pEvent->handled = TRUE;
	    if(m_bNeedToSetHyperlinkHandCursor)
	    {
		m_hPreHyperlinkCursor = SetCursor(m_hHyperlinkCursor);
	    }
	    else
	    {
		//Fix for PR-15110; we can no longer count on
		//Hxgui to handle the setting of the cursor (back to
		// arrow cursor), so we have to do it ourselves:
		SetCursor(LoadCursor(NULL, IDC_ARROW));
	    }
	}
	break;
#endif
	case HX_SURFACE_UPDATE:
	{
	    m_pEvent = pEvent;
	    Draw();
	    pEvent->handled = TRUE;
	    m_pEvent = NULL;
	}
	break;
    }

    return HXR_OK;
}


/////////////////////////////////////////////////////////////////////////
//  Method:
//	CRealTextRenderer::Draw
//  Purpose:
//	
//
void CRealTextRenderer::Draw()
{
    // changed the following from "get[Width|Height]()"
    // to "getWindow[Width|Height]()" so that the actual valid area
    // in which to draw (which may be smaller than specified in the
    // header of an .rtx file if the metafile's width&height override it:
    //m_txtWin width,height were set in RealTextRenderer::OnHeader(), above:
    HXxSize siteWinSize;
    BOOL bSizeChanged = FALSE;

    if(m_txtWin.m_bHandlingWindowResizing)
    {
	//ignore this draw request since we're in the process of
	// re-allocating the bitmap in a not-yet-finished prior
	// CRealTextRenderer::Draw() call, below.
	return;
    }

    m_pMISUSSite->GetSize(siteWinSize);

    float widthRatioNewOverOrig = 1.0;	
    LONG32 lEffectiveCrawlRate = m_txtWin.getCrawlRate();
    float heightRatioNewOverOrig = 1.0;
    LONG32 lEffectiveScrollRate = m_txtWin.getScrollRate();

    //Since we are drawing each word at (x,y) assuming a fixed
    // rate of motion, we need to adjust the rates of motion if our
    // current visible window is different in size from our original
    // size, in other words we need to transform the rates of motion
    // if we have transformed the coordinate system:
    if(lEffectiveCrawlRate  &&  (m_txtWin.getWidth() != siteWinSize.cx)  &&
	    m_txtWin.getWidth()!=0)
    {
	widthRatioNewOverOrig = (float)siteWinSize.cx /
		(float)m_txtWin.getWidth();
	lEffectiveCrawlRate = LONG32((float)lEffectiveCrawlRate *
		widthRatioNewOverOrig);
    }
    if(lEffectiveScrollRate  &&  (m_txtWin.getHeight() != siteWinSize.cy)  &&
	    m_txtWin.getHeight()!=0)
    {
	heightRatioNewOverOrig = (float)siteWinSize.cy /
		(float)m_txtWin.getHeight();
	lEffectiveScrollRate = LONG32((float)lEffectiveScrollRate *
		heightRatioNewOverOrig);
    }

    if (m_lastKnownSize.cx != siteWinSize.cx ||
	m_lastKnownSize.cy != siteWinSize.cy)
    {
	bSizeChanged = TRUE;

	// /This if() block helps fix PR 59951.  If this is plain text from
	// in-line data in a SMIL file, then we don't want to scale the text
	// when the window resizes.  Instead, change where wordwraps happen
	// to accommodate the new window dimensions:
	// /(Now, we do same for .txt streams):
	if (RealTextRenderer::isPlainText()  &&
		(m_txtWin.getWidth() != siteWinSize.cx  ||
		m_txtWin.getHeight() != siteWinSize.cy) )
	{
	    m_txtWin.overrideDefaultWindowWidth(siteWinSize.cx);
	    m_txtWin.overrideDefaultWindowHeight(siteWinSize.cy);
	}

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile)
	{
	    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	    {
		fprintf(m_logfile,"\nCRealTextRenderer::Draw()\n"
			"    SiteWinSize changed from %d*%d to %d*%d\n",
			m_lastKnownSize.cx, m_lastKnownSize.cy,
			siteWinSize.cx, siteWinSize.cy);
	    }
	    fflush(m_logfile);
	}
#endif
#endif
    }

    LONG32 lXAdditionalRoomForMotion = 0L;
    LONG32 lYAdditionalRoomForMotion = 0L;
#if defined(DO_CPU_FRIENDLY_SCROLLING) /* { */
    lXAdditionalRoomForMotion =
	    //XXXEH- no neg crawlRate handled yet...
	    (lEffectiveCrawlRate <= 0? 0 : siteWinSize.cx/2);
    //LONG32-align it:
    LONG32 lRemainder = 4L -
	    ((siteWinSize.cx + lXAdditionalRoomForMotion) % 4L);
    if(4L == lRemainder)
    {
		lRemainder = 0L;
    }
    lXAdditionalRoomForMotion += lRemainder;
    lYAdditionalRoomForMotion =
	    //XXXEH- no neg scrollRate handled yet...
	    (lEffectiveScrollRate <= 0? 0 : siteWinSize.cy/2);
    //LONG32-align it:
    lRemainder = 4L -
	    ((siteWinSize.cy + lYAdditionalRoomForMotion) % 4L);
    if(4L == lRemainder)
    {
	lRemainder = 0L;
    }
    lYAdditionalRoomForMotion += lRemainder;
#endif /* } */
 
 
    // Following if/else/else block (one section each for windows,
    // unix and mac) contains code which allocates the offscreen
    // storage which we draw into in OnPaint(). 

    /* begin offscreen storage allocation code { */
#if defined(_WINDOWS) /* { */
    if (bSizeChanged)
    {
	if(m_lastKnownSize.cx != 0  &&  m_lastKnownSize.cy != 0)
	{
	    m_txtWin.m_bHandlingWindowResizing = TRUE;
	}
	m_lastKnownSize = siteWinSize;

#if !defined(USE_DIB_SECTION) /* { */
	if(m_txtWin.m_pBmpCompat)
	{
//	    OutputDebugString("cleaning up offscreen bitmap\r\n");
	    DeleteObject((HBITMAP)m_txtWin.m_pBmpCompat);
	    m_txtWin.m_pBmpCompat = NULL;
	}
#else //USE_DIB_SECTION: /* }{ */
	// Need to make new bitmap if window was resized;
	// first, deselect it from the DC:
	if(m_txtWin.m_hOldBitmap  &&  m_txtWin.m_pDeviceContextMemory)
	{
	    SelectObject((HDC)m_txtWin.m_pDeviceContextMemory,
		    m_txtWin.m_hOldBitmap);
	}
	if(m_txtWin.m_hBitmap)
	{
	    HBITMAP hTmpBmp = m_txtWin.m_hBitmap;
	    m_txtWin.m_hBitmap = NULL;
	    DeleteObject(hTmpBmp);
	}
#endif /* } */
    } // if (bSizeChanged)


#if !defined(USE_DIB_SECTION) /* { */
    if(!m_txtWin.m_pBmpCompat)
#else  //USE_DIB_SECTION: /* }{ */
    if(!m_txtWin.m_hBitmap)
#endif /* } */
    {
	m_txtWin.setVisibleWindowWidth(siteWinSize.cx);
	m_txtWin.setVisibleWindowHeight(siteWinSize.cy);

#if !defined(USE_DIB_SECTION) /* { */
	HDC tmpHdc = GetDC(NULL); //get Desktop window DC
	if(tmpHdc)
#else /* }{ */
	if(m_txtWin.m_pDeviceContextMemory)
#endif //!defined(USE_DIB_SECTION). /* } */
	{
#if defined(DO_CPU_FRIENDLY_SCROLLING) /* { */
	    lXAdditionalRoomForMotion =
		    //XXXEH- no neg crawlRate handled yet...
		    (lEffectiveCrawlRate <= 0? 0 : siteWinSize.cx/2);
	    //LONG32-align it:
	    LONG32 lRemainder = 4L -
		    ((siteWinSize.cx + lXAdditionalRoomForMotion) % 4L);
	    if(4L == lRemainder)
		lRemainder = 0L;
	    lXAdditionalRoomForMotion += lRemainder;
	    lYAdditionalRoomForMotion =
		    //XXXEH- no neg scrollRate handled yet...
		    (lEffectiveScrollRate <= 0? 0 : siteWinSize.cy/2);
	    //LONG32-align it:
	    lRemainder = 4L -
		    ((siteWinSize.cy + lYAdditionalRoomForMotion) % 4L);
	    if(4L == lRemainder)
		lRemainder = 0L;
	    lYAdditionalRoomForMotion += lRemainder;
#endif /* } */

	    //Add to size if window has set motion; when we do moving-window-
	    // scrolling we want to have some pre-drawn just-moving-into-the-
	    // window text to scroll:
#if !defined(USE_DIB_SECTION) /* { */
	    m_txtWin.m_pBmpCompat = (void*)CreateCompatibleBitmap(
		    tmpHdc, siteWinSize.cx + lXAdditionalRoomForMotion,
		    siteWinSize.cy + lYAdditionalRoomForMotion);
	    ReleaseDC(NULL, tmpHdc);
	    if(!m_txtWin.m_pBmpCompat)
	    {
		return;
	    }
#else //USE_DIB_SECTION: /* }{ */
 	    m_txtWin.m_BITMAPINFOHEADER.biSize	= sizeof(BITMAPINFOHEADER);
 	    m_txtWin.m_BITMAPINFOHEADER.biWidth		=
		    siteWinSize.cx + lXAdditionalRoomForMotion;
	    m_txtWin.m_BITMAPINFOHEADER.biHeight	=
		    siteWinSize.cy + lYAdditionalRoomForMotion;
	    m_txtWin.m_BITMAPINFOHEADER.biPlanes	= 1;  //Must be 1.
#if defined(USE_16_BIT_COLOR_DEPTH)
	    m_txtWin.m_BITMAPINFOHEADER.biBitCount	= 16; //Bit depth.
#else
	    // /USE 32-bit-depth for ARGB transparencey support (SMIL 2.0):
	    m_txtWin.m_BITMAPINFOHEADER.biBitCount	= 32; //Bit depth.
#endif
	    m_txtWin.m_BITMAPINFOHEADER.biCompression= BI_RGB;
	    m_txtWin.m_BITMAPINFOHEADER.biSizeImage =//size of image (bytes):
		    (siteWinSize.cx + lXAdditionalRoomForMotion) *
		    (siteWinSize.cy + lYAdditionalRoomForMotion) *
		    (m_txtWin.m_BITMAPINFOHEADER.biBitCount/8);
	    m_txtWin.m_BITMAPINFOHEADER.biClrUsed	= 0;
	    m_txtWin.m_BITMAPINFOHEADER.biClrImportant= 0;
	    m_txtWin.m_BITMAPINFOHEADER.biXPelsPerMeter = 0;
	    m_txtWin.m_BITMAPINFOHEADER.biYPelsPerMeter = 0;

	    //Allocate space for a BITMAPINFOHEADER and the 3 color masks :
	    if (NULL == m_txtWin.m_LPBITMAPINFO)
	    {
		m_txtWin.AllocNewLPBITMAPINFO(sizeof(BITMAPINFOHEADER) +
			4 * sizeof(ULONG32));
		if(!m_txtWin.m_LPBITMAPINFO)
		{
		    return;  //malloc failed.
		}
		memset(m_txtWin.m_LPBITMAPINFO, 0, sizeof(BITMAPINFOHEADER) +
			4 * sizeof(ULONG32));
#if defined(USE_16_BIT_COLOR_DEPTH)
		ULONG32* colorMasksPtr = //For RGB 565 format:
			(ULONG32*)m_txtWin.m_LPBITMAPINFO->bmiColors;		
		colorMasksPtr[0] = 0x0000f800; //5 bits for red.
		colorMasksPtr[1] = 0x000007e0; //6 bits for green.
		colorMasksPtr[2] = 0x0000001f; //5 bits for blue.
#else
		ULONG32* colorMasksPtr = //For RGB 888 format:
			(ULONG32*)m_txtWin.m_LPBITMAPINFO->bmiColors;		
		colorMasksPtr[0] = 0x00ff0000; //8 bits for red.
		colorMasksPtr[1] = 0x0000ff00; //8 bits for green.
		colorMasksPtr[2] = 0x000000ff; //8 bits for blue.
#endif
	    }

	    //Make sure we're pointing at the new info (using the default
	    // assignment operator since there are no ptrs in
	    // BITMAPINFOHEADER struct):
	    m_txtWin.m_LPBITMAPINFO->bmiHeader = m_txtWin.m_BITMAPINFOHEADER;

	    m_txtWin.m_hBitmap =
		    ::CreateDIBSection(NULL,m_txtWin.m_LPBITMAPINFO,
		    DIB_RGB_COLORS, (void**)&m_txtWin.m_pBits, NULL, 0);
	    if(!m_txtWin.m_hBitmap)
	    {
		return; //resource alloc error.
	    }

	    m_txtWin.m_hOldBitmap =
		    (HBITMAP)(
		    ::SelectObject((HDC)m_txtWin.m_pDeviceContextMemory,
		    m_txtWin.m_hBitmap));

	    m_txtWin.m_bHandlingWindowResizing = FALSE;

	    if(!m_txtWin.m_hOldBitmap)
	    {
		return; //resource alloc error.
	    }
#endif /* #else USE_DIB_SECTION is defined. */ /* } */
	}
    }

#elif defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
    if (bSizeChanged)
    {
	if(m_lastKnownSize.cx != 0  &&  m_lastKnownSize.cy != 0)
	{
	    m_txtWin.m_bHandlingWindowResizing = TRUE;
	}
	m_lastKnownSize = siteWinSize;

        // this will force a reallocation below
	if (m_Pixmap != 0)
	{
	    XFreePixmap(m_pPixmapDisplay, m_Pixmap);
	    m_pPixmapDisplay = NULL;
	    m_Pixmap = 0;
	}
    }

    if (!m_Pixmap)
    {
	m_txtWin.setVisibleWindowWidth(siteWinSize.cx);
	m_txtWin.setVisibleWindowHeight(siteWinSize.cy);

        if(siteWinSize.cx <= 0 || siteWinSize.cy <= 0)
        {
            /* It is invalid to create a pixmap of 0 size */
            return;
        }
        
	// friendly scrolling code goes here
#if defined(DO_CPU_FRIENDLY_SCROLLING) /* { */
#error cpu friendly scrolling not implemented for unix
#endif /* } */
	// TODO: handle deleting on actual resize

	// use the pointer to the X display and window
	// conveniently given us by CHXSiteWindowed::HandleEvent
	HXxWindow *pWindow = (HXxWindow*)m_pEvent->param2;
	m_pPixmapDisplay = (Display*)(pWindow->display);
	Window window = (Window)m_pEvent->window; 

	// init colorInfo struct
	if (m_lastWindow != window)
	{
	  m_lastWindow = window;
	  // get visual & set m_depth
	  XWindowAttributes attr;
	  XLockDisplay(m_pPixmapDisplay);
	  XGetWindowAttributes(m_pPixmapDisplay, window, &attr);
	  XUnlockDisplay(m_pPixmapDisplay);

	  // get visual info & depth 
	  XVisualInfo visInfoMask;
	  memset(&visInfoMask, 0, sizeof(XVisualInfo));
	  visInfoMask.visualid = attr.visual->visualid;

	  if (m_pVisualInfo)
	  {
	      XFree(m_pVisualInfo);
	      m_pVisualInfo = NULL;
	  }

	  int nv;
	  XLockDisplay(m_pPixmapDisplay);
	  m_pVisualInfo = XGetVisualInfo(m_pPixmapDisplay, VisualIDMask, &visInfoMask, &nv);

	  // get bits per pixel information for the best depth we can display
	  int i, n;
	  XPixmapFormatValues *pixmap_formats = XListPixmapFormats(m_pPixmapDisplay, &n);
	  XUnlockDisplay(m_pPixmapDisplay);
	  if (pixmap_formats) 
	  {
	      for (i = 0; i < n; i++)
	      {
		  if (pixmap_formats[i].depth == m_pVisualInfo->depth)
		  {
		      m_nBitsPerPixel = pixmap_formats[i].bits_per_pixel;
		  }
	      }
	  }
	  XFree(pixmap_formats);
	} 

	m_BitmapInfoHeader.biSize = sizeof(HXBitmapInfoHeader);  
	m_BitmapInfoHeader.biWidth = 
		siteWinSize.cx + lXAdditionalRoomForMotion;
	m_BitmapInfoHeader.biHeight =
		siteWinSize.cy + lYAdditionalRoomForMotion;
	m_BitmapInfoHeader.biPlanes = 1; // just seems to be the convention  

	m_BitmapInfoHeader.biBitCount = m_nBitsPerPixel;
	HX_ASSERT(m_BitmapInfoHeader.biBitCount % 8 == 0);

	// BI_RGB just seems to be the convention 
	switch (m_BitmapInfoHeader.biBitCount) 
	{
	    case 8:
	    case 24:
	    case 32:
	    	m_BitmapInfoHeader.biCompression = BI_RGB;
		break;
	    case 16:
	        m_BitmapInfoHeader.biCompression = HXCOLOR_RGB565_ID;
		break;
	    default:
	    	HX_ASSERT(FALSE);
	}
	m_BitmapInfoHeader.biSizeImage = //size of image (bytes):
		m_BitmapInfoHeader.biWidth * m_BitmapInfoHeader.biHeight *  
		m_BitmapInfoHeader.biBitCount;
	m_BitmapInfoHeader.biXPelsPerMeter = 0;   
	m_BitmapInfoHeader.biYPelsPerMeter = 0;   
	m_BitmapInfoHeader.biClrUsed = 0;  
	m_BitmapInfoHeader.biClrImportant = 0;  
	m_BitmapInfoHeader.rcolor = m_pVisualInfo->red_mask; 
	m_BitmapInfoHeader.gcolor = m_pVisualInfo->green_mask;   
	m_BitmapInfoHeader.bcolor = m_pVisualInfo->blue_mask; 

	XLockDisplay(m_pPixmapDisplay);
	m_Pixmap = XCreatePixmap(m_pPixmapDisplay, window,
		m_BitmapInfoHeader.biWidth, m_BitmapInfoHeader.biHeight, m_pVisualInfo->depth);
	XUnlockDisplay(m_pPixmapDisplay);

	m_txtWin.m_bHandlingWindowResizing = FALSE;
    
    } // if (!m_Pixmap)

#elif defined(_MACINTOSH) || defined(_MAC_UNIX) /* }{ */

    if (bSizeChanged)
    {
	if(m_lastKnownSize.cx != 0  &&  m_lastKnownSize.cy != 0)
	{
	    m_txtWin.m_bHandlingWindowResizing = TRUE;
	    DisposeGWorld(m_txtWin.m_pOffScreenWorld);
	    m_txtWin.m_pOffScreenWorld=NULL;
	}
	m_lastKnownSize = siteWinSize;
    }

    if(!m_txtWin.m_pOffScreenWorld)
    {
	m_txtWin.setVisibleWindowWidth(siteWinSize.cx);
	m_txtWin.setVisibleWindowHeight(siteWinSize.cy);
	//
	//	Initialize the Macintosh OffScreen Port.
	//
	// /NOTE: a raw Mac Rect struct is: (top, left, right, bottom),
	// i.e., (y,x,y,x) not (x,y,x,y):
	Rect tmpRect={0,0, siteWinSize.cy + lYAdditionalRoomForMotion,
		siteWinSize.cx + lXAdditionalRoomForMotion};

	if (m_txtWin.m_pOffScreenWorld)
	{
	    DisposeGWorld(m_txtWin.m_pOffScreenWorld);
	    m_txtWin.m_pOffScreenWorld=NULL;
	}

	OSErr err=NewGWorld(
		&m_txtWin.m_pOffScreenWorld,32,&tmpRect,0,NULL,NULL);

	m_txtWin.m_bHandlingWindowResizing = FALSE; //we're done creating it.

	if (err)
	{
	    return;
	}
    }

#endif // defined(_MACINTOSH) /* } */
    /* } end offscreen storage allocation code */

#if defined(DO_CPU_FRIENDLY_SCROLLING) /* { */
    INT16 xScrollWindowAmt = 0;
    INT16 yScrollWindowAmt = 0;
    BOOL bDoManualDraw = FALSE;
#endif /* } */

    if (m_pEvent == NULL ||
        m_pEvent->event != HX_SURFACE_UPDATE ||
	m_pEvent->result != HXR_OK)
    {
	return;
    }

    { //Keep indent so the diff won't show ALL the following as line changes:
#if defined(_WINDOWS) /* { */
	PAINTSTRUCT ps;
	ps.rcPaint.left = 0;
	ps.rcPaint.top = 0;
	ps.rcPaint.right = m_txtWin.getWindowWidth();
	ps.rcPaint.bottom = m_txtWin.getWindowHeight();
#endif /* } */
	BOOL bXorYScrollAmtExceededAllowedAmt = FALSE;

	// set up OnPaint's void arguments
	void *pvArg1 = (void *)m_pEvent; // same on all platforms
#ifdef _WINDOWS /* { */
	void *pvArg2 = (void *)&ps;
#elif defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
	void *pvArg2 = (void *)&m_Pixmap;
#else
	void *pvArg2 = (void *)NULL;
#endif /* } */

	// /Moved this, & the if() that follows, out of the #ifdef code, below
	// since MAC, UNIX, and WIN all need it under all draw-#defined vals:
	ULONG32 ulTimeToUseForOnPaint = m_ulTimeOfCurrentDraw;
	// /For PR 59951: if this is plain text, then we won't get time syncs
	// after ~100msec, so time of cur draw will remain low and thus
	// later-timed text (due to resizing/reflowing) will not get drawn
	// but it should; all text is always valid, time-wise in plain text;
	if (RealTextRenderer::isPlainText())
	{
	    ulTimeToUseForOnPaint = MAX_ALLOWED_TIME_DIFFERENCE_IN_MSEC;
	    // /Now, make sure text gets redrawn in case this isn't a window-
	    // resize draw and we're at a time that's after our duration; in
	    // that case, we will not get an OnTimeSync() after re-formatting
	    // the text, so only the background draws unless we explicitly say
	    // that this text needs drawing (but note: it would be much more
	    // efficient if the core didn't give us re-size draw messages if
	    // the resizing didn't make our size different (e.g, went from
	    // (100.0, 100.0) to (100.99, 100.99) ):
	    if (!bSizeChanged)
	    {
		LISTPOSITION pos = m_pTextWindow->GetStartPosition();
		while (pos)
		{
		    TextContainer* pTC =
			    (TextContainer*)m_pTextWindow->GetAt(pos);
		    if (pTC)
		    {
			pTC->textShouldBeDrawn(TRUE);
		    }
    		    m_pTextWindow->GetNext(pos);
		}
	    }
	}

#if defined(DO_CPU_FRIENDLY_SCROLLING) /* { */
	if(m_ulTimeOfPriorDraw != TIME_INVALID  &&
		m_ulTimeOfCurrentDraw != TIME_INVALID
		&&  m_ulTimeOfLastNonScrollWindowDraw != TIME_INVALID
		)
	{
#if defined(SCROLLINGVERSION_SCROLLDC) /* { */
	    xScrollWindowAmt = (INT16)((lEffectiveCrawlRate *
		    (LONG32)(m_ulTimeOfCurrentDraw - m_ulTimeOfPriorDraw)) /
		    1000);
	    yScrollWindowAmt = (INT16)((lEffectiveScrollRate *
		    (LONG32)(m_ulTimeOfCurrentDraw - m_ulTimeOfPriorDraw)) /
		    1000);
#elif defined(SCROLLINGVERSION_SCROLL_BY_MOVING_WINDOW) /* }{ */
	    xScrollWindowAmt = (INT16)((lEffectiveCrawlRate *
		    (LONG32)(m_ulTimeOfCurrentDraw - m_ulTimeOfLastNonScrollWindowDraw)) /
		    1000);
	    yScrollWindowAmt = (INT16)((lEffectiveScrollRate *
		    (LONG32)(m_ulTimeOfCurrentDraw - m_ulTimeOfLastNonScrollWindowDraw))/
		    1000);
	    //Don't scroll more than extra that's available off screen,
	    // which is width/2:
	    if(xScrollWindowAmt > m_txtWin.getWindowWidth()/2)
	    {
		bXorYScrollAmtExceededAllowedAmt = TRUE;
		xScrollWindowAmt = 0;
	    }
	    //Don't scroll more than extra that's available off screen,
	    // which is height/2:
	    if(yScrollWindowAmt > m_txtWin.getWindowHeight()/2)
	    {
		bXorYScrollAmtExceededAllowedAmt = TRUE;
		yScrollWindowAmt = 0;
	    }
#endif /* SCROLLINGVERSION_...  */ /* } */
	}
#if defined(_WINDOWS) /* { */
	if(lEffectiveCrawlRate > 0)
	{
	    ps.rcPaint.right += m_txtWin.getWindowWidth()/2;
	}
	if(lEffectiveScrollRate > 0)
	{
	    ps.rcPaint.bottom += m_txtWin.getWindowHeight()/2;
	}
#endif //_WINDOWS /* } */

	//Fixes seeking to very end of SMIL-frozen stream and m_PlayState never
	// getting reset from "Seeking" because draw thread (SchedulerCallback)
	// never gets called again to do so (in this case) so repaint never
	// gets done:
	if(m_ulTimeOfCurrentDraw >= m_ulDuration + m_lTimeOffset  &&
		//We don't want to mess with seek-less, live,
		// wallclock-time-based sources:
		!m_txtWin.isLiveSource()  &&
		//Fixes problem where we were drawing the last frame if
		// we seek to a spot prior to the stream's begin; the
		// time of current draw would equal the duration+offset
		m_ulCurFrameTime >= m_ulDuration + m_lTimeOffset)
	{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	    if(m_logfile  &&  m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	    {
		fprintf(m_logfile,"    ::Draw(): "
		    "m_ulTimeOfCurrentDraw (%lu) [and m_ulCurFrameTime (%lu)]"
		    " >= m_ulDuration (%lu) + m_lTimeOffset (%lu). "
		    " Setting m_PlayState to Stopped\n",
		    m_ulTimeOfCurrentDraw,
		    m_ulCurFrameTime,
		    m_ulDuration,
		    m_lTimeOffset);
		fflush(m_logfile);
	    }
#endif
#endif

	    m_PlayState = Stopped;
	    //FIxes seeking past the duration of the stream and redraw was
	    // being done with the pre-seek offscreen buffer contents instead
	    // of the text that is visible at the duration.  Note: in initial
	    // Gold (and 1st patch release) the system does not give the file
	    // format the opportunity to send the data that's visible at the
	    // end of the stream, but, in most cases, most if not all of this
	    // final text has been sent when the seek occurs:
	    if(TIME_INVALID == m_ulTimeOfLastNonScrollWindowDraw)
	    {
		bDoManualDraw = TRUE;
		m_ulTimeOfLastNonScrollWindowDraw= m_ulTimeOfCurrentDraw;
		//Force re-calc of location of text that's moving:
		if(m_txtWin.getCrawlRate()!=0  || m_txtWin.getScrollRate()!=0)
		{
		    OnTimeSynch(m_ulDuration + m_lTimeOffset);
		}
	    }
	}

	//Force a manual (non-ScrollWindow) draw after each loop occurs:
	if(m_bHorizontalLoopJustOccurred)
	{
	    m_ulTimeOfLastNonScrollWindowDraw = m_ulTimeOfCurrentDraw;
	}
	//Force a manual (non-ScrollWindow) draw if we exceeded the
	// amount of window-moving we can do:
	if(bXorYScrollAmtExceededAllowedAmt)
	{
	    bDoManualDraw = TRUE;
	    m_ulTimeOfLastNonScrollWindowDraw = m_ulTimeOfCurrentDraw;
	}
	//Force a manual (non-ScrollWindow) draw every so often:
	else if(!SomeonesBeginOrEndTimeWasCrossedOverSinceLastDraw()  &&
		(xScrollWindowAmt>0  ||  yScrollWindowAmt>0))
	{
	    BOOL bTimeOfCurDrawIsMoreRecent =
		    IsTimeAMoreRecentThanTimeB(
		    m_ulTimeOfCurrentDraw,
		    m_ulTimeOfLastNonScrollWindowDraw,
		    m_txtWin.isLiveSource() );
	    if(TIME_INVALID == m_ulTimeOfCurrentDraw  &&
		    TIME_INVALID != m_ulTimeOfLastNonScrollWindowDraw)
	    {
		//sanity check:
		HX_ASSERT(bTimeOfCurDrawIsMoreRecent);
	    }
	    if(bTimeOfCurDrawIsMoreRecent)
	    {
		if(m_ulTimeOfCurrentDraw-m_ulTimeOfLastNonScrollWindowDraw >
			MAX_MSEC_BETWEEN_NON_SCROLLWINDOW_DRAWS)
		{
		    bDoManualDraw = TRUE;
		    m_ulTimeOfLastNonScrollWindowDraw= m_ulTimeOfCurrentDraw;
		}
	    }
	}
	else
	{
	    m_ulTimeOfLastNonScrollWindowDraw = m_ulTimeOfCurrentDraw;
	}

	//The following is used in OnTimeSync() to help determine when
	// to skip drawing based on the time taken by the prior draw,
	// but we don't want to skip if the prior draw took an unusually
	// long time because it was a "manual draw", i.e., a full-window
	// redraw:
	m_bLastDrawWasAFullWindowRefresh = bDoManualDraw  ||
		(0==xScrollWindowAmt  &&  0==yScrollWindowAmt);

	LOG((m_logfile,"\nCRealTextRenderer::Draw():\n"
		"    (PlayState==%d) %scalling OnPaint( , , , %lu, %d, "
		"crawl:%d, scroll:%d)\n",
		m_PlayState,
		(Seeking != m_PlayState? "" : "PlayState==Seeking; not "),
		m_ulTimeOfCurrentDraw,
		(bSizeChanged  ||  bDoManualDraw  ||
		    m_bHorizontalLoopJustOccurred)?TRUE:
		 SomeonesBeginOrEndTimeWasCrossedOverSinceLastDraw(),
			xScrollWindowAmt, yScrollWindowAmt));

	//Make sure we were not in here already right before a seek on the
	// other thread, and before the seek finished:
	if (Seeking != m_PlayState  ||
		// /Fixes problem where seeking backwards from far beyond
		// duration to less beyond duration leaves us in Seeking
		// playstate and we weren't drawing on subsequent resizes:
		(!m_bInSeekMode  &&  RealTextRenderer::isPlainText()) )
	{
	    BOOL bOnPaintRetVal = FALSE;

/* XXXEH- need UNIX version of DrawPlainText()!: */
#if defined(_WINDOWS)  ||  defined(_MACINTOSH) || defined(_MAC_UNIX) 
	    if (!RealTextRenderer::isPlainText())
#endif /* _WINDOWS  ||  _MACINTOSH.  */
	    {    // /Is RealText (not plain text):
		bOnPaintRetVal = OnPaint(&m_txtWin,
			pvArg1, pvArg2,
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
			&m_BitmapInfoHeader,
#endif  /* _UNIX */
			ulTimeToUseForOnPaint,
			(bSizeChanged  ||  bDoManualDraw  ||
				m_bHorizontalLoopJustOccurred)? TRUE :
			SomeonesBeginOrEndTimeWasCrossedOverSinceLastDraw(),
			xScrollWindowAmt, yScrollWindowAmt);
		LOG((m_logfile,"OnPaint() returned %sE\n",
			bOnPaintRetVal?"TRU":"FALS"));
	    }
/* XXXEH- need UNIX version of DrawPlainText()!: */
#if defined(_WINDOWS)  ||  defined(_MACINTOSH)  || defined(_MAC_UNIX)
 	    else // /is plain text:
	    {
		bOnPaintRetVal = DrawPlainText(&m_txtWin,
			pvArg1, pvArg2,
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
			&m_BitmapInfoHeader,
#endif  /* _UNIX */
			ulTimeToUseForOnPaint);
			
		LOG((m_logfile,"DrawPlainText() returned %sE\n",
			bOnPaintRetVal?"TRU":"FALS"));
	    }
#endif /* _WINDOWS  ||  _MACINTOSH.  */

	}

#else /* if !defined(DO_CPU_FRIENDLY_SCROLLING) */ /* }{ */

	// Since we get update messages even when we are stopped and
	// we need to draw then, we shouldn't check the playstate
	// here but just draw.  Fixes PR 12668 JEFFA 4/29/99
	//XXXEH-4/30/1999:
	// The real fix for 12668, I think (need to test) is to check
	// if(Seeking != m_PlayState) so that we do the following
	// code block (as we do above for CPU_FRIENDLY_SCROLLING)
	// if we're playing, stopped, and any other state but
	// seeking since we may be in separate threads some day
	// even if DO_CPU_FRIENDLY_SCROLLING is not defined. 
	// if(Playing == m_PlayState)
	{
	    BOOL bOnPaintRetVal;
            
#if defined(_WINDOWS)  ||  defined(_MACINTOSH) || defined(_MAC_UNIX) 
	    if (!RealTextRenderer::isPlainText())
#endif /* _WINDOWS  ||  _MACINTOSH.  */
	    {    // /Is RealText (not plain text):
                bOnPaintRetVal = OnPaint(
		    &m_txtWin,
		    pvArg1,
		    pvArg2,
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX))) /* { */
		    &m_BitmapInfoHeader,
#endif  /* } */
		    ulTimeToUseForOnPaint,
		    TRUE,
		    0,
		    0);
                LOG((m_logfile,"OnPaint() returned %s\n",
		    bOnPaintRetVal?"TRUE":"FALSE"));
	    }
/* XXXEH- need UNIX version of DrawPlainText()!: */
#if defined(_WINDOWS)  ||  defined(_MACINTOSH)  || defined(_MAC_UNIX)
 	    else // /is plain text:
	    {
		bOnPaintRetVal = DrawPlainText(&m_txtWin,
			pvArg1, pvArg2,
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
			&m_BitmapInfoHeader,
#endif  /* _UNIX */
			ulTimeToUseForOnPaint);
			
		LOG((m_logfile,"DrawPlainText() returned %sE\n",
			bOnPaintRetVal?"TRU":"FALS"));
	    }
#endif /* _WINDOWS  ||  _MACINTOSH.  */
	}

#endif /* !defined(DO_CPU_FRIENDLY_SCROLLING) */ /* } */
    }
    // Do the following in case we don't get a time synch between
    // draws, e.g., if the playback stops and then the rt window is
    // covered and then uncovered, we'll get a paint message but
    // no time sync associated with it, so we don't want to
    // DO_CPU_FRIENDLY_SCROLLING in this case:
    SetSomeonesBeginOrEndTimeWasCrossedOverSinceLastDraw(TRUE);

    return;
}


CHXString
CRealTextRenderer::GetURLString(TextContainer* pTextContainer)
{
    CHXString urlString = pTextContainer->getHrefBuf();

    //Check for a relative URL, convert it to absolute URL.
    // Since URL may have some protocol that is not one that
    // this RealText stream could have, we want to check for
    // "[protocol]://" (e.g., "rmac://", submitted by Chris
    // Carden), before a '?' is found, if any (because
    // "[ramhurlStuff]?rtsp://foo.bar" is possible,
    // In other words, don't just look for "rtsp://",
    // "pnm://", "http://" or "file://" as the URL's protocol
    // because others may be valid.  This fixes bug 2376.
    char* pQuestionMark = (char*)strchr((const char*)urlString, '?');
    char* pProtocolSeparator = (char*)strstr((const char*)urlString, "://");
    // /Fixes PR 37146:
    // URLs like "telnet:xyz.foo.com" were not being handled because
    // no "//" was found in them.  The RFC, however, for URLs says
    // that letter characters before a ':' are a valid protocol.  All
    // we need to do is check for ':' and verify that the characters
    // that come before it are all "letters" (a-z, A-Z).  Note: I
    // verified that this does *not* break Mac local-file URLs like
    // "Macintosh HD:foo.smi" because our player puts "http://" in
    // front of such a string anyway, i.e., you need to put
    // "file://" in front of any local-file location anyway.
    if(NULL == pProtocolSeparator  &&
	    (m_pTextWindow->getMajorContentVersion() >
	    REAL_TEXT_URL_HANDLING_FIX_PR37146_CONTENT_MAJOR_VERSION
	    ||
	    (m_pTextWindow->getMajorContentVersion() ==
	    REAL_TEXT_URL_HANDLING_FIX_PR37146_CONTENT_MAJOR_VERSION
	    &&
	    m_pTextWindow->getMinorContentVersion() >=
	    REAL_TEXT_URL_HANDLING_FIX_PR37146_CONTENT_MINOR_VERSION)
	    ) )
    {
	pProtocolSeparator = (char*)strchr((const char*)urlString, ':');
	if (pProtocolSeparator)
	{
	    const char* pUrl = (const char*)urlString;
	    char* pTmp = (char*)pUrl;
	    BOOL bAllProtocolCharsAreLetters = TRUE;
	    do
	    {
		if ( (('a'>*pTmp)  ||  ('z'<*pTmp))  &&
			(('A'>*pTmp)  ||  ('Z'<*pTmp)) )
		{
		    bAllProtocolCharsAreLetters = FALSE;
		    break;
		}
		++pTmp;
	    }while ('\0'!=*pTmp  &&  pTmp < pProtocolSeparator);
	    if (!bAllProtocolCharsAreLetters)
	    {
		pProtocolSeparator = NULL;
	    }
	}
    }

    //If "://" was not found or was found after '?' (and
    // if "mailto:" was not found at the start), then
    // there was no protocol at the start of the URL so
    // "prepend" the protocol&path to the URL:
    if ((strnicmp(urlString, "mailto:", 7) != 0) &&
	(strnicmp(urlString, "command:", 8) != 0) &&
	(NULL == pProtocolSeparator ||
	    (pProtocolSeparator > pQuestionMark  &&
	    NULL != pQuestionMark)))
    {
	urlString = m_urlPrefix + urlString;
    }

    return urlString;
}

TextContainer*
CRealTextRenderer::GetTextContainerAt(INT16 xPos, INT16 yPos)
{
    TextContainer* pTextContainer = NULL;

    LONG32 lXMouseMove = LONG32(xPos);
    LONG32 lYMouseMove = LONG32(yPos);

    //Translate the mouse move coords from stretched (x,y) into original
    // (x,y) coordinates, e.g., if we are in 2X mode, the positions of any
    // hyperlinks are at twice in x and y the position they are in the
    // TextContainer's x and y, so halve the mouseMove vals in that case:
#if !defined(_UNIX) || defined(_MAC_UNIX)
    lXMouseMove = MulDiv(lXMouseMove, m_txtWin.getWidth(),
	    m_txtWin.getWindowWidth());
    lYMouseMove = MulDiv(lYMouseMove, m_txtWin.getHeight(),
	    m_txtWin.getWindowHeight());
#else
    LONG32 lWindowWidth = m_txtWin.getWindowWidth();
    if(!lWindowWidth)
    {
	lWindowWidth = 1; //to avoid divide by 0.
    }
    LONG32 lWindowHeight = m_txtWin.getWindowHeight();
    if(!lWindowHeight)
    {
	lWindowHeight = 1; //to avoid divide by 0.
    }
    lXMouseMove = LONG32((float(lXMouseMove) * float(m_txtWin.getWidth())) /
	    float(lWindowWidth));
    lYMouseMove = LONG32((float(lYMouseMove) * float(m_txtWin.getHeight())) /
	    float(lWindowHeight));
#endif

    //Added the following translation of mouse point
    // so "layouted" text windows who are not painting at (0,0) will
    // detect mouse clicks at the proper place:
    //changed call to getUpperLeft[X|Y]() to
    // getWindowUpperLeft..() which does what getUpperLeft used to:
#if !defined(_UNIX) || defined(_MAC_UNIX)
    //XXXJR - I don't think I need this even for layout windows?
    //        Which could mean I've done layout slightly wrong, of course.
    lXMouseMove -= m_txtWin.getWindowUpperLeftX();
    lYMouseMove -= m_txtWin.getWindowUpperLeftY();
#endif

    ULONG32 listSize = m_txtWin.textCntnrListSize();
    if(listSize)
    {
	//We must adjust for where the virtual window location is due to
	// "teleprompter" adjustment in Y:
	LONG32 currentYOffsetForTeleprompter = 0L;
	if(TYPE_TELEPROMPTER == m_txtWin.getType())
	{
	    currentYOffsetForTeleprompter =
		    m_txtWin.getCurrentYOffsetForTeleprompter();
	}

	
	LISTPOSITION pos = m_txtWin.GetStartPosition();

	while(pos)
	{
	    TextContainer* curTextContainerPtr =
		    (TextContainer*)m_txtWin.GetAt(pos);

	    HX_ASSERT_VALID_PTR(curTextContainerPtr);
	    if(!curTextContainerPtr)
	    {
		m_txtWin.GetNext(pos);
		continue;
	    }

	    if(0 == curTextContainerPtr->getHrefBufLen())
	    {
		m_txtWin.GetNext(pos);
		continue;
	    }
	    

	    //Check for time-validity before checking "hit-test" -- don't
	    // want to change cursor on no-longer-or-net-yet-visible
	    // text:
	    BOOL bEndTimeIsSameOrMoreRecent =
		    IsTimeASameOrMoreRecentThanTimeB(
		    curTextContainerPtr->getEndTime(),
		    m_txtWin.getTimeOfLastTimeSync(),
		    m_txtWin.isLiveSource());
	    BOOL bBeginTimeIsSameOrLessRecent =
		    IsTimeASameOrMoreRecentThanTimeB(
		    m_txtWin.getTimeOfLastTimeSync(),
		    curTextContainerPtr->getBeginTime(),
		    m_txtWin.isLiveSource());
	    if(bEndTimeIsSameOrMoreRecent  &&
		    bBeginTimeIsSameOrLessRecent)
	    {
		//We must adjust for where the virtual window location is due
		// to looping:
		LONG32 currentXOffsetDueToLooping = 0L;
		LONG32 currentYOffsetDueToLooping = 0L;
		if(m_txtWin.isLooping())
		{
		    currentXOffsetDueToLooping =
			    m_txtWin.getCurrentXOffsetDueToLooping();
		    currentYOffsetDueToLooping =
			    m_txtWin.getCurrentYOffsetDueToLooping();
		}

		//Has an HREF buffer, so check if it is where mouse was
		// moved:
		if(	(lXMouseMove >= curTextContainerPtr->
				getXUpperLeftCorner() +
				currentXOffsetDueToLooping)
			&&
			(lXMouseMove <= curTextContainerPtr->
				getXLowerRightCorner() +
				currentXOffsetDueToLooping)
			&&
			(lYMouseMove >= curTextContainerPtr->
				getYUpperLeftCorner() +
				currentYOffsetDueToLooping +
				currentYOffsetForTeleprompter)
			&&
			(lYMouseMove <= curTextContainerPtr->
				getYLowerRightCorner() +
				currentYOffsetDueToLooping +
				currentYOffsetForTeleprompter)
		  )		
		{
		    pTextContainer = curTextContainerPtr;
		    break;
		}
	    }
	    m_txtWin.GetNext(pos);
	}
    }
    return pTextContainer;
}    

HX_RESULT CRealTextRenderer::PropertyUpdated(const char* pszStr,
					     BOOL bPropertyIsULONG32)
{
    HX_RESULT retVal = HXR_OK;

    IHXBuffer* pBufVal = NULL;

    if (!pszStr  ||  !strlen(pszStr))
    {
	retVal = HXR_UNEXPECTED;
	HX_ASSERT(pszStr);
	goto cleanup;
    }

    // /This is RRGGBB ULONG32 val:
    if (0 == strcmp("backgroundColor", pszStr))
    {
	if (bPropertyIsULONG32)
	{
	    // Get the current background color
	    UINT32 ulValue = 0x00FFFFFF;
	    if (m_pValues)
	    {
		m_pValues->GetPropertyULONG32("backgroundColor", ulValue);
	    }
	    // Break it out into r, g, and b
	    UINT8 ucRed   = (UINT8)((ulValue & 0x00FF0000) >> 16);
	    UINT8 ucGreen = (UINT8)((ulValue & 0x0000FF00) >>  8);
	    UINT8 ucBlue  = (UINT8) (ulValue & 0x000000FF);
	    // Create a buffer to hold the new string
	    char* pColorBuf = new char[32];
	    if(pColorBuf)
	    {
		// Null out the string
		memset((void*) pColorBuf, 0, 32);
		// Create a string with this hex color
		ULONG32 ulSprintfLen = sprintf(pColorBuf, "#%02X%02X%02X", /* Flawfinder: ignore */
					       ucRed, ucGreen, ucBlue);
		if (ulSprintfLen > 0  &&  ulSprintfLen < 32)
		{
		    if (!m_txtWin.setBackgroundColor(pColorBuf, ulSprintfLen))
		    {
			retVal = HXR_BAD_FORMAT;
		    }
		}
		else
		{
		    retVal = HXR_FAIL;
		}
	    }
	    else
	    {
		retVal = HXR_OUTOFMEMORY;
	    }
	    // Delete the string buffer
	    HX_VECTOR_DELETE(pColorBuf);
	}
	else // /prop that changed is the CString one:
	{
	    // /HTML-style bgcolor: name ("red") or #RRGGBB ("#7f0000"):
	    if (m_pValues)
	    {
		const char* pszVal = NULL;
		HX_RESULT hxr = m_pValues->GetPropertyCString(pszStr, pBufVal);
		if (HXR_OK == hxr  &&  pBufVal)
		{
		    retVal = HXR_FAILED;
		    pszVal = (const char*)pBufVal->GetBuffer();
		    UINT32 ulStrlenVal = (UINT32)strlen(pszVal);
		    if (pszVal  &&  ulStrlenVal > 0)
		    {
			_CHAR* pszColor = new char[ulStrlenVal+1];
			if (pszColor)
			{
			    strcpy(pszColor, pszVal); /* Flawfinder: ignore */
			    if (!m_txtWin.setBackgroundColor(pszColor, ulStrlenVal))
			    {
				retVal = HXR_BAD_FORMAT;
			    }
			}
			HX_VECTOR_DELETE(pszColor);
		    }
		}
		HX_RELEASE(pBufVal);
	    }
	}
    }
    else if (0 == strcmp("backgroundOpacity", pszStr))
    {
        // Get the background opacity
        UINT32 ulValue = 255;
        if (m_pValues)
        {
            m_pValues->GetPropertyULONG32(pszStr, ulValue);
        }
        // Set this value into the text window
        if (!m_txtWin.setBackgroundOpacity(ulValue))
        {
            retVal = HXR_FAIL;
        }
    }
    else if (0 == strcmp("mediaOpacity", pszStr))
    {
        // Get the media opacity
        UINT32 ulValue = 255;
        if (m_pValues)
        {
            m_pValues->GetPropertyULONG32(pszStr, ulValue);
        }
        // Set this value into the text window
        if (!m_txtWin.setMediaOpacity(ulValue))
        {
            retVal = HXR_FAIL;
        }
    }
    else if (0 == strcmp("chromaKey", pszStr))
    {
        // Get the media opacity
        UINT32 ulValue = 255;
        if (m_pValues)
        {
            m_pValues->GetPropertyULONG32(pszStr, ulValue);
        }
        // Set this value into the text window
        if (!m_txtWin.setChromaKey(ulValue))
        {
            retVal = HXR_FAIL;
        }
    }
    else if (0 == strcmp("chromaKeyTolerance", pszStr))
    {
        // Get the media opacity
        UINT32 ulValue = 255;
        if (m_pValues)
        {
	    // /This used to be "chromaKey" instead of "chromaKeyTolerance",
	    // so this change should fix c.k.tolerance bug(s) (unfiled?):
            m_pValues->GetPropertyULONG32(pszStr, ulValue);
        }
        // Set this value into the text window
        if (!m_txtWin.setChromaKeyTolerance(ulValue))
        {
            retVal = HXR_FAIL;
        }
    }
    else if (0 == strcmp("chromaKeyOpacity", pszStr))
    {
        // Get the media opacity
        UINT32 ulValue = 255;
        if (m_pValues)
        {
            m_pValues->GetPropertyULONG32(pszStr, ulValue);
        }
        // Set this value into the text window
        if (!m_txtWin.setChromaKeyOpacity(ulValue))
        {
            retVal = HXR_FAIL;
        }
    }
    // /Helps fix PR 59951:
    else if (0 == strcmp("regionWidth", pszStr))
    {
	if (RealTextRenderer::isPlainText())
	{
	    // /Get the width of the region it's playing to:
	    UINT32 ulValue = 0;
	    if (m_pValues)
	    {
		m_pValues->GetPropertyULONG32(pszStr, ulValue);
	    }
	    // Set this value into the text window
	    if (HXR_OK != m_txtWin.overrideDefaultWindowWidth((ULONG32)ulValue))
	    {
		retVal = HXR_FAIL;
	    }
	}
    }
    else if (0 == strcmp("regionHeight", pszStr))
    {
	if (RealTextRenderer::isPlainText())
	{
	    // /Get the width of the region it's playing to:
	    UINT32 ulValue = 0;
	    if (m_pValues)
	    {
		m_pValues->GetPropertyULONG32(pszStr, ulValue);
	    }
	    // Set this value into the text window
	    if (HXR_OK != m_txtWin.overrideDefaultWindowHeight((ULONG32)ulValue))
	    {
		retVal = HXR_FAIL;
	    }
	}
    }

    // /Plain text can accept <param> values from a SMIL file:
    if (m_pValues
#if 1 /* don't let .rt be param'd; test .rt & remove the following if OK: */
	    &&  RealTextRenderer::isPlainText()
#endif
	    )
    {
	const char* pszVal = NULL;

	BOOL bDidErrorOccur = FALSE;

        m_pValues->GetPropertyCString(pszStr, pBufVal);
	if (pBufVal)
	{
	    pszVal = (const char*)pBufVal->GetBuffer();
	}
	if (pBufVal  &&  pszVal)
	{
	    if (RealTextRenderer::isPlainText()  &&  0 == strcmp("src",pszStr))
	    {
		// /XXXEH- make sure that any change to the source also gets
		// the appropriate charset conversion (e.g., iso-8859-5 must be
		// converted to windows-1251 on Windows):
		HX_ASSERT(0  &&  "src_override_not_implemented");
	    }
	    // /Expand tabs (default = TRUE (ising tab stops (0 to ..., default = 8):
	    else if (0 == strcmp("expandTabs", pszStr))
	    {
		if (!strcmp(pszVal,"false"))
		{
		    m_txtWin.setExpandTabs(FALSE);
		}
		else if (!strcmp(pszVal,"true"))
		{
		    m_txtWin.setExpandTabs(TRUE);
		}
		// /else ignore; invalid value.
	    }
	    // /Single-line presentation, or multi-lines (default = multiple):
	    else if (0 == strcmp("wordWrap", pszStr))
	    {
		if (!strcmp(pszVal,"false"))
		{
		    m_txtWin.setWordwrap(FALSE);
		}
		else if (!strcmp(pszVal,"true"))
		{
		    m_txtWin.setWordwrap(TRUE);
		}
		// /else ignore; invalid value.
	    }
	    // /Vertical alignment used if and only if wordwrap is FALSE (default =
	    // center):
	    else if (0 == strcmp("vAlign", pszStr))
	    {
		if (!strcmp(pszVal,"top"))
		{
		    m_txtWin.setVertAlign(kVAlignTop);
		}
		else if (!strcmp(pszVal,"center"))
		{
		    m_txtWin.setVertAlign(kVAlignCenter);
		}
		else if (!strcmp(pszVal,"bottom"))
		{
		    m_txtWin.setVertAlign(kVAlignBottom);
		}
		// /else ignore; invalid value.
	    }
	    // /Vertical alignment used if and only if wordwrap is FALSE (default =
	    // center):
	    else if (0 == strcmp("hAlign", pszStr))
	    {
		if (!strcmp(pszVal,"left"))
		{
		    m_txtWin.setHorizAlign(kHAlignLeft);
		}
		else if (!strcmp(pszVal,"center"))
		{
		    m_txtWin.setHorizAlign(kHAlignCenter);
		}
		else if (!strcmp(pszVal,"right"))
		{
		    m_txtWin.setHorizAlign(kHAlignRight);
		}
		// /else ignore; invalid value.
	    }
	    // /Right-to-left reading for Hebrew and Arabic (default is TRUE).
	    // -XXXEH: make sure that doesn't force other fonts to go R to L:
	    else if (0 == strcmp("direction", pszStr))
	    {
		if (!strcmp(pszVal,"ltr"))
		{
		    m_txtWin.setRightToLeftReading(FALSE);
		}
		else if (!strcmp(pszVal,"rtl"))
		{
		    m_txtWin.setRightToLeftReading(TRUE);
		}
		// /else ignore; invalid value.
	    }
	    // /Angle of escapement (baseline angle of a line):
	    else if (0 == strcmp("escapementAngle", pszStr))
	    {
		ULONG32 ulIntegerPart = 0;
		ULONG32 ulDecimalPart = 0;
		double dAngle = m_txtWin.string_to_double((_CHAR*)pszVal,
			bDidErrorOccur, ulIntegerPart, ulDecimalPart);
		if (!bDidErrorOccur)
		{
		    m_txtWin.setPlainTextAngleOfEscapement(dAngle);
		}
	    }
	    // /Angle of character orientation:
	    else if (0 == strcmp("charAngle", pszStr))
	    {
		ULONG32 ulIntegerPart = 0;
		ULONG32 ulDecimalPart = 0;
		double dAngle = m_txtWin.string_to_double((_CHAR*)pszVal,
			bDidErrorOccur, ulIntegerPart, ulDecimalPart);
		if (!bDidErrorOccur)
		{
		    m_txtWin.setPlainTextAngleOfCharOrientation(dAngle);
		}
	    }
	    // /Charset stuff:
	    else if (0 == strcmp("charset", pszStr))
	    {
		m_txtWin.setDefaultCharsetString(pszVal);
	    }
	    // /Font stuff:
	    else if (0 == strncmp("font", pszStr, 4)  &&  pszStr[4] != '\0')
	    {
		// /Look for: fontFace, fontName, fontPtSize, fontSize,
		// fontWeight, fontStyle, fontColor, fontBackgroundColor:
		BOOL bIsForegroundColor = FALSE;
		const char* pszFontAttribStr = &pszStr[4];
		if (!strcmp("Face", pszFontAttribStr)  ||
			!strcmp("Name", pszFontAttribStr))
		{
		    // /Set base font face for all of window's text:
		    m_txtWin.setDefaultFontFaceString(pszVal);
		}
		else if (!strcmp("Size", pszFontAttribStr))
		{
		    // /Set font size for all of window's text:
		    // /"-3" to "+4":
		    UINT32 ulBasePtSz = m_txtWin.getDefaultPtSize();
		    INT32 lRelSizing = atoi(pszVal);

		    // /Allowed are: {-2,-1,+0,+1,+2,+3,+4, 1,2,3,4,5,6,7}
		    if ('+' != *pszVal  &&  '-' != *pszVal)
		    {
			// /It's just a number; adjust to -2 to +4 range:
			lRelSizing -= 3;
		    }
		    // /limit it to +0 through +4:
		    if (lRelSizing > 4)
		    {
			lRelSizing = 4;
		    }
		    // /limit it to -2 through -0:
		    if (lRelSizing < -2)
		    {
			lRelSizing = -2;
		    }
		    double dSizeFactor = 1.2;
		    double dSizeAdjust = 1.0;
		    // /Resize by 20% per size, e.g., (1.2*1.2)=144% for "+2",
		    // ((1/1.2)*(1/1.2))=69% for "-2":
		    // /XXXEH- pow(1.2, lRelSizing) would work, but then we'd
		    // need math.h & I don't know if all platforms support it:
		    if (lRelSizing < 0)
		    {
			dSizeFactor = 1.0 / dSizeFactor;
			lRelSizing = -lRelSizing;
		    }
		    for (INT32 lI = 0; lI < lRelSizing; lI++)
		    {
			dSizeAdjust *= dSizeFactor;
		    }
		    /*
		    ulBasePtSz = (ULONG32)
			    ((float)((double)ulBasePtSz) * dSizeAdjust);
		    m_txtWin.setDefaultPtSize(ulBasePtSz);
		    */
		    // /Note: this may fail if user pref overrides it:
		    m_txtWin.scaleDefaultPtSize(dSizeAdjust);
		}
		else if (!strcmp("PtSize", pszFontAttribStr))
		{
		    // /Set base point size for all of window's text.
		    // (Default is 16):
		    // /1 to ... (in pixels):
		    ULONG32 ulPtSz = m_txtWin.string_to_ULONG32(
			    (char*)pszVal, bDidErrorOccur);
		    if (!bDidErrorOccur)
		    {
			m_txtWin.setDefaultPtSize(ulPtSz);
		    }
		}
		else if ( (bIsForegroundColor =
			!strcmp("Color", pszFontAttribStr))  ||
			!strcmp("BackgroundColor", pszFontAttribStr) )
		{
		    COLORTYPE colortype_retVal;
		    BOOL bIsAValidColor = FALSE;
		    retVal = HXR_FAILED;
		    pszVal = (const char*)pBufVal->GetBuffer();
		    UINT32 ulStrlenVal = (UINT32)strlen(pszVal);
		    if (pszVal  &&  ulStrlenVal > 0)
		    {
			char* pszColor = new char[ulStrlenVal+1];
			if (pszColor)
			{
			    strcpy(pszColor, pszVal); /* Flawfinder: ignore */
			    if (ulStrlenVal >= 1  &&  '#'==pszColor[0])
			    {
				//Color was presented as "#RRGGBB" in hex:
				bIsAValidColor = 
					convertColorValStringToCOLORTYPE(
					pszColor, ulStrlenVal,
					colortype_retVal);
			    }
			    //  Returns FALSE if colorName contains an
			    //  unrecognized color:
			    else
			    {	//Color was named, e.g., "darkblue":
				bIsAValidColor = convertColorNameToCOLORTYPE(
					pszColor, ulStrlenVal,
					colortype_retVal);
			    }
			    if (bIsAValidColor)
			    {
				if (bIsForegroundColor)
				{
				    m_txtWin.setDefaultTextColor(
					    colortype_retVal);
				}
				else // /is backgroundColor:
				{
				    m_txtWin.setDefaultTextBgColor(
					    colortype_retVal);
				}
			    }
			}
			HX_VECTOR_DELETE(pszColor);
		    }
		}
		// /Look for "fontWeight" (bold-ness):
		else if (!strcmp("Weight", pszFontAttribStr))
		{
		    bDidErrorOccur = FALSE;
		    ULONG32 ulWeight = m_txtWin.getDefaultFontWeight();
		    // /First look for 100, 200, ..., 900:
		    if (*pszVal >= '1'  && *pszVal <= '9')
		    {
			ulWeight = m_txtWin.string_to_ULONG32(
				(char*)pszVal, bDidErrorOccur);
		    }
		    else if (!strcmp("normal", pszVal))
		    {
			ulWeight = 400;
		    }
		    else if (!strcmp("bold", pszVal))
		    {
			ulWeight = 700;
		    }
		    if (!bDidErrorOccur)
		    {
			m_txtWin.setDefaultFontWeight(ulWeight);
		    }
		}
		else if (!strcmp("Style", pszFontAttribStr))
		{
		    if (!strcmp("normal", pszVal))
		    {
			m_txtWin.setDefaultFontStyleIsItalic(FALSE);
		    }
		    if (!strcmp("italic", pszVal))
		    {
			m_txtWin.setDefaultFontStyleIsItalic(TRUE);
		    }
		}
		// /else ignore; invalid value.
	    }
#if XXXEH_NOT_FINISHED
	    else if (0 == strcmp("italics", ...
	    else if (0 == strcmp("underline", ...
	    "pitch" (default, fixed, variable)
#error:
#endif
	}
	HX_RELEASE(pBufVal);
    }

cleanup:
    return retVal;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	HXxSubclassingWindow::OnLbuttonUp
//  Purpose:
//	Handle mouse clicks on possibly hyper-linked text.
//
//  changed the function parameters from WORDs to INT16s
//  to be cross-platform.
//
//	fwKeys - NOT USED
void CRealTextRenderer::OnLbuttonUp(INT16 fwKeys, INT16 xPos, INT16 yPos)
{
#if defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
	//Clear the previously set cursors before leaving this window
	if(m_lastWindow)
	{
	    XLockDisplay(m_pPixmapDisplay);
	    XUndefineCursor(m_pPixmapDisplay, m_lastWindow);
	    XUnlockDisplay(m_pPixmapDisplay);
	}
#endif

    //Go through the TextContainerList and see if anybody with a valid
    // URL is located under (xPos, yPox); if so, use their URL:
    if (!m_pHyperNavigate)
    {
	return;
    }

    TextContainer* pTextContainer = GetTextContainerAt(xPos, yPos);

    if (pTextContainer)
    {
	CHXString urlString = GetURLString(pTextContainer);

	//XXXXXEH- need to handle target string here!
        m_pHyperNavigate->GoToURL(urlString,
	    (pTextContainer->getTargetOfURL() == URL_TARGET_PLAYER) ? "_player" : (const char*) NULL);
    }
}


/////////////////////////////////////////////////////////////////////////
//  Method:
//	HXxSubclassingWindow::OnMouseMove
//  Purpose:
//	Handle mouse movement onto possibly hyper-linked text.
//
//  Returns TRUE if mouse cursor is over a valid hyperlink, else FALSE.
//
//	fwKeys - NOT USED
BOOL CRealTextRenderer::OnMouseMove(INT16 fwKeys, INT16 xPos, INT16 yPos)
{

    // don't do anything if the x/y coordinates have changed from the
    // last call to OnMouseMove - this is needed because the call to
    // IHXStatusMessage::SetStatus() results in a WM_MOUSEMOVE event
    static INT16 z_nXPos = 0;
    static INT16 z_nYPos = 0;

    if(xPos == z_nXPos &&
	yPos == z_nYPos)
    {
	return FALSE;
    }
    z_nXPos = xPos;
    z_nYPos = yPos;

#if defined(_WINDOWS)
    HCURSOR hCurrentCursor = GetCursor();
#endif

    TextContainer* pTextContainer = GetTextContainerAt(xPos, yPos);
    if(pTextContainer)
    {
#if defined(_WINDOWS)

	if(!m_hHyperlinkCursor)
	{
	    m_hHyperlinkCursor = LoadCursor(g_hInstance, 
		    MAKEINTRESOURCE(HANDCURSOR));
	    if(!m_hHyperlinkCursor)
	    {
		m_hHyperlinkCursor=LoadCursor(NULL, IDC_UPARROW);
	    }
	}

	if(m_hHyperlinkCursor  &&
		hCurrentCursor != m_hHyperlinkCursor)
	{
	    //Change it; we just moved onto a hyperlink:
	    //Handle this in WM_SETCURSOR (which should arrive
	    // shortly):
	    m_bNeedToSetHyperlinkHandCursor = TRUE;
	}
	//Note: if needed, Window Handle is m_pEvent->window.
		
#elif defined(_MACINTOSH) || defined(_MAC_UNIX)
	if (m_hHyperlinkCursor)
	{
	    ::SetCursor(*m_hHyperlinkCursor);
	    m_CurrentCursor = CURSOR_HYPERLINK;
	}
#elif defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
	if (!m_hHyperlinkCursor && m_pPixmapDisplay && m_lastWindow)
	{
  	    XLockDisplay(m_pPixmapDisplay);
	    m_hHyperlinkCursor = XCreateFontCursor(m_pPixmapDisplay, 
						   XC_hand2);
	    XUnlockDisplay(m_pPixmapDisplay);
	}
	if (m_hHyperlinkCursor)
	{
	    XLockDisplay(m_pPixmapDisplay);
	    XDefineCursor(m_pPixmapDisplay, m_lastWindow, m_hHyperlinkCursor);
	    XUnlockDisplay(m_pPixmapDisplay);
	    m_bHandActivated = TRUE;
	}
#endif
	if (m_pStatusMessage)
	{
	    m_bStatusMsgWillNeedErasing = TRUE;
	    m_pStatusMessage->SetStatus(GetURLString(pTextContainer));
	}
	return TRUE;
    }
    else
    {
#if defined(_WINDOWS)
	if(hCurrentCursor == m_hHyperlinkCursor)
	{
	    //We need to change it back -- we just moved off of a hyperlink:
	    //Handle this in WM_SETCURSOR (which should arrive
	    // shortly):
	    m_bNeedToSetHyperlinkHandCursor = FALSE;
	}
#elif defined(_MACINTOSH) || defined(_MAC_UNIX)
	if (m_CurrentCursor == CURSOR_HYPERLINK)
	{
	    InitCursor();
	    m_CurrentCursor = CURSOR_ARROW;
	}
#elif defined(_UNIX) && (!(defined(_BEOS))) && (!(defined(_MAC_UNIX)))
	if (m_bHandActivated)
	{
	    XLockDisplay(m_pPixmapDisplay);
	    XUndefineCursor(m_pPixmapDisplay, m_lastWindow);
	    XUnlockDisplay(m_pPixmapDisplay);
	    m_bHandActivated = FALSE;
	}
#endif
	if (m_pStatusMessage  &&
		// /Fixes PR 65008 (RT version): only set this to NULL if we
		// have recently set the status message, otherwise we may
		// cause SMIL's setting of the status message to be
		// overwritten with NULL, i.e., erased:
		m_bStatusMsgWillNeedErasing)
	{
	    m_bStatusMsgWillNeedErasing = FALSE;
	    m_pStatusMessage->SetStatus(NULL);
	}
	return FALSE;
    }
}


//For adding the protocol & path to <A >-tag URLs that are relative
// to the rtx file's location:
void
CRealTextRenderer::generatePreFix()
{
    // get the protocol/server for later...
    IHXStreamSource* pSource = 0;
    m_pStream->GetSource(pSource);
    HX_ASSERT(pSource);
    if (!pSource)
    {
	return;
    }

    BOOL bHasHost = FALSE;
    const char* pURL = pSource->GetURL();
    HX_ASSERT(pURL);
    
    if (!pURL)
    {
	return;
    }

    if (0 == strnicmp(pURL, "data:", 5))
    {
	const char* pComma = strchr(pURL, ',');
	if (pComma  &&  *(pComma+1) != '\0'  &&
		(pComma - pURL == 5  /*== "data:,"*/ ||
		!strnicmp(pURL+5, "text/plain,", 11) ) )
	{
	    // /Helps fix PR 59951, PR 58791, and PR 58784:
	    // this is a <text src="data:..."/> from within a SMIL file, so
	    // we should handle it as plain text, ignoring what the rtffplin
	    // says the width and height and other aspects of the stream are:
	    RealTextRenderer::setSMILFileDataURLFlag(TRUE);
	}
    }

    CHXString urlRoot;
    char* pURLFragment = NULL;
    CHXURL::GeneratePrefixRootFragment(pURL, m_urlPrefix, urlRoot, pURLFragment);
    HX_VECTOR_DELETE(pURLFragment);
    HX_RELEASE(pSource);

    return;
}

HX_RESULT
CRealTextRenderer::GetCurrentMousePos(INT16& nXPos, INT16& nYPos)
{
    HX_RESULT rc = HXR_OK;

#if defined ( _WINDOWS)

    POINT   ptMouse;

    GetCursorPos(&ptMouse);

    ScreenToClient(m_hWnd,&ptMouse);

    nXPos = (INT16)ptMouse.x;
    nYPos = (INT16)ptMouse.y;

#elif defined(_MACINTOSH) || defined(_MAC_UNIX)

    Point localPt;
    GetMouse(&localPt);
    nXPos = (INT16)localPt.h;
    nYPos = (INT16)localPt.v;

#endif

    return rc;
}

BOOL
CRealTextRenderer::ShouldKickStartScheduler()
{
    return(!m_bPendingCallback && !m_bInCallback);
}

HX_RESULT
CRealTextRenderer::ScheduleCallback(UINT32 ulRelativeTime, UINT32 ulTicksTime)
{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,"\nCRealTextRenderer::ScheduleCallback()\n");
	}
	fflush(m_logfile);
    }
#endif
#endif
    // this new callback is going to take presidence over the
    // current one so remove it.
    if (m_pOptimizedScheduler != NULL || m_pScheduler != NULL)
    {
	IHXCallback* pCallback = NULL;

	if (HXR_OK == QueryInterface(IID_IHXCallback, (void**)&pCallback))
	{
#if defined(XXXEH_USE_ABSOLUTETIMECALLBACKS)
	    HXTimeval lTime;

	    // Always use the optimized scheduler if we have one
	    if (m_pOptimizedScheduler != NULL)
	    {
		lTime = m_pOptimizedScheduler->GetCurrentSchedulerTime();
	    }
	    else
	    {
		lTime = m_pScheduler->GetCurrentSchedulerTime();
	    }

	    lTime.tv_usec += CALCULATE_ELAPSED_TICKS(HX_GET_BETTERTICKCOUNT(),
		    ulTicksTime) * MILLISECOND;
	    if (lTime.tv_usec >= SECOND) {
		lTime.tv_sec += lTime.tv_usec / SECOND;
		lTime.tv_usec %= SECOND;
	    }
#else
            // /Use relative time.
            UINT32 ulRelativeCallbackTime = CALCULATE_ELAPSED_TICKS(HX_GET_BETTERTICKCOUNT(),
		    ulTicksTime);
#endif // / XXXEH_USE_ABSOLUTETIMECALLBACKS.
	    
	    // we want to do the right thing here and remove a callback
	    // before we schedule a new one but we should never have to do
	    // that, so assert if FALSE:
	    HX_ASSERT(!m_bPendingCallback);
	    if (m_bPendingCallback)
	    {
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
		if(m_logfile)
		{
		    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
		    {
			fprintf(m_logfile,
				"\tpendingHandle:%lu\tTICKCOUNT:%lu\n",
				m_PendingHandle, HX_GET_BETTERTICKCOUNT());
		    }
		    fflush(m_logfile);
		}
#endif
#endif
		// Always use the optimized scheduler if we have one
		if (m_pOptimizedScheduler != NULL)
		{
		    m_pOptimizedScheduler->Remove(m_PendingHandle);
		}
		else if (m_pScheduler)
		{
		    m_pScheduler->Remove(m_PendingHandle);
		}
	    }
	    
	    m_bPendingCallback = TRUE;

#if defined(XXXEH_USE_ABSOLUTETIMECALLBACKS)
	    // Always use the optimized scheduler if we have one
	    if (m_pOptimizedScheduler != NULL)
	    {
	        m_PendingHandle = m_pOptimizedScheduler->AbsoluteEnter(
			pCallback, lTime);
	    }
	    else
	    {
	        m_PendingHandle = m_pScheduler->AbsoluteEnter(
			pCallback, lTime);
	    }
#else
	    // Always use the optimized scheduler if we have one
	    if (m_pOptimizedScheduler != NULL)
	    {
	        m_PendingHandle = m_pOptimizedScheduler->RelativeEnter(
			pCallback, ulRelativeCallbackTime);
	    }
	    else
	    {
	        m_PendingHandle = m_pScheduler->RelativeEnter(
			pCallback, ulRelativeCallbackTime);
	    }
#endif // / XXXEH_USE_ABSOLUTETIMECALLBACKS.


#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	    if(m_logfile)
	    {
		if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
		{
		    fprintf(m_logfile,
 			    "\tpendingHandle:%lu\tTICKCOUNT:%lu"
			    "\tRelTime:%lu"
#if defined(XXXEH_USE_ABSOLUTETIMECALLBACKS)
                            "\ttv_sec:%lu\ttv_usec:%lu"
#endif // / XXXEH_USE_ABSOLUTETIMECALLBACKS.
                            "\n",
			    m_PendingHandle, HX_GET_BETTERTICKCOUNT(),
			    ulRelativeTime
#if defined(XXXEH_USE_ABSOLUTETIMECALLBACKS)
                            , lTime.tv_sec, lTime.tv_usec
#endif // / XXXEH_USE_ABSOLUTETIMECALLBACKS.
                            );
		    fflush(m_logfile);
		}
	    }
#endif
#endif
	}
	HX_RELEASE(pCallback);
    }

    return HXR_OK;
}

STDMETHODIMP
CRealTextRenderer::Func(void)
{
    m_pMutex->Lock();

    m_bInCallback = TRUE;

    HX_ASSERT(m_bPendingCallback);
	
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    HXTimeval lTime;

    if (m_pOptimizedScheduler || m_pScheduler)
    {
	// Always use the optimized scheduler if we have one
	if (m_pOptimizedScheduler != NULL)
	{
	    lTime = m_pOptimizedScheduler->GetCurrentSchedulerTime();
	}
	else
	{
	    lTime = m_pScheduler->GetCurrentSchedulerTime();
	}
    }

    if(m_logfile)
    {
	if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,
		    "CRealTextRenderer::Func():\n"
		    "\tpendingHandle:%lu\tTICKCOUNT:%lu"
		    "\t          \ttv_sec:%lu\ttv_usec:%lu\n",
		    m_PendingHandle, HX_GET_BETTERTICKCOUNT(),
		    lTime.tv_sec, lTime.tv_usec);
	}
	fflush(m_logfile);
    }
#endif
#endif

    m_PendingHandle = 0;
    m_bPendingCallback = FALSE;
    SchedulerCallback();

    m_bInCallback = FALSE;

    m_pMutex->Unlock();

    return HXR_OK;
}


ULONG32
CRealTextRenderer::GetNextFrameTime(ULONG32 ulStreamTime)
{
    //Calculate next "frame time" based on the next multiple of
    // m_ulGranularity:
    ULONG32 ulNextFrameTime = TIME_INVALID;

    ULONG32 ulStreamTimeWithLiveEncoderStartTickOffset = ulStreamTime;
    if(m_txtWin.isLiveSource())
    {
#if defined(_DEBUG)
	ulStreamTimeWithLiveEncoderStartTickOffset =
		ulStreamTime - m_ulStreamStartTime;
#endif
	//but first, adjust for possible round-off error:
	double dblFramesSoFar =
		(double)m_ulFrameRate * ((double)ulStreamTime / 1000.0);
	double diff = dblFramesSoFar - ULONG32(dblFramesSoFar);
	if(diff > .0001)
	{
	    //Not evenly divisible, so adjust up by 1:
	    dblFramesSoFar++;
	}
	ULONG32 ulFramesSoFar = (ULONG32)dblFramesSoFar;
	dblFramesSoFar = (double)ulFramesSoFar;
	ulNextFrameTime =
		(ULONG32)((dblFramesSoFar * 1000.0) / (double)m_ulFrameRate);
    }
    else //Let's not mess with non-live calculation this close to Gold:
	 //XXXXXEH- note: the only diff between the below code and the
	 // islive version, above, is in the placement of parentheses
	 // around the first line's right side (rvalue) of the '=':
    {
	//but first, adjust for possible round-off error:
	double dblFramesSoFar =
		((double)m_ulFrameRate * (double)ulStreamTime) / 1000.0;
	double diff = dblFramesSoFar - ULONG32(dblFramesSoFar);
	if(diff > .0001)
	{
	    //Not evenly divisible, so adjust up by 1:
	    dblFramesSoFar++;
	}
	ULONG32 ulFramesSoFar = (ULONG32)dblFramesSoFar;
	dblFramesSoFar = (double)ulFramesSoFar;
	ulNextFrameTime =
		(ULONG32)((dblFramesSoFar * 1000.0) / (double)m_ulFrameRate);
    }

    // (Add 1 to curFrameTime to account for possible rounding error if,
    // for instance, we're doing 30 frames per second which is
    // 33.333333... millisecond gaps, not 33 msec gaps; we want
    // OnTimeSynch() to decide which text needs to be drawn in the
    // upcoming draw based on slightly late rather than slightly early
    // time vals, e.g., use 67 instead of 66 if 66.66667 is calculated:
    if(ulNextFrameTime > 0)
    {
	ulNextFrameTime++;
    }
    if((TIME_INVALID == ulNextFrameTime  ||
	    TIME_INFINITY == ulNextFrameTime)
	    &&  m_txtWin.isLiveSource())
    {
	ulNextFrameTime=1L;
    }

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile, "CRealTextRenderer::GetNextFrameTime("
		    "ulStreamTime=%lu) returned %lu\n",
		    ulStreamTime, ulNextFrameTime);
	    fflush(m_logfile);
	}
    }
#endif
#endif

    return ulNextFrameTime;
}

void
CRealTextRenderer::SchedulerCallback()
{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile, "CRealTextRenderer::SchedulerCallback()\n");
	}
	fflush(m_logfile);
    }
#endif
#endif
    if (m_PlayState == Playing)
    {
	BOOL bRectWasDamaged = FALSE;

	if (m_bSeekJustFinished)
	{
	    if (m_PlayState != Playing)
	    {
		return;
	    }

	    /* Preseek */

	    // /Fixes PR 64996: if last <clear/> is associated with time prior
	    // to seeked-to time, then we don't want to reset the following to
	    // zero since the cur value was set from *post-seek* packet data.
	    // Before this fix, when "LC" was parsed, m_bClearWasJustSent was
	    // erroneously set to TRUE because the LC val was > zero, and this
	    // caused text from packets received *during* the seek to have
	    // their end times shortened when the next (post-seek) packet
	    // arrives with "<time LC=.../>":
	    if (m_ulLastSeekedToTime < m_txtWin.getTimeOfLastClearTag())
	    {
		m_txtWin.setTimeOfLastClearTag(0L);
	    }

	    m_txtWin.setCurrentXOffsetDueToLooping(0L);
	    m_txtWin.setCurrentYOffsetDueToLooping(0L);

	    m_txtWin.setCurrentYOffsetForTeleprompter(0L);

	    m_txtWin.setPriorLineAlreadyCentered(FALSE);

	    m_txtWin.setInsideCommentTagNestCount(0L);

	    m_bPriorPacketWasLostOrWeJustSeeked = TRUE;

	    //Do the following in case we seek to a spot where there's nothing
	    // to draw; we want to make sure to repaint the window:
	    SetSomeonesBeginOrEndTimeWasCrossedOverSinceLastDraw(TRUE);

	    m_bLastDrawWasAFullWindowRefresh = TRUE;

	    /* Postseek */

	    m_bSeekJustFinished = TRUE;

	    m_ulTimeOfPriorDraw = TIME_INVALID;
	    m_ulTimeOfCurrentDraw = TIME_INVALID;
	    m_ulTimeOfLastNonScrollWindowDraw = TIME_INVALID;

	    m_bLastDrawWasAFullWindowRefresh = TRUE;

	    m_ulLastTimeSyncWorkTime = 0L; //repaint for sure in next time sync.

	    m_bSeekJustFinished = FALSE;
	    bRectWasDamaged = TRUE;
	}

        // /This helps fix PR 100673 by "normalizing" m_ulCurFrameTime to an
        // exact frame boundary:
        m_ulCurFrameTime = (m_ulCurFrameTime / m_ulGranularity) * m_ulGranularity;

	//Check if we're ready to draw based on the rate(s) of motion,
	// if any:
	UINT32 ulStreamTime = StreamTime();
	if(m_ulCurFrameTime == ulStreamTime)
	{
	    //We don't want to repeat the draw from last time:
	    ulStreamTime++;
	    if(m_txtWin.isLiveSource()  &&
		    (0== ulStreamTime  ||  TIME_INFINITY == ulStreamTime  ||
		    TIME_INVALID == ulStreamTime))
	    {
		ulStreamTime=1L;
	    }
	}
	//"Frame" time is time that we should redraw the window.  This was
	// already calculated, in m_ulGranularity, based on the scroll
	// and/or crawl rates of the window:
	UINT32 ulNextFrameTime = GetNextFrameTime(ulStreamTime);

	//If we've already drawn for this time (or later), don't schedule
	// another draw:
//XXXXXEH- verify that this is OK to ignore this if we're live:
	if(!m_txtWin.isLiveSource()  &&
		(m_ulTimeOfCurrentDraw!=TIME_INVALID)  &&
		(m_ulTimeOfCurrentDraw >= ulNextFrameTime
/* XXXXXEH-  Rahul says the renderer will never get a time sync after its
duration except maybe the last time sync, so the following check should not
be done because we do want to draw that final time even if it exceeds the
duration, but call OnPaint() with duration, not with m_ulCurFrameTime in
that case:
		    ||
		  (m_ulCurFrameTime>m_ulDuration+1  &&
		  !m_txtWin.isLiveSource())//<-- let live streams keep going.
*/
		)
	  )
	{
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	    if(m_logfile)
	    {
		if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
		{
		    fprintf(m_logfile, "    Skipped OnTimeSynch & "
			    "callback because TimeOfCurrentDraw=%lu & "
			    "NextFrameTime=%lu (streamTime=%lu, "
			    "CurFrameTime=%lu)\n",
			    m_ulTimeOfCurrentDraw, ulNextFrameTime,
			    ulStreamTime, m_ulCurFrameTime
/*XXXXXEH-  see above.
			    ,
			    m_ulCurFrameTime>m_ulDuration+1?
			    " and duration was exceeded":""
*/
			    );
		}
		fflush(m_logfile);
	    }
#endif
#endif
	    return;
	}
    
	// Redraw our data by damaging the window.
	CHXxRect rect(
		m_txtWin.getUpperLeftX(),
		m_txtWin.getUpperLeftY(),
		(m_txtWin.getUpperLeftX()) + m_txtWin.getWindowWidth(),
		(m_txtWin.getUpperLeftY()) + m_txtWin.getWindowHeight());

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile)
	{
	    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	    {
		fprintf(m_logfile, "    Calling OnTimeSynch(%lu)\n",
		    m_ulCurFrameTime);

//XXXXXEH- extra debug stuff should be done only if specific flag bit is set:
fprintf(m_logfile, "    TimeOfPriorDraw:%lu, TimeOfCurrentDraw:%lu,"
  "CurFrameTime=%lu, NextFrameTime=%lu, streamTime=%lu\n",
  m_ulTimeOfPriorDraw, m_ulTimeOfCurrentDraw, m_ulCurFrameTime,
  ulNextFrameTime, ulStreamTime);
	    }
	    fflush(m_logfile);
	}
#endif
#endif
	//XXXEH- unfinished code; OnTimeSynch() does not invalidate only what's
	//	needed; when this is changed, pass reference to rect to OnTimeSynch()
	//	and let OnTimeSynch() fill in rect's vals:

	//Helps fix for PR 22048 and 22705; if first timeSync() is later than
	// our duration, draw anyway as if we're right at our duration:
	if (m_bIsFirstTimeSynch  &&  m_ulCurFrameTime > m_ulDuration)
	{
	    bRectWasDamaged |= RealTextRenderer::OnTimeSynch(
		    m_ulDuration);
	}
	else
	{
	    bRectWasDamaged |= RealTextRenderer::OnTimeSynch(
		    m_ulCurFrameTime);
	}

#if defined(_DEBUG)
	//Keep original state for debug output, below:
	BOOL bTmp = m_bLatePacketNotYetDrawn;
#endif

	//This is needed to fix PR 22705
	if(m_bLatePacketNotYetDrawn)
	{
	    bRectWasDamaged = TRUE;
	    m_bLatePacketNotYetDrawn = FALSE;
	}

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile)
	{
	    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	    {
		fprintf(m_logfile, "    OnTimeSynch() returned %sE; "
		    "Draw() will %soccur (m_bLatePacketNotYetDrawn=%sE)\n",
		    bRectWasDamaged?"TRU":"FALS",
		    m_pMISUSSite  &&  (bRectWasDamaged
				||  m_bSeekJustFinished) ? "":"not ",
		    	    bTmp?"TRU":"FALS"
		    );
	    }
	    fflush(m_logfile);
	}
#endif
#endif

	//Force redraw if OnTimeSynch() determined that there was something that
	// needed redrawing, e.g., text that moved or whose start or endtime had
	// just been crossed, or if we just seeked to a spot where there is no
	// text to be drawn.  I moved code for the post-seek case to here from
	// inside OnPostSeek() due to the fact that OnPostSeek() would always
	// draw a blank window before the first post-seek packet could be drawn
	// and this caused bug #2852's "flashing" behavior:
	if (m_pMISUSSite && bRectWasDamaged)
	{
	    IHXSite* pTmpMISUSSite = m_pMISUSSite;
	    pTmpMISUSSite->AddRef(); // /In case it gets deleted in other thread.
	    m_ulTimeOfPriorDraw = m_ulTimeOfCurrentDraw;
	    m_ulTimeOfCurrentDraw = m_ulCurFrameTime;//ulNextFrameTime;
	    if(m_ulTimeOfCurrentDraw > m_ulDuration+m_lTimeOffset  &&
		    !m_txtWin.isLiveSource())
	    {
		//Never draw at time greater than duration or we might scroll
		// too far...etc.:
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
		if(m_logfile)
		{
		    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
		    {
			fprintf(m_logfile, "    setting m_ulTimeOfCurrentDraw"
				" to m_ulDuration(%lu) +"
				" m_lTimeOffset(%lu)=%lu\n",
				m_ulDuration, m_lTimeOffset,
				m_ulDuration + m_lTimeOffset);
		    }
		    fflush(m_logfile);
		}
#endif
#endif
		m_ulTimeOfCurrentDraw = m_ulDuration + m_lTimeOffset;
	    }
	    pTmpMISUSSite->DamageRect(rect);
	    pTmpMISUSSite->ForceRedraw();

	    pTmpMISUSSite->Release(); // /remove extra addRef from above.
	 
	    m_bSeekJustFinished = FALSE;
	}

	m_ulCurFrameTime = ulNextFrameTime;
	
	//If we've already done a callback for this time (or later),
	// don't schedule one now:
	BOOL bTimeNotDoneAlready = TRUE;

	//Note: >= calc may fail to produce the correct answer if wallclock
	// just wrapped around in a live stream, but that's not a big deal:
	// we'll just draw the current text an extra time:
	if((m_ulTimeOfCurrentDraw!=TIME_INVALID)  &&
		m_ulTimeOfCurrentDraw >= ulNextFrameTime)
	{
	    bTimeNotDoneAlready = FALSE;
	}

	UINT32 ulNextFrameDelta = CALCULATE_ELAPSED_TICKS(ulStreamTime, 
							ulNextFrameTime);
	ULONG32 ulRightNow = HX_GET_BETTERTICKCOUNT();

#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile)
	{
	    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	    {
		fprintf(m_logfile, "    %s ScheduleCallback("
		    "delta:%lu, @time:%lu) "
		    "[streamTime:%lu, nextFrameTime:%lu]\n",
		    (ulNextFrameDelta>10  &&  bTimeNotDoneAlready?
			    "calling":
			    (bTimeNotDoneAlready?
			    "delta too small; skipping":
			    "draw already done: skipping") ),
		    ulNextFrameDelta,
		    ulRightNow + ulNextFrameDelta,
		    ulStreamTime, ulNextFrameTime);
	    }
	    fflush(m_logfile);
	}
#endif
#endif

        // /Draw even if time is right away since we may have missed drawing
        // the last chance we had.  This helps fix PR 100673:
        {
	    if(m_bEndStreamJustOccurred  ||
		    ulStreamTime > m_ulDuration + m_lTimeOffset  &&
		    !m_txtWin.isLiveSource())
	    {
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
		if(m_logfile)
		{
		    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
		    {
			fprintf(m_logfile, "\tWe're done.  Setting "
				"PlayState to Stopped and skipping "
				"ScheduleCallback()%s\n",
				m_bEndStreamJustOccurred?
				": m_bEndStreamJustOccurred=TRUE":"");
		    }
		    fflush(m_logfile);
		}
#endif
#endif
		m_bEndStreamJustOccurred = FALSE;
		m_PlayState = Stopped;
	    }
	    else
	    {
		ScheduleCallback(ulNextFrameDelta,
			ulRightNow + ulNextFrameDelta);
	    }
	}
    }
    else
    {
#ifdef _DEBUG
#if defined(RT_OUTPUT_LOGFILE)
	if(m_logfile)
	{
	    if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	    {
		fprintf(m_logfile, "\tSkipped drawing; PlayState=%d, "
			"(!=Playing)\n", m_PlayState);
	    }
	    fflush(m_logfile);
	}
#endif
#endif
    }
}


HX_RESULT
CRealTextRenderer::adjustForUserTextSizeSetting()
{
    HX_RESULT hxrslt = HXR_FAILED;

    IHXBuffer* pBufPtSz = NULL;
    if (m_pTextWindow  &&  HXR_OK == GetUserPref("font-size", pBufPtSz))
    {
	const char* pszBufPtSz = (const char*)pBufPtSz->GetBuffer();
	if (pszBufPtSz  &&  strlen(pszBufPtSz))
	{
	    hxrslt = HXR_OK;

	    ULONG32 ulBasePointSize = m_pTextWindow->getDefaultPtSize();
	    // /Relative sizes:
	    if (0 == strcmp(pszBufPtSz, "larger")  ||
		    0 == strcmp(pszBufPtSz,"smaller"))
	    {
		if ('l' == *pszBufPtSz)
		{
		    m_pTextWindow->setUserPrefRelativeTextSizing(
			    FONT_SIZEFACTOR_LARGER_PCT);
		}
		else // /"smaller"
		{
		    m_pTextWindow->setUserPrefRelativeTextSizing(
			    FONT_SIZEFACTOR_SMALLER_PCT);
		}
	    }
	    else
	    {
		BOOL bValidValue = FALSE;
		// /Absolute sizes:
		// /These are in order with the more-likely user choices first:
		if (0 == strcmp(pszBufPtSz, "medium"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_0;
		}
		else if (0 == strcmp(pszBufPtSz, "large"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_PLUS1;
		}
		else if (0 == strcmp(pszBufPtSz, "x-large"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_PLUS2;
		}
		else if (0 == strcmp(pszBufPtSz, "xx-large"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_PLUS3;
		}
		else if (0 == strcmp(pszBufPtSz, "small"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_MINUS1;
		}
		else if (0 == strcmp(pszBufPtSz, "x-small"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_MINUS2;
		}
		else if (0 == strcmp(pszBufPtSz, "xx-small"))
		{
		    bValidValue = TRUE;
		    ulBasePointSize = FONT_SIZE_MINUS3;
		}

		HX_ASSERT(bValidValue  ||  0 == strcmp(pszBufPtSz, "default"));
		if (bValidValue)
		{
		    m_pTextWindow->setUserPrefAbsoluteTextSizing(
			    ulBasePointSize);
		}
		// /else ignore bad value
	    }
	}
	HX_RELEASE(pBufPtSz);
    }

    return hxrslt;
}


// /--------------GETS A USER PREFERENCE SETTING FROM REGISTRY:--------------
// Caller must release pBuf when done:
HX_RESULT
CRealTextRenderer::GetUserPref(const char* pPrefName, IHXBuffer*& pBuf /*OUT*/)
{
    HX_RESULT hxrslt = HXR_FAILED;

    ULONG32 ulStrLenPrefName = (ULONG32)strlen(pPrefName);

    HX_ASSERT(NULL == pBuf);
    HX_ASSERT(pPrefName  &&  ulStrLenPrefName);

    if (NULL == pBuf  &&  pPrefName  &&  ulStrLenPrefName > 0)
    {
	hxrslt = HXR_OK;

	// /Look for pref in registry; if found, return the value found:
	IHXPreferences* pPrefs    = NULL;
	IHXRegistry*  pRegistry = NULL;

	// /If there is a uid attribute, then we fail unless
	// we can find a match in the registry (that's set to
	// a number that isn't 0 or a string that isn't a
	// case-insensitive version of the "false"):
	if(HXR_OK == m_pContext->QueryInterface(IID_IHXPreferences,
		(void**)&pPrefs))
	{
	    hxrslt = pPrefs->ReadPref(pPrefName, pBuf);
	    HX_RELEASE(pPrefs);
	}
    }

    return hxrslt;
}


/************************************************************************
 *	Method:
 *	    IHXUpdateProperties::UpdatePacketTimeOffset
 *	Purpose:
 *	    Call this method to update the timestamp offset of cached packets
 */
STDMETHODIMP
CRealTextRenderer::UpdatePacketTimeOffset(INT32 lTimeOffset)
{
    HX_RESULT		rc = HXR_OK;

#if defined(_DEBUG)  &&  defined (RT_OUTPUT_LOGFILE)
    if(m_logfile)
    {
	if(m_txtWin.getDebugFlags()&RT_RENDER_DEBUG_FLAGS_MASK)
	{
	    fprintf(m_logfile,
		    "CRealTextRenderer::UpdatePacketTimeOffset() "
		    "from %ld to %ld (playstate=%s)\n",
		    m_lTimeOffset, lTimeOffset,
		    Stopped==m_PlayState?"Stopped":
			Buffering==m_PlayState?"Buffering":
			    Playing==m_PlayState?"Playing":
				Paused==m_PlayState?"Paused":
				    "Seeking");
	}
	fflush(m_logfile);
    }
#endif

    // /Don't adjust m_lTimeOffset here; it should remain unchanged while
    // the following is used to keep track of how much pause-time we've had
    // while playback of the main timeline was *not* paused:
    m_lCumulativeUpdatedTimeOffset -= lTimeOffset;

    return rc;
}

/************************************************************************
 *	Method:
 *	    IHXUpdateProperties::UpdatePlayTimes
 *	Purpose:
 *	    Call this method to update the playtime attributes
 */
STDMETHODIMP
CRealTextRenderer::UpdatePlayTimes(IHXValues* pProps)
{
    return HXR_OK;
}

/*
 * IHXValues methods
 */

STDMETHODIMP
CRealTextRenderer::GetPropertyULONG32	     (
	    const char*          pPropertyName,
	    REF(ULONG32)         uPropertyValue)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetPropertyULONG32(pPropertyName,
                                               uPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::SetPropertyULONG32	     (
		const char*          pPropertyName,
		ULONG32              uPropertyValue)
{
    HX_RESULT retVal = HXR_OK;

    if (m_pValues)
    {
        ULONG32   ulTmp = 0;
        HX_RESULT rv    = m_pValues->GetPropertyULONG32(pPropertyName, ulTmp);
        if (FAILED(rv) ||
            (SUCCEEDED(rv) && ulTmp != uPropertyValue))
        {
            retVal = m_pValues->SetPropertyULONG32(pPropertyName,
                                                   uPropertyValue);
            if (SUCCEEDED(retVal))
            {
                retVal = PropertyUpdated(pPropertyName, TRUE /*is ULONG32*/);
            }
        }
    }
    else
    {
        retVal = HXR_NOT_INITIALIZED;
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetPropertyCString	    (
		const char*         pPropertyName,
		REF(IHXBuffer*)    pPropertyValue)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetPropertyCString(pPropertyName,
                                               pPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetFirstPropertyULONG32   (
		REF(const char*)     pPropertyName,
		REF(ULONG32)         uPropertyValue)
{	
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetFirstPropertyULONG32(pPropertyName,
                                                    uPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetNextPropertyULONG32   (
		REF(const char*)    pPropertyName,
		REF(ULONG32)        uPropertyValue)
{	
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetNextPropertyULONG32(pPropertyName,
                                                   uPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::SetPropertyBuffer	    (
		const char*         pPropertyName,
		IHXBuffer*         pPropertyValue)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->SetPropertyBuffer(pPropertyName,
                                              pPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetPropertyBuffer	    (
		const char*         pPropertyName,
		REF(IHXBuffer*)    pPropertyValue)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetPropertyBuffer(pPropertyName,
                                              pPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetFirstPropertyBuffer   (
		REF(const char*)    pPropertyName,
		REF(IHXBuffer*)    pPropertyValue)
{	
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetFirstPropertyBuffer(pPropertyName,
                                                   pPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetNextPropertyBuffer    (
		REF(const char*)    pPropertyName,
		REF(IHXBuffer*)    pPropertyValue)
{	
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetNextPropertyBuffer(pPropertyName,
                                                  pPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::SetPropertyCString	    (
		const char*         pPropertyName,
		IHXBuffer*         pPropertyValue)
{
    HX_RESULT retVal = HXR_FAIL;

    if (!pPropertyName  ||  !pPropertyValue)
    {
	retVal = HXR_INVALID_PARAMETER;
    }
    else if (m_pValues)
    {
        IHXBuffer*  pPrevValBuf = NULL;
        HX_RESULT rv =
		m_pValues->GetPropertyBuffer(pPropertyName, pPrevValBuf);
	const char* pszPrevVal = (pPrevValBuf != NULL)?
		(const char*)pPrevValBuf->GetBuffer() : NULL;
	const char* pszNewVal = (const char*)pPropertyValue->GetBuffer();

        if (FAILED(rv)  ||
		pszPrevVal  &&  !strcmp(pszPrevVal, pszNewVal))
        {
            retVal = m_pValues->SetPropertyCString(pPropertyName,
                                                   pPropertyValue);
            if (SUCCEEDED(retVal))
            {
                retVal = PropertyUpdated(pPropertyName, FALSE /*not ULONG32*/);
            }
        }
    }
    else
    {
        retVal = HXR_NOT_INITIALIZED;
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetFirstPropertyCString   (
		REF(const char*)    pPropertyName,
		REF(IHXBuffer*)    pPropertyValue)
{	
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetFirstPropertyCString(pPropertyName,
                                                    pPropertyValue);
    }

    return retVal;
}

STDMETHODIMP
CRealTextRenderer::GetNextPropertyCString    (
		REF(const char*)    pPropertyName,
		REF(IHXBuffer*)    pPropertyValue)
{	
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetNextPropertyCString(pPropertyName,
                                                   pPropertyValue);
    }

    return retVal;
}


