#include	"lsmem.h"
#include	"limits.h"
#include	"ruby.h"
#include	"objhelp.h"
#include	"lscbk.h"
#include	"lsdevres.h"
#include	"pdobj.h"
#include	"objdim.h"
#include	"plssubl.h"
#include	"plsdnode.h"
#include	"pilsobj.h"
#include	"lscrsubl.h"
#include	"lssubset.h"
#include	"lsdnset.h"
#include	"zqfromza.h"
#include	"lsdocinf.h"
#include	"fmti.h"
#include	"posichnk.h"
#include	"locchnk.h"
#include	"lsdnfin.h"
#include	"brko.h"
#include	"lspap.h"
#include	"plspap.h"
#include	"lsqsubl.h"
#include	"dispi.h"
#include	"lsdssubl.h"
#include	"lsems.h"
#include	"dispmisc.h"
#include	"lstfset.h"
#include	"lsqout.h"
#include	"lsqin.h"
#include	"sobjhelp.h"
#include	"brkkind.h"


#define RUBY_MAIN_ESC_CNT	1
#define RUBY_RUBY_ESC_CNT	1


struct ilsobj
{
    POLS				pols;
	LSCBK				lscbk;
	PLSC				plsc;
	LSDEVRES			lsdevres;
	RUBYSYNTAX			rubysyntax;
	LSESC				lsescMain;
	LSESC				lsescRuby;
	RUBYCBK				rcbk;			/* Callbacks  to client application */

};

typedef struct SUBLINEDNODES
{
	PLSDNODE			plsdnStart;
	PLSDNODE			plsdnEnd;

} SUBLINEDNODES, *PSUBLINEDNODES;

struct dobj
{	
	SOBJHELP			sobjhelp;			/* common area for simple objects */	
	PILSOBJ				pilsobj;			/* ILS object */
	PLSDNODE			plsdn;				/* DNODE for this object */
	LSCP				cpStart;			/* Starting LS cp for object */
	LSTFLOW				lstflow;			/* text flow for the Ruby object */
	PLSRUN				plsrunFirstRubyChar;/* plsrun for first Ruby line char */
	PLSRUN				plsrunLastRubyChar;	/* plsrun for last Ruby line char */
	LSCP				cpStartRuby;		/* first cp of the ruby line */
	LSCP				cpStartMain;		/* first cp of the main line */
	PLSSUBL				plssublMain;		/* Handle to first subline */
	OBJDIM				objdimMain;			/* Objdim of first subline */
	PLSSUBL				plssublRuby;		/* Handle to second line */
	OBJDIM				objdimRuby;			/* Objdim of second line */
	long				dvpMainOffset;		/* Offset of main line's baseline */
											/* from baseline ofRuby object. */
	long				dvpRubyOffset;		/* Offset of Ruby line's baseline */
											/* from baseline of Ruby object. */
	long				dvrRubyOffset;		/* Offset of Ruby line's baseline */
											/* from baseline of Ruby object in reference units. */
	enum rubycharjust	rubycharjust;		/* Type of centering */
	long				durSplWidthMod;		/* special Ruby width mod if special behavior
											 * when Ruby is on the end of the line */
	BOOL				fFirstOnLine:1;		/* TRUE = object is first on line */
	BOOL				fSpecialLineStartEnd:1;/* Special Begin of Line or End of */
											/* Line behavior. */
	BOOL				fModAfterCalled:1;	/* Whether mod width after has been called */
	long				durDiff;			/* Amount of overhang of ruby line if */
											/* ruby line is longer, otherwise amount */
											/* of underhang if main text is longer. */
	long				durModBefore;		/* Mod width distance before */
	long				dupOffsetMain;		/* Offset from start of object of main line. */
	long				dupOffsetRuby;		/* Offset from start of object of ruby line. */
	SUBLINEDNODES		sublnlsdnMain;		/* Start end dnodes of main line */
	SUBLINEDNODES		sublnlsdnRuby;		/* Start end dnodes of ruby line */
};



/* F R E E D O B J */
/*----------------------------------------------------------------------------
	%%Function: RubyFreeDobj
	%%Contact: antons

		Free all resources associated with this Ruby dobj.
	
----------------------------------------------------------------------------*/
static LSERR RubyFreeDobj (PDOBJ pdobj)
{
	LSERR lserr1 = lserrNone;
	LSERR lserr2 = lserrNone;

	PILSOBJ pilsobj = pdobj->pilsobj;

	if (pdobj->plssublMain != NULL)
		{
		lserr1 = LsDestroySubline(pdobj->plssublMain);
		}

	if (pdobj->plssublRuby != NULL)
		{
		lserr2 = LsDestroySubline(pdobj->plssublRuby);
		}

    pilsobj->lscbk.pfnDisposePtr(pilsobj->pols, pdobj);

	if (lserr1 != lserrNone) 
		{
		return lserr1;
		}
	else
		{
		return lserr2;
		}

}


/* R U B Y  F M T  F A I L E D */
/*----------------------------------------------------------------------------
	%%Function: RubyFmtFailed
	%%Contact: antons

		Could not create Ruby DOBJ due to error. 
		IN:	pdobj of partially created Ruby; NULL if pdobj was not yet allocated;
		IN:	lserr from the last error
		
----------------------------------------------------------------------------*/
static LSERR RubyFmtFailed (PDOBJ pdobj, LSERR lserr)
{
	if (pdobj != NULL) RubyFreeDobj (pdobj); /* Works with parially-filled DOBJ */

	return lserr;
}


/* G E T R U N S F O R S U B L I N E */
/*----------------------------------------------------------------------------
	%%Function: GetRunsForSubline
	%%Contact: ricksa

		This gets all the runs for a particular subline.
----------------------------------------------------------------------------*/
static LSERR GetRunsForSubline(
	PILSOBJ pilsobj,			/* (IN): object ILS */
	PLSSUBL plssubl,			/* (IN): subline to get the runs from */
	DWORD *pcdwRuns,			/* (OUT): count of runs for subline */
	PLSRUN **ppplsrun)			/* (OUT): array of plsruns for subline */
{
	DWORD cdwRuns;

	LSERR lserr = LssbGetNumberDnodesInSubline(plssubl, &cdwRuns);

	*ppplsrun = NULL; /* No runs or in case of error */

	if (lserr != lserrNone) return lserr;

	if (cdwRuns != 0)
		{
		
	    *ppplsrun = (PLSRUN *) pilsobj->lscbk.pfnNewPtr(pilsobj->pols,
			sizeof(PLSRUN) * cdwRuns);

		if (*ppplsrun == NULL) return lserrOutOfMemory;

		lserr = LssbGetPlsrunsFromSubline(plssubl, cdwRuns, *ppplsrun);

		if (lserr != lserrNone)
			{
			pilsobj->lscbk.pfnDisposePtr(pilsobj->pols, *ppplsrun);
			
			*ppplsrun = NULL;
			return lserr;
			}
		}

	*pcdwRuns = cdwRuns;

	return lserrNone;
}

/* D I S T R I B U T E T O L I N E */
/*----------------------------------------------------------------------------
	%%Function: DistributeToLine
	%%Contact: ricksa

		Distribute space to line & get new size of line.
----------------------------------------------------------------------------*/
static LSERR DistributeToLine(
	PLSC plsc,					/* (IN): LS context */
	SUBLINEDNODES *psublnlsdn,	/* (IN): start/end dnode for subline */
	long durToDistribute,		/* (IN): amount to distribute*/
	PLSSUBL plssubl,			/* (IN): subline for distribution */
	POBJDIM pobjdim)			/* (OUT): new size of line dimesions */
{
	LSERR lserr = LsdnDistribute(plsc, psublnlsdn->plsdnStart, 
		psublnlsdn->plsdnEnd, durToDistribute);
	LSTFLOW lstflowUnused;

	if (lserrNone == lserr)
		{
		/* recalculate objdim for line */
		lserr = LssbGetObjDimSubline(plssubl, &lstflowUnused, pobjdim);
		}

	return lserr;
}


/* D O R U B Y S P A C E D I S T R I B U T I O N */
/*----------------------------------------------------------------------------
	%%Function: DoRubySpaceDistribution
	%%Contact: ricksa

		Do the ruby space distribution to handle overhangs.
----------------------------------------------------------------------------*/
static LSERR DoRubySpaceDistribution(
	PDOBJ pdobj)
{
	long durDiff = 0; 
	long dur = pdobj->objdimMain.dur - pdobj->objdimRuby.dur;
	long durAbs = dur;
	PLSSUBL plssubl;
	LSDCP dcp;
	PILSOBJ pilsobj = pdobj->pilsobj;
	LSERR lserr = lserrNone;
	SUBLINEDNODES *psublnlsdn;
	POBJDIM pobjdim;
	BOOL fSpecialJust;
	long durToDistribute;

	if ((0 == pdobj->objdimMain.dur)
		|| (0 == pdobj->objdimRuby.dur)
		|| (0 == dur))
		{
		/* Can't distribute space on a shorter line so we are done. */
		return lserrNone;
		}

	if (dur > 0)
		{
		/* Main line is longer - distibute in Ruby pronunciation line */

		/*
		 *	According to the JIS spec, special alignment only occurs when the 
		 *	Ruby text is longer than the main text. Therefore, if the main
		 *	line is longer we turn of the special aligment flag here.
		 */
		pdobj->fSpecialLineStartEnd = FALSE;
		plssubl = pdobj->plssublRuby;
		psublnlsdn = &pdobj->sublnlsdnRuby;
		pobjdim = &pdobj->objdimRuby;
		}
	else
		{
		/* Ruby pronunciation line is longer - distibute in main line */
		plssubl = pdobj->plssublMain;
		psublnlsdn = &pdobj->sublnlsdnMain;
		pobjdim = &pdobj->objdimMain;
		durAbs = -dur;
		}

	fSpecialJust = FALSE;
//	fSpecialJust = 
//		pdobj->fSpecialLineStartEnd && pdobj->fFirstOnLine;

	if (!fSpecialJust)
		{
		switch (pdobj->rubycharjust)
			{
			case rcj121:
				lserr = LssbGetVisibleDcpInSubline(plssubl, &dcp);

				Assert (dcp > 0);

				if (lserr != lserrNone)
					{
					break;
					}

				dcp *= 2;

				if (durAbs >= (long) dcp)
					{
					durDiff = durAbs / dcp;

					/* Note: distribution amount is amount excluding 
					 * beginning and end.
					 */
					lserr = DistributeToLine(pilsobj->plsc, psublnlsdn,
						durAbs - 2 * durDiff, plssubl, pobjdim);

					if (dur < 0)
						{
						durDiff = - durDiff;
						}

					break;
					}

				/*
				 * Intention fall through in the case where the overhang will
				 * be less than one pixel.
				 */

			case rcj010:
				AssertSz(0 == durDiff, 
					"DoRubySpaceDistribution rcj010 unexpected value for durDiff");

				lserr = LssbGetVisibleDcpInSubline(plssubl, &dcp);

				Assert (dcp > 0);

				if (lserr != lserrNone)
					{
					break;
					}
				
				if (dcp != 1)
					{
					lserr = DistributeToLine(pilsobj->plsc, psublnlsdn,
						durAbs, plssubl, pobjdim);
					break;
					}

				/*
				 * Intentional fall through to center case.
				 * Only one character in line so we just center it.
				 */
	
			case rcjCenter:
				durDiff = dur / 2;
				break;

			case rcjLeft:
				durDiff = 0;
				break;

			case rcjRight:
				durDiff = dur;
				break;

			default:
				AssertSz(FALSE, 
					"DoRubySpaceDistribution - invalid adjustment value");
			}
		}
	else
		{
		/* First on line & special justification used. */
		LSERR lserr = LssbGetVisibleDcpInSubline(plssubl, &dcp);

		Assert (dcp > 0);

		if (lserrNone == lserr)
		{
			if (durAbs >= (long) dcp)
				{
				durDiff = durAbs / dcp;
				}

				durToDistribute = durAbs - durDiff;

				if (dur < 0)
					{
					durDiff = -durDiff;
					}

			lserr = DistributeToLine(pilsobj->plsc, psublnlsdn, 
				durToDistribute, plssubl, pobjdim);
			}
		}

	pdobj->durDiff = durDiff;
	return lserr;
}

/* G E T M A I N P O I N T */
/*----------------------------------------------------------------------------
	%%Function: GetMainPoint
	%%Contact: ricksa

		This gets the point for the baseline of the main line of text in
		the Ruby object.
		
----------------------------------------------------------------------------*/
static LSERR GetMainPoint(
	PDOBJ pdobj,				/*(IN): dobj for Ruby */
	const POINT *pptBase,		/*(IN): point for baseline. */
	LSTFLOW lstflow,			/*(IN): lstflow at baseline of object */
	POINT *pptLine)				/*(OUT): point for baseline of main text */
{	
	POINTUV pointuv;
	pointuv.u = pdobj->dupOffsetMain;
	pointuv.v = pdobj->dvpMainOffset;
	return LsPointXYFromPointUV(pptBase, lstflow, &pointuv, pptLine);
}

/* G E T M A I N P O I N T */
/*----------------------------------------------------------------------------
	%%Function: GetMainPoint
	%%Contact: ricksa

		This gets the point for the baseline of the main line of text in
		the Ruby object.
		
----------------------------------------------------------------------------*/
static LSERR GetRubyPoint(
	PDOBJ pdobj,				/*(IN): dobj for Ruby */
	const POINT *pptBase,		/*(IN): point for baseline. */
	LSTFLOW lstflow,			/*(IN): lstflow at baseline of object */
	POINT *pptLine)				/*(OUT): point for baseline of ruby text */
{	
	POINTUV pointuv;
	pointuv.u = pdobj->dupOffsetRuby;
	pointuv.v = pdobj->dvpRubyOffset;
	return LsPointXYFromPointUV(pptBase, lstflow, &pointuv, pptLine);
}

/* M O D W I D T H H A N D L E R */
/*----------------------------------------------------------------------------
	%%Function: ModWidthHandler
	%%Contact: ricksa

		This gets the adjustment for the Ruby object and the text character
		and then adjusts the Ruby object's size based on the response from
		the client.
----------------------------------------------------------------------------*/
static LSERR ModWidthHandler(
	PDOBJ pdobj,				/* (IN): dobj for Ruby */
	enum rubycharloc rubyloc,	/* (IN): whether char is before or after */
	PLSRUN plsrun,				/* (IN): run for character */
	WCHAR wch,					/* (IN): character before or after Ruby object */
	MWCLS mwcls,				/* (IN): mod width class for for character */
	PCHEIGHTS pcheightsRef,		/* (IN): height of character */
	PLSRUN plsrunRubyObject,	/* (IN): plsrun for the ruby object */
	PLSRUN plsrunRubyText,		/* (IN): plsrun for ruby text */
	long durOverhang,			/* (IN): maximum amount of overhang */
	long *pdurAdjText,			/* (OUT): amount to change text object size */
	long *pdurRubyMod)			/* (OUT): amount to change ruby object */
{
	LSERR lserr;
	PILSOBJ pilsobj = pdobj->pilsobj;
	LSEMS lsems;
	long durModRuby = 0;
	long durMaxOverhang = 0;

	/*
	 * Ruby can overhang only if it is longer and if preceeding/succeeding
	 * character is of lesser or equal height than the bottom of the Ruby
	 * pronunciation line.
	 */
	if ((durOverhang < 0) 
		&& (pcheightsRef->dvAscent <= 
			(pdobj->dvrRubyOffset - pdobj->objdimRuby.heightsRef.dvDescent)))
		{
		/* Ruby line overhangs - get max to overhang */
		lserr = pilsobj->lscbk.pfnGetEms(pilsobj->pols, plsrunRubyText, 
			pdobj->lstflow, &lsems);

		if (lserr != lserrNone)
			{
			return lserr;
			}

		durMaxOverhang = lsems.em;
		durOverhang = -durOverhang;

		if (durMaxOverhang > durOverhang)
			{
			/* limit maximum overhang to max overhang for ruby line */
			durMaxOverhang = durOverhang;
			}
		}

	lserr = pilsobj->rcbk.pfnFetchRubyWidthAdjust(pilsobj->pols, 
		pdobj->cpStart, plsrun, wch, mwcls, plsrunRubyObject, 
			rubyloc, durMaxOverhang, pdurAdjText, &durModRuby);

	if (lserrNone == lserr)
		{
		if (durModRuby != 0)
			{
			/* size of ruby object needs to change */
			pdobj->sobjhelp.objdimAll.dur += durModRuby;
			lserr = LsdnResetObjDim(pilsobj->plsc, pdobj->plsdn, 
				&pdobj->sobjhelp.objdimAll);
			}

		*pdurRubyMod = durModRuby;
		}

	return lserr;
}

/* M A S S A G E F O R R I G H T A D J U S T */
/*----------------------------------------------------------------------------
	%%Function: MassageForRightAdjust
	%%Contact: ricksa


		Massage object so that right aligned lines will end on exactly
		the same pixel.
	
----------------------------------------------------------------------------*/
static LSERR MassageForRightAdjust(
	PDOBJ pdobj)				/* dobj for Ruby */
{
	LSERR lserr;
	long dupRuby;
	long dupMain;
	long dupDiff;
	LSTFLOW lstflowIgnored;

	/* Get the length of the two lines */
	lserr = LssbGetDupSubline(pdobj->plssublMain, &lstflowIgnored, &dupMain);
	if (lserr != lserrNone) return lserr;

	lserr = LssbGetDupSubline(pdobj->plssublRuby, &lstflowIgnored, &dupRuby);
	if (lserr != lserrNone)	return lserr;

	/* Get difference between two lines */
	dupDiff = dupMain - dupRuby;

	if (dupDiff >= 0)
		{
		/* Main line longest */
		pdobj->dupOffsetRuby = pdobj->dupOffsetMain + dupDiff;
		}
	else
		{
		/* Ruby line longest - reverse sign of dupDiff to add */
		pdobj->dupOffsetMain = pdobj->dupOffsetRuby - dupDiff;
		}

	return lserrNone;
}

/* R U B I C R E A T E I L S O B J */
/*----------------------------------------------------------------------------
	%%Function: RubyCreateILSObj
	%%Contact: ricksa

		CreateILSObj

		Create the ILS object for all Ruby objects.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyCreateILSObj(
	POLS pols,				/* (IN): client application context */
	PLSC plsc,				/* (IN): LS context */
	PCLSCBK pclscbk,		/* (IN): callbacks to client application */
	DWORD idObj,			/* (IN): id of the object */
	PILSOBJ *ppilsobj)		/* (OUT): object ilsobj */
{
    PILSOBJ pilsobj;
	LSERR lserr;
	RUBYINIT rubyinit;
	rubyinit.dwVersion = RUBY_VERSION;

	/* Get initialization data */
	lserr = pclscbk->pfnGetObjectHandlerInfo(pols, idObj, &rubyinit);

	if (lserr != lserrNone)
		{
		return lserr;
		}

    pilsobj = pclscbk->pfnNewPtr(pols, sizeof(*pilsobj));

	if (NULL == pilsobj)
	{
		return lserrOutOfMemory;
	}

    pilsobj->pols = pols;
    pilsobj->lscbk = *pclscbk;
	pilsobj->plsc = plsc;
	pilsobj->lsescMain.wchFirst = rubyinit.wchEscMain;
	pilsobj->lsescMain.wchLast = rubyinit.wchEscMain;
	pilsobj->lsescRuby.wchFirst = rubyinit.wchEscRuby;
	pilsobj->lsescRuby.wchLast = rubyinit.wchEscRuby;
	pilsobj->rcbk = rubyinit.rcbk;
	pilsobj->rubysyntax = rubyinit.rubysyntax;

	*ppilsobj = pilsobj;
	return lserrNone;
}

/* R U B I D E S T R O Y I L S O B J */
/*----------------------------------------------------------------------------
	%%Function: RubyDestroyILSObj
	%%Contact: ricksa

		DestroyILSObj

		Free all resources assocaiated with Ruby ILS object.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyDestroyILSObj(
	PILSOBJ pilsobj)			/* (IN): object ilsobj */
{
	pilsobj->lscbk.pfnDisposePtr(pilsobj->pols, pilsobj);
	return lserrNone;
}

/* R U B I S E T D O C */
/*----------------------------------------------------------------------------
	%%Function: RubySetDoc
	%%Contact: ricksa

		SetDoc

		Keep track of device resolution.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubySetDoc(
	PILSOBJ pilsobj,			/* (IN): object ilsobj */
	PCLSDOCINF pclsdocinf)		/* (IN): initialization data of the document level */
{
	pilsobj->lsdevres = pclsdocinf->lsdevres;
	return lserrNone;
}


/* R U B I C R E A T E L N O B J */
/*----------------------------------------------------------------------------
	%%Function: RubyCreateLNObj
	%%Contact: ricksa

		CreateLNObj

		Create the Line Object for the Ruby. Since we only really need
		the global ILS object, just pass that object back as the line object.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyCreateLNObj(
	PCILSOBJ pcilsobj,			/* (IN): object ilsobj */
	PLNOBJ *pplnobj)			/* (OUT): object lnobj */
{
	*pplnobj = (PLNOBJ) pcilsobj;
	return lserrNone;
}

/* R U B I D E S T R O Y L N O B J */
/*----------------------------------------------------------------------------
	%%Function: RubyDestroyLNObj
	%%Contact: ricksa

		DestroyLNObj

		Frees resources associated with the Ruby line object. No-op because
		we don't really allocate one.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyDestroyLNObj(
	PLNOBJ plnobj)				/* (OUT): object lnobj */

{
	Unreferenced(plnobj);
	return lserrNone;
}

/* R U B I F M T */
/*----------------------------------------------------------------------------
	%%Function: RubyFmt
	%%Contact: ricksa

		Fmt

		Format the Ruby object. This formats the main line and the 
		pronunciation line. It then queries the client for spacing
		information and then completes the formatting.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyFmt(
    PLNOBJ plnobj,				/* (IN): object lnobj */
    PCFMTIN pcfmtin,			/* (IN): formatting input */
    FMTRES *pfmtres)			/* (OUT): formatting result */
{
	PDOBJ pdobj;
	LSERR lserr;
	PILSOBJ pilsobj = (PILSOBJ) plnobj;
	POLS pols = pilsobj->pols;
	LSCP cpStartMain;
	LSCP cpStartRuby = pcfmtin->lsfgi.cpFirst + 1;
	LSCP cpOut;
	LSTFLOW lstflow = pcfmtin->lsfgi.lstflow;
	DWORD cdwRunsMain;
	DWORD cdwRunsRuby;
	PLSRUN *pplsrunMain = NULL;
	PLSRUN *pplsrunRuby = NULL;
	FMTRES fmtres;
	OBJDIM objdimAll;
	FMTRES fmtr = fmtrCompletedRun;
	BOOL fSpecialLineStartEnd;

    /*
     * Allocate the DOBJ
     */
    pdobj = pilsobj->lscbk.pfnNewPtr(pols, sizeof(*pdobj));

    if (pdobj == NULL) return RubyFmtFailed (NULL, lserrOutOfMemory);

	ZeroMemory(pdobj, sizeof(*pdobj));
	pdobj->pilsobj = pilsobj;
	pdobj->plsdn = pcfmtin->plsdnTop;
	pdobj->cpStart = pcfmtin->lsfgi.cpFirst;
	pdobj->fFirstOnLine = pcfmtin->lsfgi.fFirstOnLine;
	pdobj->lstflow = lstflow;

	if (RubyPronunciationLineFirst == pilsobj->rubysyntax)
		{
		/*
		 * Build pronunciation line of text
		 */
		 
		lserr = FormatLine(pilsobj->plsc, cpStartRuby, LONG_MAX, lstflow,
			&pdobj->plssublRuby, RUBY_RUBY_ESC_CNT, &pilsobj->lsescRuby,  
				&pdobj->objdimRuby, &cpOut, &pdobj->sublnlsdnRuby.plsdnStart,
					&pdobj->sublnlsdnRuby.plsdnEnd, &fmtres);

		/* +1 moves passed the ruby line escape character */
		cpStartMain = cpOut + 1;

		pdobj->cpStartRuby = cpStartRuby;
		pdobj->cpStartMain = cpStartMain;

		/*
		 * Build main line of text
 		 */
		if (lserrNone == lserr)
			{
			lserr = FormatLine(pilsobj->plsc, cpStartMain, LONG_MAX, lstflow,
				&pdobj->plssublMain, RUBY_MAIN_ESC_CNT, &pilsobj->lsescMain,  
					&pdobj->objdimMain, &cpOut, &pdobj->sublnlsdnMain.plsdnStart, 
						&pdobj->sublnlsdnMain.plsdnEnd, &fmtres);
			}
		}
	else
		{
		/*
		 * Build main line of text
 		 */

		cpStartMain = cpStartRuby;

		lserr = FormatLine(pilsobj->plsc, cpStartMain, LONG_MAX, lstflow,
			&pdobj->plssublMain, RUBY_MAIN_ESC_CNT, &pilsobj->lsescMain,  
				&pdobj->objdimMain, &cpOut, &pdobj->sublnlsdnMain.plsdnStart, 
					&pdobj->sublnlsdnMain.plsdnEnd, &fmtres);

		/* +1 moves passed the main line escape character */
		cpStartRuby = cpOut + 1;

		pdobj->cpStartRuby = cpStartRuby;
		pdobj->cpStartMain = cpStartMain;

		/*
		 * Build pronunciation line of text
		 */
		if (lserrNone == lserr)
			{
			lserr = FormatLine(pilsobj->plsc, cpStartRuby, LONG_MAX, lstflow,
				&pdobj->plssublRuby, RUBY_RUBY_ESC_CNT, &pilsobj->lsescRuby,  
					&pdobj->objdimRuby, &cpOut, &pdobj->sublnlsdnRuby.plsdnStart, 
						&pdobj->sublnlsdnRuby.plsdnEnd, &fmtres);

			}
		}

	if (lserr != lserrNone)	return RubyFmtFailed (pdobj, lserr);

	lserr = GetRunsForSubline(pilsobj, pdobj->plssublMain, &cdwRunsMain, &pplsrunMain);

	if (lserr != lserrNone) return RubyFmtFailed (pdobj, lserr);

	lserr = GetRunsForSubline(pilsobj, pdobj->plssublRuby, &cdwRunsRuby, &pplsrunRuby);

	if (lserr != lserrNone) return RubyFmtFailed (pdobj, lserr);

	/* Save the first and last plsrun for use in GetModWidth */
	if (cdwRunsRuby != 0)
		{
		pdobj->plsrunFirstRubyChar = pplsrunRuby[0];
		pdobj->plsrunLastRubyChar = pplsrunRuby[cdwRunsRuby - 1];
		}

	/* 
	 *	Calculate the object dimensions.
	 */
	lserr = pilsobj->rcbk.pfnFetchRubyPosition(pols, pdobj->cpStart, pdobj->lstflow,
		cdwRunsMain, pplsrunMain, &pdobj->objdimMain.heightsRef, 
			&pdobj->objdimMain.heightsPres, cdwRunsRuby, pplsrunRuby, 
				&pdobj->objdimRuby.heightsRef, &pdobj->objdimRuby.heightsPres,
					&objdimAll.heightsRef, &objdimAll.heightsPres, 
						&pdobj->dvpMainOffset, &pdobj->dvrRubyOffset, 
							&pdobj->dvpRubyOffset, &pdobj->rubycharjust, 
								&fSpecialLineStartEnd);

	/* Free buffers allocated for plsruns for this call */
	
	if (pplsrunMain != NULL) pilsobj->lscbk.pfnDisposePtr(pilsobj->pols, pplsrunMain);

	if (pplsrunRuby != NULL) pilsobj->lscbk.pfnDisposePtr(pilsobj->pols, pplsrunRuby);

	if (lserr != lserrNone) return RubyFmtFailed (pdobj, lserr);

	/*
	 * Special line start/end adjustment matters only when a justification of
	 * centered, 0:1:0 or 1:2:1 is selected.
	 */

	if (fSpecialLineStartEnd 
		&& (pdobj->rubycharjust != rcjLeft)
		&& (pdobj->rubycharjust != rcjRight))
		{
		pdobj->fSpecialLineStartEnd = TRUE;
		}

	/* Distribute space for Ruby */
	lserr = DoRubySpaceDistribution(pdobj);

	if (lserr != lserrNone) return RubyFmtFailed (pdobj, lserr);

	/* ur is ur of longest subline. */

	objdimAll.dur = pdobj->objdimMain.dur;

	if (pdobj->objdimMain.dur < pdobj->objdimRuby.dur)
		{
		objdimAll.dur = pdobj->objdimRuby.dur;
		}

	pdobj->sobjhelp.objdimAll = objdimAll;

	/* Need to add 1 to take into account escape character at end. */

	pdobj->sobjhelp.dcp = cpOut - pdobj->cpStart + 1;

	lserr = LsdnFinishRegular(pilsobj->plsc, pdobj->sobjhelp.dcp, 
		pcfmtin->lsfrun.plsrun, pcfmtin->lsfrun.plschp, pdobj, 
			&pdobj->sobjhelp.objdimAll);
		
	if (lserr != lserrNone) return RubyFmtFailed (pdobj, lserr);

	if (pcfmtin->lsfgi.urPen + objdimAll.dur > pcfmtin->lsfgi.urColumnMax)
		{
		fmtr = fmtrExceededMargin;
		}

	*pfmtres = fmtr;

	AssertSz(((pdobj->fFirstOnLine && pcfmtin->lsfgi.fFirstOnLine) 
		|| (!pdobj->fFirstOnLine && !pcfmtin->lsfgi.fFirstOnLine)), 
		"RubyFmt - bad first on line flag");

	return lserrNone;
}

/* R U B Y G E T M O D W I D T H P R E C E D I N G C H A R */
/*----------------------------------------------------------------------------
	%%Function: RubyGetModWidthPrecedingChar
	%%Contact: ricksa

		.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyGetModWidthPrecedingChar(
	PDOBJ pdobj,				/* (IN): dobj */
	PLSRUN plsrun,				/* (IN): plsrun of the object */
	PLSRUN plsrunText,			/* (IN): plsrun of the preceding char */
	PCHEIGHTS pcheightsRef,		/* (IN): height info about character */
	WCHAR wchar,				/* (IN): preceding character */
	MWCLS mwcls,				/* (IN): ModWidth class of preceding character */
	long *pdurChange)			/* (OUT): amount by which width of the preceding char is to be changed */
{
	AssertSz(!pdobj->fFirstOnLine, "RubyGetModWidthPrecedingChar got called for first char");

	return ModWidthHandler(pdobj, rubyBefore, plsrunText, wchar, mwcls, 
		pcheightsRef, plsrun, pdobj->plsrunFirstRubyChar, pdobj->durDiff, 
			pdurChange, &pdobj->durModBefore);
}

/* R U B Y G E T M O D W I D T H F O L L O W I N G C H A R */
/*----------------------------------------------------------------------------
	%%Function: RubyGetModWidthFollowingChar
	%%Contact: ricksa

		.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyGetModWidthFollowingChar(
	PDOBJ pdobj,				/* (IN): dobj */
	PLSRUN plsrun,				/* (IN): plsrun of the object */
	PLSRUN plsrunText,			/* (IN): plsrun of the following char */
	PCHEIGHTS pcheightsRef,		/* (IN): height info about character */
	WCHAR wchar,				/* (IN): following character */
	MWCLS mwcls,				/* (IN): ModWidth class of the following character */
	long *pdurChange)			/* (OUT): amount by which width of the following char is to be changed */
{
	long durDiff = pdobj->durDiff;
	pdobj->fModAfterCalled = TRUE;

	switch (pdobj->rubycharjust)
		{
		case rcjRight:
			/* Right justified so no overhang on right */
			durDiff = 0;
			break;

		case rcjLeft:
			/* For left, max overhang is difference between widths of lines */
			durDiff = pdobj->objdimMain.dur - pdobj->objdimRuby.dur;
			break;

		default:
			break;				
		}

	return ModWidthHandler(pdobj, rubyAfter, plsrunText, wchar, mwcls, 
		pcheightsRef, plsrun, pdobj->plsrunLastRubyChar, durDiff, pdurChange,
			&pdobj->sobjhelp.durModAfter);
}


/* R U B Y S E T B R E A K */
/*----------------------------------------------------------------------------
	%%Function: RubySetBreak
	%%Contact: ricksa

		SetBreak

		.
----------------------------------------------------------------------------*/
LSERR WINAPI RubySetBreak(
	PDOBJ pdobj,				/* (IN): dobj which is broken */
	BRKKIND brkkind,			/* (IN): prev | next | force | after */
	DWORD cBreakRecord,			/* (IN): size of array */
	BREAKREC *rgBreakRecord,	/* (IN): array of break records */
	DWORD *pcActualBreakRecord)	/* (IN): actual number of used elements in array */
{
	LSERR lserr = lserrNone;
	LSCP cpOut;

	LSDCP dcpVisible;

	/* REVIEW (antons): Check this strange logic after new breaking will work */

	Unreferenced (rgBreakRecord);
	Unreferenced (cBreakRecord);
	Unreferenced (brkkind);
	Unreferenced (pdobj);

	Unreferenced (cpOut);
	Unreferenced (dcpVisible);


	*pcActualBreakRecord = 0;

#ifdef UNDEFINED

	if (pdobj->fSpecialLineStartEnd && !pdobj->fFirstOnLine && 
		brkkind != brkkindImposedAfter)
	{

		/*
		 * Because object is last on line and Ruby overhangs, we need to adjust 
		 * its width for the new overhang.
		 */

		PILSOBJ pilsobj = pdobj->pilsobj;
		FMTRES fmtres;
		long dur;
		long dcpOffset = pdobj->dcpRuby;

		if (RubyMainLineFirst == pdobj->pilsobj->rubysyntax)
			{
			dcpOffset = 0;
			}

		/* clear out original subline */
		LsDestroySubline(pdobj->plssublMain);

		/* Format the main line over again */
		lserr = FormatLine(pilsobj->plsc, pdobj->cpStart + dcpOffset + 1, 
			LONG_MAX, pdobj->lstflow, &pdobj->plssublMain, RUBY_MAIN_ESC_CNT,
				&pilsobj->lsescMain, &pdobj->objdimMain, &cpOut, 
					&pdobj->sublnlsdnMain.plsdnStart, 
						&pdobj->sublnlsdnMain.plsdnEnd, &fmtres);

		if (lserr != lserrNone) return lserr;

		dur = pdobj->objdimRuby.dur - pdobj->objdimMain.dur;

		AssertSz(dur > 0, "RubySetBreak - no overhang width");

		lserr = LssbGetVisibleDcpInSubline(pdobj->plssublMain, &dcpVisible);

		if (lserrNone == lserr)
			{
			pdobj->durDiff = 0;

			if (dur > (long) dcpVisible)
				{
				pdobj->durDiff = -(dur / (long) dcpVisible);
				dur += pdobj->durDiff;
				}

			/* Force to right just so we can guranatee end on same pixel */
			pdobj->rubycharjust = rcjRight;	

			lserr = LsdnDistribute(pilsobj->plsc, 
				pdobj->sublnlsdnMain.plsdnStart,
					pdobj->sublnlsdnMain.plsdnEnd, dur);
			}
		}

#endif

	return lserr;	
}

/* R U B Y G E T S P E C I A L E F F E C T S I N S I D E */
/*----------------------------------------------------------------------------
	%%Function: RubyGetSpecialEffectsInside
	%%Contact: ricksa

		GetSpecialEffectsInside

		.

----------------------------------------------------------------------------*/
LSERR WINAPI RubyGetSpecialEffectsInside(
	PDOBJ pdobj,				/* (IN): dobj */
	UINT *pEffectsFlags)		/* (OUT): Special effects for this object */
{
	LSERR lserr = LsGetSpecialEffectsSubline(pdobj->plssublMain, pEffectsFlags);

	if (lserrNone == lserr)
		{
		UINT uiSpecialEffectsRuby;
	
		lserr = LsGetSpecialEffectsSubline(pdobj->plssublRuby, &uiSpecialEffectsRuby);

		*pEffectsFlags |= uiSpecialEffectsRuby;
		}

	return lserr;
}

/* R U B Y C A L C P R E S E N T A T I O N */
/*----------------------------------------------------------------------------
	%%Function: RubyCalcPresentation
	%%Contact: ricksa

		CalcPresentation
	
		This has two jobs. First, it prepares each line for presentation. Then,
		it calculates the positions of the lines in output device coordinates.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyCalcPresentation(
	PDOBJ pdobj,				/* (IN): dobj */
	long dup,					/* (IN): dup of dobj */
	LSKJUST lskjust,			/* (IN): Justification type */
	BOOL fLastVisibleOnLine )	/* (IN): Is this object last visible on line? */
{
	PILSOBJ pilsobj = pdobj->pilsobj;
	LSERR lserr = lserrNone;
	long durOffsetMain;
	long durOffsetRuby;
	long durDiff = pdobj->durDiff;

	Unreferenced (lskjust);
	Unreferenced(dup);
		
	/*
	 *	Prepare lines for presentation
	 */

	if (pdobj->fSpecialLineStartEnd && !pdobj->fFirstOnLine && fLastVisibleOnLine)
		{
		pdobj->rubycharjust = rcjRight;	
		};

	lserr = LsMatchPresSubline(pdobj->plssublMain);

	if (lserr != lserrNone)
		{
		return lserr;
		}

	lserr = LsMatchPresSubline(pdobj->plssublRuby);

	if (lserr != lserrNone)
		{
		return lserr;
		}

	/*
	 *	Calculate positions of lines
	 */

	if (pdobj->fFirstOnLine && pdobj->fSpecialLineStartEnd)
		{
		durDiff = 0;
		}

	durOffsetMain = pdobj->durModBefore;

	/* Calculate amount to adjust in reference */
	if ((durDiff < 0) && (pdobj->rubycharjust != rcjLeft))
		{
		/* Ruby line overhangs main line */
		durOffsetMain -= durDiff;
		}

	pdobj->dupOffsetMain = UpFromUr(pdobj->lstflow, (&pilsobj->lsdevres), 
		durOffsetMain);

	durOffsetRuby = pdobj->durModBefore;

	if (durDiff > 0)
		{
		/* Main line underhangs ruby line */
		durOffsetRuby += durDiff;
		}

	pdobj->dupOffsetRuby = UpFromUr(pdobj->lstflow, (&pilsobj->lsdevres), 
		durOffsetRuby);

	if (rcjRight == pdobj->rubycharjust)
		{
		/*
		 * There can be a pixel rounding error in the above calculations
		 * so that we massage the above calculations so that when the
		 * adjustment is right, both lines are guaranteed to end of the
		 * same pixel.
		 */
		MassageForRightAdjust(pdobj);
		}

	return lserr;
}

/* R U B Y Q U E R Y P O I N T P C P */
/*----------------------------------------------------------------------------
	%%Function: RubyQueryPointPcp
	%%Contact: ricksa

		Map dup to dcp

		There is a certain trickiness about how we determine which subline
		to query. Because the client specifies the offsets, the sublines
		can actually wind up anywhere. We use the simple algorithm that
		if the query does not fall into the Ruby pronunciation line, they
		actually mean the main line of text.
----------------------------------------------------------------------------*/
LSERR WINAPI RubyQueryPointPcp(
	PDOBJ pdobj,				/*(IN): dobj to query */
	PCPOINTUV ppointuvQuery,	/*(IN): query point (uQuery,vQuery) */
	PCLSQIN plsqin,				/*(IN): query input */
	PLSQOUT plsqout)			/*(OUT): query output */
{
	PLSSUBL plssubl;
 	long dupAdj;
	long dvpAdj;
	long dvpRubyOffset = pdobj->dvpRubyOffset;

	/*
	 * Decide which line to to return based on the height of the point input
	 */

	/* Assume main line */
	plssubl = pdobj->plssublMain;
	dupAdj = pdobj->dupOffsetMain;
	dvpAdj = 0;

	if ((ppointuvQuery->v > (dvpRubyOffset - pdobj->objdimRuby.heightsPres.dvDescent))
		&& (ppointuvQuery->v <= (dvpRubyOffset + pdobj->objdimRuby.heightsPres.dvAscent)))
		{
		/* hit second line */
		plssubl = pdobj->plssublRuby;
		dupAdj = pdobj->dupOffsetRuby;
		dvpAdj = pdobj->dvpRubyOffset;
		}

	return CreateQueryResult(plssubl, dupAdj, dvpAdj, plsqin, plsqout);
}
	
/* R U B Y Q U E R Y C P P P O I N T */
/*----------------------------------------------------------------------------
	%%Function: RubyQueryCpPpoint
	%%Contact: ricksa

		Map dcp to dup

		If client wants all text treated as a single object, then the handler
		just returns the object dimensions. Otherwise, we calculate the line to
		query and ask that line for the dimensions of the dcp.
----------------------------------------------------------------------------*/
LSERR WINAPI RubyQueryCpPpoint(
	PDOBJ pdobj,				/*(IN): dobj to query, */
	LSDCP dcp,					/*(IN): dcp for the query */
	PCLSQIN plsqin,				/*(IN): query input */
	PLSQOUT plsqout)			/*(OUT): query output */
{
	PLSSUBL plssubl;
 	long dupAdj;
	long dvpAdj;
	BOOL fMain = fFalse;

	LSCP cpQuery = pdobj->cpStart + dcp;

	/*
	 *	Calculate subline to query
	 */

	/* Assume ruby line */
	plssubl = pdobj->plssublRuby;
	dupAdj = pdobj->dupOffsetRuby;
	dvpAdj = pdobj->dvpRubyOffset;

	/* + 1 means we include the cp of the object in the Ruby pronunciation line. */
	if (RubyPronunciationLineFirst == pdobj->pilsobj->rubysyntax)
		{
		/* Ruby pronunciation line is first */
		if (cpQuery >= pdobj->cpStartMain)
			{
			fMain = fTrue;
			}
		}
	else
		{
		/* Main text line is first */
		if (cpQuery < pdobj->cpStartRuby)
			{
			fMain = fTrue;
			}
		}

	if (fMain)
		{
		plssubl = pdobj->plssublMain;
		dupAdj = pdobj->dupOffsetMain;
		dvpAdj = pdobj->dvpMainOffset;
		}

	return CreateQueryResult(plssubl, dupAdj, dvpAdj, plsqin, plsqout);
}

	
/* R U B I D I S P L A Y */
/*----------------------------------------------------------------------------
	%%Function: RubyDisplay
	%%Contact: ricksa

		Display

		This calculates the positions of the various lines for the
		display and then displays them.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyDisplay(
	PDOBJ pdobj,				/*(IN): dobj to display */
	PCDISPIN pcdispin)			/*(IN): display info */
{
	LSERR lserr;
	LSTFLOW lstflow = pcdispin->lstflow;
	UINT kDispMode = pcdispin->kDispMode;
	POINT ptLine;

	/* Calculate point to start displaying main line. */
	GetMainPoint(pdobj, &pcdispin->ptPen, lstflow, &ptLine);

	/* display first line */
	lserr = LsDisplaySubline(pdobj->plssublMain, &ptLine, kDispMode,
		pcdispin->prcClip);

	if (lserr != lserrNone)
		{
		return lserr;
		}

	/* Calculate point to start displaying ruby line. */
	GetRubyPoint(pdobj, &pcdispin->ptPen, lstflow, &ptLine);

	/* display ruby line */
	return LsDisplaySubline(pdobj->plssublRuby, &ptLine, kDispMode, 
		pcdispin->prcClip);
}

/* R U B I D E S T R O Y D O B J */
/*----------------------------------------------------------------------------
	%%Function: RubyDestroyDobj
	%%Contact: ricksa

		DestroyDobj

		Free all resources connected with the input dobj.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyDestroyDobj(
	PDOBJ pdobj)				/*(IN): dobj to destroy */
{
	return RubyFreeDobj (pdobj);
}

/* R U B Y E N U M */
/*----------------------------------------------------------------------------
	%%Function: RubyEnum
	%%Contact: ricksa

		Enum

		Enumeration callback - passed to client.
	
----------------------------------------------------------------------------*/
LSERR WINAPI RubyEnum(
	PDOBJ pdobj,				/*(IN): dobj to enumerate */
	PLSRUN plsrun,				/*(IN): from DNODE */
	PCLSCHP plschp,				/*(IN): from DNODE */
	LSCP cp,					/*(IN): from DNODE */
	LSDCP dcp,					/*(IN): from DNODE */
	LSTFLOW lstflow,			/*(IN): text flow*/
	BOOL fReverse,				/*(IN): enumerate in reverse order */
	BOOL fGeometryNeeded,		/*(IN): */
	const POINT *pt,			/*(IN): starting position (top left), iff fGeometryNeeded */
	PCHEIGHTS pcheights,		/*(IN): from DNODE, relevant iff fGeometryNeeded */
	long dupRun)				/*(IN): from DNODE, relevant iff fGeometryNeeded */
{
	POINT ptMain;
	POINT ptRuby;
	long dupMain = 0;
	long dupRuby = 0;
	LSERR lserr;
	LSTFLOW lstflowIgnored;

	if (fGeometryNeeded)
		{
		GetMainPoint(pdobj, pt, lstflow, &ptMain);
		GetRubyPoint(pdobj, pt, lstflow, &ptMain);
		lserr = LssbGetDupSubline(pdobj->plssublMain, &lstflowIgnored, &dupMain);
		AssertSz(lserrNone == lserr, "RubyEnum - can't get dup for main");
		lserr = LssbGetDupSubline(pdobj->plssublRuby, &lstflowIgnored, &dupRuby);
		AssertSz(lserrNone == lserr, "RubyEnum - can't get dup for ruby");
		}

	return pdobj->pilsobj->rcbk.pfnRubyEnum(pdobj->pilsobj->pols, plsrun, 
		plschp, cp, dcp, lstflow, fReverse, fGeometryNeeded, pt, pcheights, 
			dupRun, &ptMain, &pdobj->objdimMain.heightsPres, dupMain, &ptRuby, 
				&pdobj->objdimRuby.heightsPres, dupRuby, pdobj->plssublMain,
					pdobj->plssublRuby);
}
	
	

/* R U B I H A N D L E R I N I T */
/*----------------------------------------------------------------------------
	%%Function: RubyHandlerInit
	%%Contact: ricksa

		Initialize global Ruby data and return LSIMETHODS.
	
----------------------------------------------------------------------------*/
LSERR WINAPI LsGetRubyLsimethods(
	LSIMETHODS *plsim)
{
	plsim->pfnCreateILSObj = RubyCreateILSObj;
	plsim->pfnDestroyILSObj = RubyDestroyILSObj;
	plsim->pfnSetDoc = RubySetDoc;
	plsim->pfnCreateLNObj = RubyCreateLNObj;
	plsim->pfnDestroyLNObj = RubyDestroyLNObj;
	plsim->pfnFmt = RubyFmt;
	plsim->pfnFmtResume = ObjHelpFmtResume;
	plsim->pfnGetModWidthPrecedingChar = RubyGetModWidthPrecedingChar;
	plsim->pfnGetModWidthFollowingChar = RubyGetModWidthFollowingChar;
	plsim->pfnTruncateChunk = SobjTruncateChunk;
	plsim->pfnFindPrevBreakChunk = SobjFindPrevBreakChunk;
	plsim->pfnFindNextBreakChunk = SobjFindNextBreakChunk;
	plsim->pfnForceBreakChunk = SobjForceBreakChunk;
	plsim->pfnSetBreak = RubySetBreak;
	plsim->pfnGetSpecialEffectsInside = RubyGetSpecialEffectsInside;
	plsim->pfnFExpandWithPrecedingChar = ObjHelpFExpandWithPrecedingChar;
	plsim->pfnFExpandWithFollowingChar = ObjHelpFExpandWithFollowingChar;
	plsim->pfnCalcPresentation = RubyCalcPresentation;
	plsim->pfnQueryPointPcp = RubyQueryPointPcp;
	plsim->pfnQueryCpPpoint = RubyQueryCpPpoint;
	plsim->pfnDisplay = RubyDisplay;
	plsim->pfnDestroyDObj = RubyDestroyDobj;
	plsim->pfnEnum = RubyEnum;
	return lserrNone;
}