windows-nt/Source/XPSP1/NT/windows/richedit/lssrc/prepdisp.c
2020-09-26 16:20:57 +08:00

1957 lines
56 KiB
C

#include <limits.h>
#include "prepdisp.h"
#include "lsc.h"
#include "lsline.h"
#include "lssubl.h"
#include "iobj.h"
#include "lstxtjst.h"
#include "lstxttab.h"
#include "lsgrchnk.h"
#include "posichnk.h"
#include "chnutils.h"
#include "tabutils.h"
#include "lsdnode.h"
#include "zqfromza.h"
#include "lsdevice.h"
#include "lssubset.h"
#include "lsffi.h"
#include "iobjln.h"
#include "txtconst.h"
#include "lskalign.h"
#include "dninfo.h"
typedef enum /* types of TextGroupChunk walls */
{
LineBegin,
LineEnd,
Tab,
Pen,
} KWALL;
typedef struct
{
KWALL kwall; /* wall type */
PLSDNODE pdn; /* tab or pen dnode, PLINEBEGIN for LineBegin */
LSKTAB lsktab; /* if type is Tab - kind of tab */
WCHAR wchCharTab; /* point character if lsktab == lsktChar */
long upTab; /* scaled tab position */
} GrpChnkWall;
static BOOL DnodeHasSublineForMe(PLSDNODE pdn, BOOL fLineCompressed);
static void ScaleDownLevel(PLSSUBL plssubl, BOOL* pfAnySublines, BOOL* pfCollectVisual);
static LSERR SetJustificationForLastGroupChunk(PLSLINE plsline, GrpChnkWall LastWall,
LSKJUST* plskj, LSKALIGN* plskalign);
static LSERR CalcPresAutonumbers(PLSLINE plsline, PLSDNODE* pdnStartMainText);
static void FindWallToCollectSublinesAfter(PLSDNODE pdnFirst, LSCP cpLim, BOOL fLineCompressed, PLSDNODE* ppdnLastWall);
static LSERR GetDistanceToTabPoint(GRCHUNKEXT* pgrchunkext, LSCP cpLim, LSKTAB lsktab, WCHAR wchCharTab,
PLSDNODE pdnFirst, long* pdupToDecimal);
static void WidenNonTextObjects(GRCHUNKEXT* pgrchunkext, long dupToAdd, DWORD cObjects);
static void ConvertAutoTabToPen(PLSLINE plsline, PLSDNODE pdnAutoDecimalTab);
static LSERR CalcPresForDnodeWithSublines(PLSC plsc, PLSDNODE pdn, BOOL fLineCompressed,
LSKJUST lskj, BOOL fLastOnLine);
static LSERR CalcPresChunk(PLSC plsc, PLSDNODE pdnFirst, PLSDNODE pdnLim,
COLLECTSUBLINES CollectGroupChunkPurpose, BOOL fLineCompressed,
LSKJUST lskj, BOOL fLastOnLine);
static void UpdateUpLimUnderline(PLSLINE plsline, long dupTail);
static LSERR PrepareLineForDisplay(PLSLINE plsline);
#define PLINEBEGIN ((void *)(-1))
#define FIsWall(p, cpLim) (!FDnodeBeforeCpLim(p, cpLim) || p->fTab || FIsDnodePen(p))
#define FIsDnodeNormalPen(plsdn) (FIsDnodePen(plsdn) && (!(plsdn)->fAdvancedPen))
#define FCollinearTflows(t1, t2) (((t1) & fUVertical) == ((t2) & fUVertical))
// %%Function: DnodeHasSublineForMe
// %%Contact: victork
//
// Is there relevant subline in this dnode?
static BOOL DnodeHasSublineForMe(PLSDNODE pdn, BOOL fLineCompressed)
{
BOOL fSublineFound = fFalse;
if (FIsDnodeReal(pdn) && pdn->u.real.pinfosubl != NULL)
{
if (pdn->u.real.pinfosubl->fUseForCompression && fLineCompressed)
{
fSublineFound = fTrue;
}
if (pdn->u.real.pinfosubl->fUseForJustification && !fLineCompressed)
{
fSublineFound = fTrue;
}
}
return fSublineFound;
}
// %%Function: ScaleDownLevel
// %%Contact: victork
//
/*
* Scales all non-text objects on the level(s).
*
* If the level (meaning subline) contains dnode(s) which submitted sublines for compression
* or expansion, ScaleDownLevel reports the fact and calls itself for submitted sublines.
* This strategy relyes on the fact that ScaleDownLevel is idempotent procedure. Some sublines
* will be scaled down twice - let that be.
*
* Two additional questions are answered - whether there are some submitted sublines and
* whether there is a reason go VisualLine (underlining, shading, borders on lower levels).
*/
static void ScaleDownLevel(PLSSUBL plssubl, BOOL* pfAnySublines, BOOL* pfCollectVisual)
{
const PLSC plsc = plssubl->plsc;
LSTFLOW lstflow = plssubl->lstflow;
const DWORD iobjText = IobjTextFromLsc(&(plsc->lsiobjcontext));
LSDEVRES* pdevres = &(plsc->lsdocinf.lsdevres);
PLSDNODE pdn = plssubl->plsdnFirst;
DWORD i;
BOOL fDummy;
BOOL fSeeReasonForVisualLine = fFalse;
while (pdn != NULL) /* don't care about break */
{
if (FIsDnodeReal(pdn))
{
if (pdn->u.real.lschp.fUnderline || pdn->u.real.lschp.fShade || pdn->u.real.lschp.fBorder)
{
fSeeReasonForVisualLine = fTrue;
}
if (IdObjFromDnode(pdn) == iobjText)
{
if (pdn->fTab)
pdn->u.real.dup = UpFromUr(lstflow, pdevres, pdn->u.real.objdim.dur);
}
else
{
if (!pdn->fRigidDup)
{
pdn->u.real.dup = UpFromUr(lstflow, pdevres, pdn->u.real.objdim.dur);
}
if (pdn->u.real.pinfosubl != NULL)
{
*pfAnySublines = fTrue;
for (i = 0; i < pdn->u.real.pinfosubl->cSubline; i++)
{
ScaleDownLevel((pdn->u.real.pinfosubl->rgpsubl)[i], &fDummy, pfCollectVisual);
}
}
}
}
else if (FIsDnodePen(pdn))
{
pdn->u.pen.dup = UpFromUr(lstflow, pdevres, pdn->u.pen.dur);
}
else
{
// Borders are rigidDup always - no scaling down
// we'll try to "undo" the moving at display time at the main level if
// fUnderlineTrailSpacesRM is on. So, after prepdisp we want none or only one
// fBorderMovedFromTrailingArea flag remain and the meaning of the flag is:
// I am the border that should be moved back into trailing spaces.
if (pdn->fBorderMovedFromTrailingArea)
{
if (!FIsSubLineMain(pdn->plssubl) ||
!plsc->lsadjustcontext.fUnderlineTrailSpacesRM)
{
pdn->fBorderMovedFromTrailingArea = fFalse;
}
}
}
pdn = pdn->plsdnNext;
}
if (fSeeReasonForVisualLine && !plssubl->fMain)
{
*pfCollectVisual = fTrue;
}
}
// %%Function: FindWallToCollectSublinesAfter
// %%Contact: victork
//
// Finds the last wall - wall after which we will start to use submitted subllines.
// If there are no sublines to participate in justification, pdnLastWall is set to null,
// else it points to the last wall (tab, pen or PLINEBEGIN).
static void FindWallToCollectSublinesAfter(PLSDNODE pdnFirst, LSCP cpLim, BOOL fLineCompressed,
PLSDNODE* ppdnLastWall)
{
PLSDNODE pdn;
BOOL fSublineFound;
// Find last tab.
*ppdnLastWall = PLINEBEGIN;
pdn = pdnFirst;
while (FDnodeBeforeCpLim(pdn, cpLim))
{
if (FIsDnodeReal(pdn))
{
if (pdn->fTab)
{
*ppdnLastWall = pdn;
}
}
else /* pen */
if (!FIsDnodeBorder(pdn) && !pdn->fAdvancedPen) // and not advance pen or border
{
*ppdnLastWall = pdn;
}
pdn = pdn->plsdnNext;
}
// OK, last groupchunk starts with a tab or there is only one groupchunk on the line.
// Are there submitted sublines of our compression/expansion type after it?
fSublineFound = fFalse;
if (*ppdnLastWall == PLINEBEGIN)
{
pdn = pdnFirst;
}
else
{
pdn = (*ppdnLastWall)->plsdnNext;
}
while (FDnodeBeforeCpLim(pdn, cpLim))
{
fSublineFound |= DnodeHasSublineForMe(pdn, fLineCompressed);
pdn = pdn->plsdnNext;
}
if (!fSublineFound)
{
*ppdnLastWall = NULL; // don't need last tab
}
return;
}
// %%Function: CalcPresAutonumbers
// %%Contact: victork
//
/*
* Scales dup for autonumbering dnodes, calls CalcPres for autonumbering object.
*
* We want to have main line start exactly on upStartMainText. To achive that we play with
* the width of "white space" dnode, which today contains a tab (usually) or a space.
* (This dnode is pdnWhiteSpace in the code.) If it is not present, we change width of autonumbering
* object itself. We don't want one of them go negative,so sometimes rounding errors force us
* to move start of the main text to the right.
*/
static LSERR CalcPresAutonumbers(PLSLINE plsline, PLSDNODE* pdnStartMainText)
{
LSERR lserr;
const PLSC plsc = plsline->lssubl.plsc;
LSTFLOW lstflow = plsline->lssubl.lstflow;
LSDEVRES* pdevres = &(plsc->lsdocinf.lsdevres);
PLSDNODE pdn, pdnObject, pdnWhiteSpace, pdnToAdjust, pdnAfterAutonumbers;
long dupAdjust, dupClosingBorder = 0;
long dupAutonumbering = 0;
plsline->upStartAutonumberingText = UpFromUr(lstflow, pdevres, plsc->lsadjustcontext.urStartAutonumberingText);
// Find the first dnode after autonumbering sequence
// First find the first dnode with positive cpFirst
pdn = plsline->lssubl.plsdnFirst;
Assert(pdn != NULL && FIsNotInContent(pdn));
pdnAfterAutonumbers = pdn->plsdnNext;
while (pdnAfterAutonumbers != NULL && FIsNotInContent(pdnAfterAutonumbers))
{
pdn = pdnAfterAutonumbers;
pdnAfterAutonumbers = pdn->plsdnNext;
}
// pdnAfterAutonumbers is first dnode in content (with positive cpFirst). It can be NULL.
// But it is not the first dnode after autonumbering sequence if autodecimal tab is present
if (plsc->lsadjustcontext.fAutodecimalTabPresent)
{
Assert(FIsDnodeReal(pdn) && pdn->fTab);
pdnAfterAutonumbers = pdn;
}
// Now go againg through autonumbering sequence
// process opening border
pdn = plsline->lssubl.plsdnFirst;
if (FIsDnodeBorder(pdn))
{
Assert(pdn->cpFirst < 0);
Assert(pdn->fOpenBorder);
pdnObject = pdn->plsdnNext;
dupAutonumbering += pdn->u.pen.dup;
}
else
{
pdnObject = pdn;
}
// process B&N object
Assert(pdnObject != NULL && pdnObject->cpFirst < 0); // B&N object should be there
Assert(FIsDnodeReal(pdnObject));
// scale down dup from dur for the first dnode
pdnObject->u.real.dup = UpFromUr(lstflow, pdevres, pdnObject->u.real.objdim.dur);
dupAutonumbering += pdnObject->u.real.dup;
pdn = pdnObject->plsdnNext;
Assert(pdn != NULL); // line must contain something after B&N dnodes
// process "white space" dnode
if (pdn != pdnAfterAutonumbers && FIsDnodeReal(pdn))
{
pdnWhiteSpace = pdn;
dupAutonumbering += pdnWhiteSpace->u.real.dup;
pdnToAdjust = pdnWhiteSpace;
pdn = pdnWhiteSpace->plsdnNext;
}
else
{
pdnWhiteSpace = NULL;
pdnToAdjust = pdnObject;
}
Assert(pdn != NULL); // line must contain something after B&N dnodes
// process closing border
if (pdn != pdnAfterAutonumbers)
{
Assert(FIsDnodeBorder(pdn));
Assert(!pdn->fOpenBorder);
dupClosingBorder = pdn->u.pen.dup;
dupAutonumbering += dupClosingBorder;
pdn = pdn->plsdnNext;
}
Assert(pdn == pdnAfterAutonumbers);
*pdnStartMainText = pdn;
// change dup of the tab or object dnode to ensure exact main text alignment
dupAdjust = plsline->upStartMainText - plsline->upStartAutonumberingText - dupAutonumbering;
pdnToAdjust->u.real.dup += dupAdjust;
if (pdnToAdjust->u.real.dup < 0)
{
// Rounding errors result in negative dup - better to move starting point of the main line.
// It can lead to the nasty situation of right margin to the left of the line beginning in
// theory, but not in practice. This problem is ignored then.
plsline->upStartMainText -= pdnToAdjust->u.real.dup;
pdnToAdjust->u.real.dup = 0;
}
// do CalcPres for the autonumbering object - it's always lskjNone and not last object on the line
lserr = (*plsc->lsiobjcontext.rgobj[pdnObject->u.real.lschp.idObj].lsim.pfnCalcPresentation)
(pdnObject->u.real.pdobj, pdnObject->u.real.dup, lskjNone, fFalse);
if (lserr != lserrNone)
return lserr;
if (pdnWhiteSpace != NULL)
{
plsline->upLimAutonumberingText = plsline->upStartMainText -
pdnWhiteSpace->u.real.dup - dupClosingBorder;
// If "white space" dnode is not a tab, dup should be set in it.
if (!pdnWhiteSpace->fTab)
{
// It's always lskjNone and not last object on the line for white space dnode
lserr = (*plsc->lsiobjcontext.rgobj[pdnWhiteSpace->u.real.lschp.idObj].lsim.pfnCalcPresentation)
(pdnWhiteSpace->u.real.pdobj, pdnWhiteSpace->u.real.dup, lskjNone, fFalse);
if (lserr != lserrNone)
return lserr;
}
}
else
{
plsline->upLimAutonumberingText = plsline->upStartMainText - dupClosingBorder;
}
return lserrNone;
}
// %%Function: SetJustificationForLastGroupChunk
// %%Contact: victork
//
// Changes lskj and lskalign for the last GC if it should be done
// If not, it's OK to leave these parameters unchanged - so they are kind of I/O
//
// We adjust all group chunks except maybe the last one with lskjNone, so guestion is about
// last GroupChunk only.
//
// We do some tricks with justification mode at the end of line, and the answer depends on
// kind of last tab, end of paragraph, etc.
//
static LSERR SetJustificationForLastGroupChunk(PLSLINE plsline, GrpChnkWall LastWall,
LSKJUST* plskj, LSKALIGN* plskalign)
{
LSERR lserr;
const PLSC plsc = plsline->lssubl.plsc;
LSKJUST lskjPara = plsc->lsadjustcontext.lskj;
LSKALIGN lskalignPara = plsc->lsadjustcontext.lskalign;
ENDRES endr = plsline->lslinfo.endr;
BOOL fJustify;
// no justification intended - lskj remains None, lskalign unchanged
if ((lskjPara == lskjNone || lskjPara == lskjSnapGrid) && lskalignPara == lskalLeft)
{
return lserrNone;
}
// Line ends in a normal way - we apply justification, lskalign unchanged
if (endr == endrNormal || endr == endrHyphenated)
{
*plskj = lskjPara;
return lserrNone;
}
// break-through tab kills justification, alignment games
if (FBreakthroughLine(plsc))
{
return lserrNone;
}
// if last Left Wall is non-left tab, justification is off too
if (LastWall.kwall == Tab && LastWall.lsktab != lsktLeft)
{
// we used to return here
// Now we want to give Word a chance to change lskalign from Right to Left for
// the last line in paragraph after text box.
// REVIEW (Victork) Should we call pfnFGetLastLineJustification always?
lskjPara = lskjNone;
}
// What's the matter behind the callback.
//
// They say: no full justification for the last line in paragraph. What does this exactly mean?
// For example, Latin and FE word make different decisions for endrEndSection line.
// Let's ask.
//
// Additional parameter is added to cover the behavior full-justified text wrapping a textbox (bug 682)
// A lone word should be aligned to the right to create a full-justified page, but not at the end of
// the paragraph.
lserr = (*plsc->lscbk.pfnFGetLastLineJustification)(plsc->pols, lskjPara, lskalignPara, endr,
&fJustify, plskalign);
if (lserr != lserrNone) return lserr;
if (fJustify)
{
*plskj = lskjPara;
}
return lserrNone;
}
// %%Function: GetDistanceToTabPoint
// %%Contact: victork
//
/*
* Calculate DistanceToTabPoint given GrpChnk and first Dnode
*
* TabPoint is decimal point for the decimal tab, wchCharTab for character tab
*/
static LSERR GetDistanceToTabPoint(GRCHUNKEXT* pgrchunkext, LSCP cpLim, LSKTAB lsktab, WCHAR wchCharTab,
PLSDNODE pdnFirst, long* pdupToTabPoint)
{
LSERR lserr;
DWORD igrchnk; /* # of dnodes before dnode with the point */
long dupToPointInsideDnode;
PLSDNODE pdnTabPoint;
if (pgrchunkext->durTotal == 0)
{
*pdupToTabPoint = 0;
return lserrNone;
}
lserr = CollectTextGroupChunk(pdnFirst, cpLim, CollectSublinesForDecimalTab, pgrchunkext);
if (lserr != lserrNone)
return lserr;
if (lsktab == lsktDecimal)
{
lserr = LsGetDecimalPoint(&(pgrchunkext->lsgrchnk), lsdevPres, &igrchnk, &dupToPointInsideDnode);
}
else
{
Assert(lsktab == lsktChar);
lserr = LsGetCharTab(&(pgrchunkext->lsgrchnk), wchCharTab, lsdevPres, &igrchnk, &dupToPointInsideDnode);
}
if (lserr != lserrNone)
return lserr;
if (igrchnk == ichnkOutside) // no TabPoint in the whole grpchnk
{
// we say: pretend it is right after last dnode (in logical sequence)
pdnTabPoint = pgrchunkext->plsdnLastUsed;
dupToPointInsideDnode = DupFromDnode(pdnTabPoint);
}
else
{
pdnTabPoint = pgrchunkext->plschunkcontext->pplsdnChunk[igrchnk];
}
// We now have the distance between TabPoint and the beginning of the dnode containing it.
// FindPointOffset will add the dup's of all dnodes before that dnode.
FindPointOffset(pdnFirst, lsdevPres, LstflowFromDnode(pdnFirst), CollectSublinesForDecimalTab,
pdnTabPoint, dupToPointInsideDnode, pdupToTabPoint);
return lserrNone;
}
// %%Function: WidenNonTextObjects
// %%Contact: victork
//
/*
* Add dupToAddToNonTextObjects to the width of first cNonTextObjectsToExtend in the GroupChunk
*/
static void WidenNonTextObjects(GRCHUNKEXT* pgrchunkext, long dupToAdd, DWORD cObjects)
{
PLSDNODE pdn;
long dupAddToEveryone;
long dupDistributeToFew;
long dupAddToThis;
long dupCurrentSum;
DWORD cObjectsLeft, i;
Assert(cObjects != 0);
Assert(dupToAdd > 0);
dupAddToEveryone = dupToAdd / cObjects;
dupDistributeToFew = dupToAdd - (dupAddToEveryone * cObjects);
cObjectsLeft = cObjects;
dupCurrentSum = 0;
/*
* Following loop tries to distribute remaining dupDistributeToFew pixels evenly.
*
* Algorithm would be easy if fractions are allowed and you can see it in comments;
* The actual algorithm avoids fractions by multiplying everything by cObjects
*/
i = 0;
while (cObjectsLeft > 0)
{
Assert(i < pgrchunkext->cNonTextObjects);
pdn = (pgrchunkext->pplsdnNonText)[i];
Assert(pdn != NULL && FIsDnodeReal(pdn) /* && IdObjFromDnode(pdn) != iobjText */ );
if ((pgrchunkext->pfNonTextExpandAfter)[i])
{
dupAddToThis = dupAddToEveryone;
dupCurrentSum += dupDistributeToFew; /* currentSum += Distribute / cObjects; */
if (dupCurrentSum >= (long)cObjects) /* if (currentSum >= 1) */
{
dupAddToThis ++;
dupCurrentSum -= (long)cObjects; /* currentSum--; */
}
pdn->u.real.dup += dupAddToThis;
cObjectsLeft --;
}
i++;
}
return;
}
// %%Function: ConvertAutoTabToPen
// %%Contact: victork
//
static void ConvertAutoTabToPen(PLSLINE plsline, PLSDNODE pdnAutoDecimalTab)
{
long dup, dur;
Assert(pdnAutoDecimalTab->fTab); /* it's still a tab */
dup = pdnAutoDecimalTab->u.real.dup;
dur = pdnAutoDecimalTab->u.real.objdim.dur;
pdnAutoDecimalTab->klsdn = klsdnPenBorder;
pdnAutoDecimalTab->fAdvancedPen = fFalse;
pdnAutoDecimalTab->fTab = fFalse;
pdnAutoDecimalTab->icaltbd = 0;
pdnAutoDecimalTab->u.pen.dup = dup;
pdnAutoDecimalTab->u.pen.dur = dur;
pdnAutoDecimalTab->u.pen.dvp = 0;
pdnAutoDecimalTab->u.pen.dvr = 0;
plsline->fNonRealDnodeEncounted = fTrue;
}
// %%Function: CalcPresForDnodeWithSublines
// %%Contact: victork
//
static LSERR CalcPresForDnodeWithSublines(PLSC plsc, PLSDNODE pdn, BOOL fLineCompressed,
LSKJUST lskj, BOOL fLastOnLine)
{
PLSSUBL* rgpsubl;
DWORD i;
LSTFLOW lstflow; // dummy parameter
LSERR lserr;
long dupSubline;
long dupDnode = 0;
COLLECTSUBLINES CollectGroupChunkPurpose;
Assert(DnodeHasSublineForMe(pdn, fLineCompressed));
// calculate dup for dnode with sublines that took part in justification
if (fLineCompressed)
{
CollectGroupChunkPurpose = CollectSublinesForCompression;
}
else
{
CollectGroupChunkPurpose = CollectSublinesForJustification;
}
rgpsubl = pdn->u.real.pinfosubl->rgpsubl;
for (i = 0; i < pdn->u.real.pinfosubl->cSubline; i++)
{
// fLastOnLine is always false on lower levels
lserr = CalcPresChunk(plsc, rgpsubl[i]->plsdnFirst, rgpsubl[i]->plsdnLastDisplay,
CollectGroupChunkPurpose, fLineCompressed, lskj, fFalse);
if (lserr != lserrNone)
return lserr;
LssbGetDupSubline(rgpsubl[i], &lstflow, &dupSubline);
dupDnode += dupSubline;
(rgpsubl[i])->fDupInvalid = fFalse;
}
// fill dup and call CalcPresentation
pdn->u.real.dup = dupDnode;
lserr = (*plsc->lsiobjcontext.rgobj[pdn->u.real.lschp.idObj].lsim.pfnCalcPresentation)
(pdn->u.real.pdobj, dupDnode, lskj, fLastOnLine);
if (lserr != lserrNone)
return lserr;
return lserrNone;
}
// %%Function: CalcPresChunk
// %%Contact: victork
//
/*
* Calls CalcPresentation for all non-text objects on the chunk.
* That means 1) all dnodes in all GroupChunks (including dnodes in submitted sublines)
* 2) all dnodes that have submitted sublines
*
* Foreign object on the upper level, which is followed only by trailing spaces,
* should be called with fLastOnLine == fTrue.
* Input boolean says whether the input groupchunk is the last on line.
*
* Sets dup for justified sublines
*/
static LSERR CalcPresChunk(PLSC plsc, PLSDNODE pdnFirst, PLSDNODE pdnLast,
COLLECTSUBLINES CollectGroupChunkPurpose, BOOL fLineCompressed,
LSKJUST lskj, BOOL fLastOnLine)
{
LSERR lserr;
const DWORD iobjText = IobjTextFromLsc(&(plsc->lsiobjcontext));
BOOL fCollecting;
PLSDNODE pdn;
long dupTailDnode; // dummy parameter - will not use
DWORD cNumOfTrailers;
fCollecting = (CollectGroupChunkPurpose != CollectSublinesNone);
Assert(pdnFirst != NULL);
Assert(pdnLast != NULL);
pdn = pdnLast;
// go backwards to switch fLastOnLine off once we are not in trailing spaces
for (;;)
{
if (FIsDnodeReal(pdn))
if (IdObjFromDnode(pdn) == iobjText)
{
if (fLastOnLine == fTrue)
{
GetTrailInfoText(pdn->u.real.pdobj, pdn->dcp, &cNumOfTrailers, &dupTailDnode);
if (cNumOfTrailers < pdn->dcp)
{
fLastOnLine = fFalse; // trailing spaces stop here
}
}
}
else
{
if (fCollecting && DnodeHasSublineForMe(pdn, fLineCompressed))
{
lserr = CalcPresForDnodeWithSublines(plsc, pdn, fLineCompressed, lskj, fLastOnLine);
if (lserr != lserrNone)
return lserr;
}
else
{
lserr = (*plsc->lsiobjcontext.rgobj[pdn->u.real.lschp.idObj].lsim.pfnCalcPresentation)
(pdn->u.real.pdobj, pdn->u.real.dup, lskj, fLastOnLine);
if (lserr != lserrNone)
return lserr;
}
fLastOnLine = fFalse;
}
if (pdn == pdnFirst)
{
break;
}
pdn = pdn->plsdnPrev;
Assert(pdn != NULL); // we'll encounter pdnFirst first
}
return lserrNone;
}
// %%Function: UpdateUpLimUnderline
// %%Contact: victork
//
/*
* Change upLimUnderline to underline trailing spaces, but not EOP.
* Notice that from now on upLimUnderline doesn't equals upStartTrailing anymore
*/
static void UpdateUpLimUnderline(PLSLINE plsline, long dupTail)
{
PLSDNODE pdnLast;
plsline->upLimUnderline += dupTail;
// Now EOPs - they are alone in the last dnode or have some borders around them
if (plsline->lslinfo.endr == endrEndPara ||
plsline->lslinfo.endr == endrAltEndPara ||
plsline->lslinfo.endr == endrEndParaSection ||
plsline->lslinfo.endr == endrSoftCR)
{
pdnLast = plsline->lssubl.plsdnLastDisplay;
Assert(FIsDnodeReal(pdnLast)); // no borders in trailing spaces area
Assert(pdnLast->dcp == 1);
Assert(pdnLast->u.real.dup <= dupTail);
plsline->upLimUnderline -= pdnLast->u.real.dup;
pdnLast = pdnLast->plsdnPrev;
}
// This option extends underlining only up to the right margin
if (plsline->upLimUnderline > plsline->upRightMarginJustify)
{
plsline->upLimUnderline = plsline->upRightMarginJustify;
}
}
// %%Function: PrepareLineForDisplayProc
// %%Contact: victork
//
/*
* PrepareLineForDisplayProc fills in the dup's in dnode list and lsline
*
* Input dnode list consists of "normal dnode list" of dnodes with positive non-negative cp,
* which can be preceded (in this order) by B&N sequence either and/or one Autotab dnode.
*
* B&N sequence is OpeningBorder+AutonumberingObject+TabOrSpace+ClosingBorder.
* ClosingBorder or both OpeningBorder and ClosingBorder can be missing. TabOrSpace can be
* missing too. B&N sequence starts at urStartAutonumberingText and ends at urStartMainText.
* Tab in B&N sequence should not be resolved in a usual way.
*
* Autotab dnode has negative cpFirst, but starts at urStartMainText. It is to be resolved in
* a usual way and then be replaced by a pen dnode.
*/
LSERR PrepareLineForDisplayProc(PLSLINE plsline)
{
LSERR lserr;
const PLSC plsc = plsline->lssubl.plsc;
const DWORD iobjText = IobjTextFromLsc(&(plsc->lsiobjcontext));
LSDEVRES* pdevres = &(plsc->lsdocinf.lsdevres);
LSTFLOW lstflow = plsline->lssubl.lstflow; /* text flow of the line */
BOOL fVertical = lstflow & fUVertical;
long dupText, dupTail, dupTailDnode;
DWORD cNumOfTrailers;
PLSDNODE pdn;
BOOL fLastOnLine;
DWORD i;
PDOBJ pdobj[txtobjMaxM]; // quick group chunk
Assert(FIsLSLINE(plsline));
// Next assert means that client should destroy line immediately after creating it
// if fDisplay is set to fFalse in LsSetDoc.
Assert(FDisplay(plsc));
if (!plsline->lssubl.fDupInvalid) /* line has been prepared earlier */
return lserrNone;
Assert(plsc->lsstate == LsStateFree);
Assert(plsc->plslineCur == plsline);
plsc->lsstate = LsStatePreparingForDisplay;
// first try to recognize quick cases, call slow PredDisp otherwise
if (plsc->lsadjustcontext.lskj != lskjNone ||
plsc->lsadjustcontext.lskalign != lskalLeft ||
plsc->lsadjustcontext.lsbrj != lsbrjBreakJustify ||
plsc->lsadjustcontext.fNominalToIdealEncounted ||
plsc->lsadjustcontext.fSubmittedSublineEncounted ||
plsline->fNonRealDnodeEncounted ||
plsline->lssubl.plsdnFirst == NULL ||
FIsNotInContent(plsline->lssubl.plsdnFirst))
{
return PrepareLineForDisplay(plsline);
}
if (plsc->lsdocinf.fPresEqualRef && !FSuspectDeviceDifferent(PlnobjFromLsline(plsline, iobjText)))
{
// Trident quick case - no need to scale down. Dups are already set in text dnodes.
// go through dnode list to calculate dupTrail and CalcPres foreign objects
pdn = plsline->lssubl.plsdnLastDisplay;
dupTail = 0;
fLastOnLine = fTrue;
while (pdn != NULL && IdObjFromDnode(pdn) == iobjText)
{
Assert(pdn->u.real.dup == pdn->u.real.objdim.dur);
GetTrailInfoText(pdn->u.real.pdobj, pdn->dcp, &cNumOfTrailers, &dupTailDnode);
dupTail += dupTailDnode;
if (cNumOfTrailers < pdn->dcp)
{
fLastOnLine = fFalse; // trailing spaces stop here
break; // text is the last on the line
}
pdn = pdn->plsdnPrev;
}
// dupTail is calculated, we still should call pfnCalcPresentation for foreing objects
if (plsc->lsadjustcontext.fForeignObjectEncounted)
{
while (pdn != NULL)
{
Assert(pdn->u.real.dup == pdn->u.real.objdim.dur);
if (IdObjFromDnode(pdn) != iobjText)
{
lserr = (*plsc->lsiobjcontext.rgobj[pdn->u.real.lschp.idObj].lsim.pfnCalcPresentation)
(pdn->u.real.pdobj, pdn->u.real.dup, lskjNone, fLastOnLine);
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
fLastOnLine = fFalse; // only the first coulsd be the last on the line
}
pdn = pdn->plsdnPrev;
}
}
plsline->lssubl.fDupInvalid = fFalse;
plsline->upRightMarginJustify = plsc->lsadjustcontext.urRightMarginJustify;
plsline->upStartMainText = plsc->lsadjustcontext.urStartMainText;
plsline->upStartAutonumberingText = plsline->upStartMainText;
plsline->upLimAutonumberingText = plsline->upStartMainText;
plsline->upLimLine = plsline->lssubl.urCur;
plsline->upStartTrailing = plsline->upLimLine - dupTail;
plsline->upLimUnderline = plsline->upStartTrailing;
plsline->fCollectVisual = fFalse;
if (plsc->lsadjustcontext.fUnderlineTrailSpacesRM &&
plsline->upLimUnderline < plsline->upRightMarginJustify)
{
UpdateUpLimUnderline(plsline, dupTail);
}
plsc->lsstate = LsStateFree;
return lserrNone;
}
if ((plsc->grpfManager & fFmiPresExactSync) != 0 &&
!plsc->lsadjustcontext.fForeignObjectEncounted &&
!plsc->lsadjustcontext.fNonLeftTabEncounted &&
plsline->lssubl.plsdnLastDisplay != NULL && // empty line is not a quick case ;(
FQuickScaling(PlnobjFromLsline(plsline, iobjText), fVertical,
plsline->lssubl.urCur - plsc->lsadjustcontext.urStartMainText))
{
// Looks like Word quick case
// We can still go slow way if all trailing spaces are not in one dnode
if (plsline->lslinfo.endr == endrEndPara)
{
Assert(FIsDnodeReal(plsline->lssubl.plsdnLastDisplay));
Assert(plsline->lssubl.plsdnLastDisplay->dcp == 1);
pdn = plsline->lssubl.plsdnLastDisplay->plsdnPrev;
if (pdn != NULL)
{
GetTrailInfoText(pdn->u.real.pdobj, pdn->dcp, &cNumOfTrailers, &dupTailDnode);
if (cNumOfTrailers > 0)
{
// There are spaces before EOP - go slow way
return PrepareLineForDisplay(plsline);
}
}
cNumOfTrailers = 1;
}
else
{
pdn = plsline->lssubl.plsdnLastDisplay;
GetTrailInfoText(pdn->u.real.pdobj, pdn->dcp, &cNumOfTrailers, &dupTailDnode);
if (cNumOfTrailers == pdn->dcp)
{
// We can't be sure all spaces are in this dnode - forget it then
return PrepareLineForDisplay(plsline);
}
}
// we are sure now that all cNumOfTrailers trailing spaces are in the last dnode
// fill standard output part, upStartMainText will be used below
plsline->lssubl.fDupInvalid = fFalse;
plsline->fCollectVisual = fFalse;
plsline->upRightMarginJustify = UpFromUr(lstflow, pdevres, plsc->lsadjustcontext.urRightMarginJustify);
plsline->upStartMainText = UpFromUr(lstflow, pdevres, plsc->lsadjustcontext.urStartMainText);
plsline->upStartAutonumberingText = plsline->upStartMainText;
plsline->upLimAutonumberingText = plsline->upStartMainText;
if (!plsc->lsadjustcontext.fTabEncounted)
{
// Very nice, we have only one groupchunk to collect
for (pdn = plsline->lssubl.plsdnFirst, i = 0;;)
{
Assert(FIsDnodeReal(pdn));
Assert(IdObjFromDnode(pdn) == iobjText);
// i never gets outside of pdobj array.
// Text makes sure in FQuickscaling
Assert(i < txtobjMaxM);
pdobj[i] = pdn->u.real.pdobj;
i++;
if (pdn == plsline->lssubl.plsdnLastDisplay)
{
break;
}
pdn = pdn->plsdnNext;
}
QuickAdjustExact(&(pdobj[0]), i, cNumOfTrailers, fVertical, &dupText, &dupTail);
plsline->upRightMarginJustify = UpFromUr(lstflow, pdevres, plsc->lsadjustcontext.urRightMarginJustify);
plsline->upStartMainText = UpFromUr(lstflow, pdevres, plsc->lsadjustcontext.urStartMainText);
plsline->upStartAutonumberingText = plsline->upStartMainText;
plsline->upLimAutonumberingText = plsline->upStartMainText;
plsline->upLimLine = plsline->upStartMainText + dupText;
plsline->upStartTrailing = plsline->upLimLine - dupTail;
plsline->upLimUnderline = plsline->upStartTrailing;
plsline->fCollectVisual = fFalse;
if (plsc->lsadjustcontext.fUnderlineTrailSpacesRM &&
plsline->upLimUnderline < plsline->upRightMarginJustify)
{
UpdateUpLimUnderline(plsline, dupTail);
}
plsc->lsstate = LsStateFree;
return lserrNone;
}
else
{
// Tabs are present, but they all are left tabs
pdn = plsline->lssubl.plsdnFirst;
plsline->upLimLine = plsline->upStartMainText;
// Do one QuickGroupChunk after another, moving plsline->upLimLine
for (;;)
{
// loop body: Collect next QuickGroupChunk, deal with it, exit after last one
for (i = 0;;)
{
Assert(FIsDnodeReal(pdn));
Assert(IdObjFromDnode(pdn) == iobjText);
if (pdn->fTab)
{
break;
}
Assert(i < txtobjMaxM);
pdobj[i] = pdn->u.real.pdobj;
i++;
if (pdn == plsline->lssubl.plsdnLastDisplay)
{
break;
}
pdn = pdn->plsdnNext;
}
Assert(pdn == plsline->lssubl.plsdnLastDisplay || pdn->fTab);
if (pdn->fTab)
{
long upTabStop;
if (i == 0)
{
dupText = 0;
dupTail = 0;
}
else
{
QuickAdjustExact(pdobj, i, 0, fVertical, &dupText, &dupTail);
}
Assert(plsc->lstabscontext.pcaltbd[pdn->icaltbd].lskt == lsktLeft);
upTabStop = UpFromUr(lstflow, pdevres, plsc->lstabscontext.pcaltbd[pdn->icaltbd].ur);
pdn->u.real.dup = upTabStop - plsline->upLimLine - dupText;
plsline->upLimLine = upTabStop;
if (pdn == plsline->lssubl.plsdnLastDisplay)
{
break;
}
pdn = pdn->plsdnNext;
}
else
{
Assert(i != 0);
QuickAdjustExact(pdobj, i, cNumOfTrailers, fVertical, &dupText, &dupTail);
plsline->upLimLine += dupText;
break;
}
}
plsline->upStartTrailing = plsline->upLimLine - dupTail;
plsline->upLimUnderline = plsline->upStartTrailing;
if (plsc->lsadjustcontext.fUnderlineTrailSpacesRM &&
plsline->upLimUnderline < plsline->upRightMarginJustify)
{
UpdateUpLimUnderline(plsline, dupTail);
}
plsc->lsstate = LsStateFree;
return lserrNone;
}
}
// Getting here means quick prepdisp haven't happen
return PrepareLineForDisplay(plsline);
}
/*
* This is slow and painstaking procedure that does everyting.
* Called when QuickPrep above cannot cope.
*/
static LSERR PrepareLineForDisplay(PLSLINE plsline)
{
LSERR lserr = lserrNone;
const PLSC plsc = plsline->lssubl.plsc;
LSTFLOW lstflow = plsline->lssubl.lstflow; /* text flow of the subline */
const DWORD iobjText = IobjTextFromLsc(&(plsc->lsiobjcontext));
LSDEVRES* pdevres = &(plsc->lsdocinf.lsdevres);
long urColumnMax = plsc->lsadjustcontext.urRightMarginJustify;
long upColumnMax = UpFromUr(lstflow, pdevres, urColumnMax);
LSCP cpLim = plsline->lssubl.cpLimDisplay;
PLSDNODE pdnFirst = plsline->lssubl.plsdnFirst; /* the first dnode of the line */
PLSDNODE pdnAutoDecimalTab = NULL; /* NULL means - no such a thing on the line */
GRCHUNKEXT grchunkext;
BOOL fEmptyGroupChunk;
PLSDNODE pdnLastWall = NULL; /* last wall with submitted sublines after it */
BOOL fAnySublines = fFalse;
COLLECTSUBLINES CollectGroupChunkPurpose = (plsc->lsadjustcontext.fLineCompressed) ?
CollectSublinesForCompression : CollectSublinesForJustification;
// parameters to call AdjustText
LSKJUST lskj = lskjNone; /* These four will be changed only when calling */
BOOL fForcedBreak = fFalse; /* AdjustText last time on the line */
BOOL fSuppressTrailingSpaces = fFalse; /* if ever */
LSKALIGN lskalign = plsc->lsadjustcontext.lskalign; // Alignment can be changed too
long dupAvailable;
BOOL fExact;
BOOL fSuppressWiggle;
long dupText, dupTail = 0, dupToAddToNonTextObjects;
long durColumnMax;
DWORD cNonTextObjectsToExtend;
PLSDNODE pdnNextFirst; /* first Dnode of the next GrpChnk */
GrpChnkWall LeftWall, RightWall; /* current TextGroupChunk walls */
long upLeftWall,urLeftWall; /* Left wall position */
long dupWall, durWall;
long dupGrpChnk;
long dupToTabPoint;
long dupJustifyLine;
LSKTAB lsktabLast = lsktLeft;
long dupLastTab = 0;
long upLeftWallForCentering;
PLSDNODE pdnLast;
InitGroupChunkExt(plsline->lssubl.plschunkcontext, iobjText, &grchunkext); /* prepare one GRCHUNKEXT for all */
plsline->upStartMainText = UpFromUr(lstflow, pdevres, plsc->lsadjustcontext.urStartMainText);
// set defaults incase there are no autonumbering
plsline->upStartAutonumberingText = plsline->upStartMainText;
plsline->upLimAutonumberingText = plsline->upStartMainText;
// fCollectVisual can be reset to fTrue by ScaleDownLevel called here or in AdjustSubline
plsline->fCollectVisual = fFalse;
if (!plsline->fAllSimpleText)
{
/* straighforward scaling down of non-text objects */
ScaleDownLevel(&(plsline->lssubl), &fAnySublines, &(plsline->fCollectVisual));
if (plsc->lsadjustcontext.fLineContainsAutoNumber)
{
// do dup setting for autonumbers, update pdnFirst to point after it
lserr = CalcPresAutonumbers(plsline, &pdnFirst);
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
}
// If autodecimal tab is there, pdnFirst points at it - make a note.
// This tab can only be just before main text and it has negative cpFirst
// Check for NULL is needed because empty dnode list is possible in LS.
// We don't have a dnode for splat, so we'll get here with pdnFirst == NULL
// when line is (object that said delete me) + splat
if (plsc->lsadjustcontext.fAutodecimalTabPresent)
{
Assert(pdnFirst != NULL && FIsNotInContent(pdnFirst) && pdnFirst->fTab);
// It doesn't need any special handling even having negative cpFirst
// We note it to convert to pen later
pdnAutoDecimalTab = pdnFirst;
}
if (fAnySublines)
{
// Find last tab and prepare sublines after it.
FindWallToCollectSublinesAfter(pdnFirst, cpLim, plsc->lsadjustcontext.fLineCompressed,
&pdnLastWall);
}
}
/*
* Loop structure : While !end_of_line do
* {
* get next Wall (collect GrpChnk);
* adjust GrpChnk;
* set dup of the tab to the left of the GrpChnk;
* move one Wall to the right
* }
*
* Invariance: all dup before LeftWall are done.
* upLeftWall is at the beginning of the left wall
* pdnNextFirst is the dnode to start collecting next GrpChnk with
*/
pdnNextFirst = pdnFirst;
LeftWall.kwall = LineBegin;
LeftWall.pdn = PLINEBEGIN;
LeftWall.lsktab = lsktLeft; // 4 lines just against asserts
LeftWall.wchCharTab = 0;
LeftWall.upTab = 0;
RightWall = LeftWall;
upLeftWall = 0;
urLeftWall = 0;
while (LeftWall.kwall != LineEnd)
{
/* 1. Find next wall (collect GrpChnk or skip collecting if two walls in a row)
*
* Input: pdnNextFirst - first dnode after Left wall
*
* Output: RightWall.pdn & grchunkext.
* if there is no GrpChnk some zeros in grchunkext is enough
*/
if (FIsWall(pdnNextFirst, cpLim))
{
fEmptyGroupChunk = fTrue;
RightWall.pdn = pdnNextFirst;
grchunkext.durTotal = 0;
grchunkext.durTextTotal = 0;
grchunkext.dupNonTextTotal = 0;
}
else
{
lserr = CollectTextGroupChunk(pdnNextFirst, cpLim, CollectGroupChunkPurpose, &grchunkext);
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
if (grchunkext.lsgrchnk.clsgrchnk == 0 && grchunkext.cNonTextObjects == 0)
{
// only borders in this groupchunk - no need to call AdjustText
fEmptyGroupChunk = fTrue;
grchunkext.durTextTotal = 0;
}
else
{
fEmptyGroupChunk = fFalse;
}
RightWall.pdn = grchunkext.plsdnNext;
}
/*
* 2. fill in Right Wall information
*
* Input: RightWall.pdn
*
* Output: pdnNextFirst, RightWall information.
*/
if (!FDnodeBeforeCpLim(RightWall.pdn, cpLim))
{
RightWall.kwall = LineEnd;
}
else
{
Assert(FIsWall(RightWall.pdn, cpLim));
pdnNextFirst = RightWall.pdn->plsdnNext;
if (FIsDnodePen(RightWall.pdn))
{
RightWall.kwall = Pen;
}
else
{
Assert(RightWall.pdn->fTab); /* it must be a tab */
RightWall.kwall = Tab;
RightWall.lsktab = plsc->lstabscontext.pcaltbd[RightWall.pdn->icaltbd].lskt;
RightWall.wchCharTab = plsc->lstabscontext.pcaltbd[RightWall.pdn->icaltbd].wchCharTab;
RightWall.upTab = UpFromUr(lstflow, pdevres, plsc->lstabscontext.pcaltbd[RightWall.pdn->icaltbd].ur);
}
}
/*
* prepare parameters for AdjustText
*
* Input: LeftWall, urLeftWall, upLeftWall, is_it_the_last_one
*
* Output: durColumnMax; lskj, dupAvailable and other input parameters for AdjustText
*
*/
if (RightWall.kwall != LineEnd)
{
if (RightWall.kwall == Tab && RightWall.lsktab == lsktLeft)
{
// Now we know for sure what space we have for text in this groupchunk
// and can do decent job if client doesn't care about fExact
long upLeft, urLeft, upRight, urRight;
urRight = plsc->lstabscontext.pcaltbd[RightWall.pdn->icaltbd].ur;
upRight = UpFromUr(lstflow, pdevres, urRight);
if (LeftWall.kwall == Tab && LeftWall.lsktab == lsktLeft)
{
urLeft = plsc->lstabscontext.pcaltbd[LeftWall.pdn->icaltbd].ur;
upLeft = UpFromUr(lstflow, pdevres, urLeft);
}
else if (LeftWall.kwall == LineBegin)
{
urLeft = plsc->lsadjustcontext.urStartMainText;
upLeft = plsline->upStartMainText;
}
else if (LeftWall.kwall == Pen)
{
/* pen - it've been scaled already, we know left wall dimensions in advance */
urLeft = urLeftWall + LeftWall.pdn->u.pen.dur;
upLeft = upLeftWall + LeftWall.pdn->u.pen.dup;
}
else /* now non-left tabs */
{
urLeft = urLeftWall;
upLeft = upLeftWall;
}
durColumnMax = urRight - urLeft;
dupAvailable = upRight - upLeft;
Assert(durColumnMax >= 0);
// dupAvailable can be < 0 here - visi optional hyphens in previous GC, for example.
// AdjustText doesn't mind, meaning it won't crush.
fSuppressWiggle = ((plsc->grpfManager & fFmiPresSuppressWiggle) != 0);
fExact = ((plsc->grpfManager & fFmiPresExactSync) != 0);
}
else
{
// situation is complicated - we go the safest way.
durColumnMax = grchunkext.durTotal;
dupAvailable = LONG_MAX;
fExact = fTrue;
fSuppressWiggle = fTrue;
}
}
else
{
/* for the last GrpChnk we must to calculate durColumnMax and dupAvailable */
if (LeftWall.kwall == Tab && LeftWall.lsktab == lsktLeft)
{
durColumnMax = urColumnMax - plsc->lstabscontext.pcaltbd[LeftWall.pdn->icaltbd].ur;
dupAvailable = UpFromUr(lstflow, pdevres, urColumnMax) - grchunkext.dupNonTextTotal -
UpFromUr(lstflow, pdevres, plsc->lstabscontext.pcaltbd[LeftWall.pdn->icaltbd].ur);
}
else if (LeftWall.kwall == LineBegin)
{
durColumnMax = urColumnMax - plsc->lsadjustcontext.urStartMainText;
dupAvailable = upColumnMax - plsline->upStartMainText - grchunkext.dupNonTextTotal;
// Ask AdjustText to set widths of trailing spaces to 0 only if
// It is the last groupchunk, (we actually only care for "the only one" situation)
// and its first dnode (again, we actually only care for "the only one" situation)
// submits subline for both justification and trailing spaces
// and this subline runs in the direction opposite to the line direction.
if (!fEmptyGroupChunk &&
FIsDnodeReal(grchunkext.plsdnFirst) && grchunkext.plsdnFirst->u.real.pinfosubl != NULL &&
grchunkext.plsdnFirst->u.real.pinfosubl->fUseForJustification &&
grchunkext.plsdnFirst->u.real.pinfosubl->fUseForTrailingArea &&
FCollinearTflows(((grchunkext.plsdnFirst->u.real.pinfosubl->rgpsubl)[0])->lstflow, lstflow) &&
((grchunkext.plsdnFirst->u.real.pinfosubl->rgpsubl)[0])->lstflow != lstflow)
{
fSuppressTrailingSpaces = fTrue;
}
}
else if (LeftWall.kwall == Pen)
{
/* pen - it've been scaled already, we know wall dimensions in advance */
durColumnMax = urColumnMax - urLeftWall - LeftWall.pdn->u.pen.dur;
dupAvailable = UpFromUr(lstflow, pdevres, urColumnMax) - upLeftWall -
LeftWall.pdn->u.pen.dup - grchunkext.dupNonTextTotal;
}
else /* now non-left tabs */
{
durColumnMax = urColumnMax - urLeftWall;
dupAvailable = UpFromUr(lstflow, pdevres, urColumnMax) - upLeftWall - grchunkext.dupNonTextTotal;
}
// we do some tricks with justification mode at the end of line
// alignment can change too.
lserr = SetJustificationForLastGroupChunk(plsline, LeftWall, &lskj, &lskalign);
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
// Don't try to squeeze into RMJustify is RMBreak is infinite.
if (plsc->urRightMarginBreak >= uLsInfiniteRM)
{
dupAvailable = LONG_MAX;
}
fSuppressWiggle = ((plsc->grpfManager & fFmiPresSuppressWiggle) != 0);
fExact = ((plsc->grpfManager & fFmiPresExactSync) != 0);
fForcedBreak = plsline->lslinfo.fForcedBreak;
}
/*
* Adjust text (if any)
*
* Input: durColumnMax, dupAvailable, lskj and other input parameters
*
* Output: dupText and dupTail
*/
if (fEmptyGroupChunk)
{
dupText = 0;
dupTail = 0;
dupToAddToNonTextObjects = 0;
}
else
{
lserr = AdjustText(lskj, durColumnMax, grchunkext.durTotal - grchunkext.durTrailing,
dupAvailable, &(grchunkext.lsgrchnk),
&(grchunkext.posichnkBeforeTrailing), lstflow,
plsc->lsadjustcontext.fLineCompressed && RightWall.kwall == LineEnd,
grchunkext.cNonTextObjectsExpand,
fSuppressWiggle, fExact, fForcedBreak, fSuppressTrailingSpaces,
&dupText, &dupTail, &dupToAddToNonTextObjects, &cNonTextObjectsToExtend);
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
// Finish justification by expanding non-text object.
if (cNonTextObjectsToExtend != 0 && dupToAddToNonTextObjects > 0)
{
WidenNonTextObjects(&grchunkext, dupToAddToNonTextObjects, cNonTextObjectsToExtend);
}
else
// We don't compressi and we don't expand the last non-text object on the line
{
dupToAddToNonTextObjects = 0; // don't say we did it
}
/*
* Set dup in non-text objects (do CalcPres) in the current GroupChunk
*
* The job cannot be postponed until after the end of the main loop and done for the whole line
* because GetDistanceToDecimalPoint relies on dups in upper level dnodes
*/
if (!plsline->fAllSimpleText)
{
// find the last upper level dnode of the groupchunk
if (grchunkext.plsdnNext != NULL)
{
pdnLast = (grchunkext.plsdnNext)->plsdnPrev;
}
else
{
Assert(RightWall.kwall == LineEnd);
pdnLast = plsline->lssubl.plsdnLastDisplay;
}
lserr = CalcPresChunk(plsc, grchunkext.plsdnFirst, pdnLast, CollectGroupChunkPurpose,
plsc->lsadjustcontext.fLineCompressed, lskj, (RightWall.kwall == LineEnd));
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
}
}
/*
* Set the left wall (if it's a tab - resolve it)
*
* Input: LeftWall, dupText, dupTail, grchunkext.dupNonTextTotal, grchunkext.durTotal,
* dupToAddToNonTextObjects (grchunkext for decimal tab)
*
* Output: dupWall, durWall
*/
dupGrpChnk = dupText + grchunkext.dupNonTextTotal + dupToAddToNonTextObjects;
lsktabLast = lsktLeft; // no tab equal left tab for my purpose
if (LeftWall.kwall == Tab)
{
/* calculate dup of the Left wall now */
if (dupGrpChnk == 0) /* consecutive tabs */
dupWall = LeftWall.upTab - upLeftWall;
else
if (LeftWall.lsktab == lsktLeft)
dupWall = LeftWall.upTab - upLeftWall;
else if (LeftWall.lsktab == lsktRight)
dupWall = LeftWall.upTab - upLeftWall - (dupGrpChnk - dupTail);
else if (LeftWall.lsktab == lsktCenter)
dupWall = LeftWall.upTab - upLeftWall - ((dupGrpChnk - dupTail) / 2);
else /* LeftWall.lsktab == lsktDecimal or lsktChar */
{
lserr = GetDistanceToTabPoint(&grchunkext, cpLim, LeftWall.lsktab, LeftWall.wchCharTab,
LeftWall.pdn->plsdnNext, &dupToTabPoint);
if (lserr != lserrNone)
{
plsc->lsstate = LsStateFree;
return lserr;
}
dupWall = LeftWall.upTab - upLeftWall - dupToTabPoint;
}
// take care of previous text and right margin
if (RightWall.kwall == LineEnd &&
(upLeftWall + dupWall + dupGrpChnk - dupTail) > upColumnMax)
{
// We don't want to cross RM because of last center tab
dupWall = upColumnMax - upLeftWall - dupGrpChnk + dupTail;
}
if (dupWall < 0)
dupWall = 0;
/* LeftWall tab resolving */
LeftWall.pdn->u.real.dup = dupWall;
durWall = LeftWall.pdn->u.real.objdim.dur;
// for reproducing Word's bug of forgetting last not-left tab for centering.
lsktabLast = LeftWall.lsktab;
dupLastTab = dupWall;
}
else if (LeftWall.kwall == Pen)
{
dupWall = LeftWall.pdn->u.pen.dup; /* it've been scaled already */
durWall = LeftWall.pdn->u.pen.dur;
}
else /* LeftWall.kwall == LineBegin */
{
dupWall = plsline->upStartMainText;
durWall = plsc->lsadjustcontext.urStartMainText;
}
/* update loop variables, move one wall to the right */
upLeftWall += dupWall + dupGrpChnk;
urLeftWall += durWall + grchunkext.durTotal;
LeftWall = RightWall;
} /* end of the main loop */
/*
* prepare output parameters
*/
plsline->upRightMarginJustify = upColumnMax;
plsline->upLimLine = upLeftWall;
plsline->upStartTrailing = upLeftWall - dupTail;
plsline->upLimUnderline = plsline->upStartTrailing;
plsline->lssubl.fDupInvalid = fFalse;
/*
* Do left margin adjustment (not for breakthrough tab)
* We're interested in lskalRight and Centered now
*/
if (lskalign != lskalLeft && !FBreakthroughLine(plsc))
{
if (plsc->lsadjustcontext.fForgetLastTabAlignment && lsktabLast != lsktLeft)
{
// reproduction of an old Word bug: when last tab was not left and was resolved at line end
// they forgot to update their counterpart of upLeftWallForCentering. Word still have to be
// able to show old documents formatted in this crazy way as they were.
upLeftWallForCentering = upLeftWall - dupLastTab - dupTail;
}
else
{
upLeftWallForCentering = upLeftWall - dupTail;
}
if (lskalign == lskalRight)
{
dupJustifyLine = upColumnMax - upLeftWallForCentering;
}
else
{
/* These logic of centering is too simple to be valid, but Word uses it */
dupJustifyLine = (upColumnMax - upLeftWallForCentering) / 2;
}
// Apply adjustment if hanging punctuation haven't make it negative
if (dupJustifyLine > 0)
{
plsline->upStartAutonumberingText += dupJustifyLine;
plsline->upLimAutonumberingText += dupJustifyLine;
plsline->upStartMainText += dupJustifyLine;
plsline->upLimLine += dupJustifyLine;
plsline->upStartTrailing += dupJustifyLine;
plsline->upLimUnderline += dupJustifyLine;
}
}
if (plsc->lsadjustcontext.fUnderlineTrailSpacesRM &&
plsline->upLimUnderline < plsline->upRightMarginJustify)
{
UpdateUpLimUnderline(plsline, dupTail);
}
if (pdnAutoDecimalTab != NULL)
ConvertAutoTabToPen(plsline, pdnAutoDecimalTab);
plsc->lsstate = LsStateFree;
return lserr;
}
// %%Function: MatchPresSubline
// %%Contact: victork
//
/*
* Order of operations
*
* 1. Straighforward scaling down of non-text objects
* 2. Adjusting of text by LeftExact
* 3. Intelligent rescaling of pens to counteract rounding errors and text non-expansion.
* 4. Calling CalcPresentation for all non-text objects
*/
LSERR MatchPresSubline(PLSSUBL plssubl)
{
LSERR lserr;
const PLSC plsc = plssubl->plsc;
LSTFLOW lstflow = plssubl->lstflow; /* text flow of the subline */
const DWORD iobjText = IobjTextFromLsc(&(plsc->lsiobjcontext));
LSDEVRES* pdevres = &(plsc->lsdocinf.lsdevres);
LSCP cpLim = plssubl->cpLimDisplay;
PLSDNODE pdnFirst = plssubl->plsdnFirst;
GRCHUNKEXT grchunkext;
long dupAvailable; /* input for AdjustText */
long dupText, dupTail, dupToAddToNonTextObjects; /* dummy output for AdjustText */
DWORD cNonTextObjectsToExtend;
BOOL fDummy1, fDummy2; // dummy parameters
long urAlreadyScaled, upAlreadyScaled, upAlreadyScaledNew;
PLSDNODE pdn;
Assert(plssubl->fDupInvalid == fTrue);
/* 1. Straighforward scaling down of non-text objects */
ScaleDownLevel(plssubl, &fDummy1, &fDummy2);
/* 2. Adjusting of text on the level by LeftExact */
InitGroupChunkExt(plssubl->plschunkcontext, iobjText, &grchunkext); /* prepare GRCHUNKEXT */
pdn = pdnFirst;
while (pdn != NULL && (pdn->fTab || FIsDnodeNormalPen(pdn))) /* skip GrpChnk Wall(s) */
pdn = pdn->plsdnNext;
while (FDnodeBeforeCpLim(pdn, cpLim))
{
lserr = CollectTextGroupChunk(pdn, cpLim, CollectSublinesNone, &grchunkext);
if (lserr != lserrNone)
return lserr;
/* Adjust text by Left, Exact, no durFreeSpace, ignore any shortcomings */
dupAvailable = UpFromUr(lstflow, pdevres, grchunkext.durTotal) - grchunkext.dupNonTextTotal;
// posichnkBeforeTrailing is undefined when CollectSublinesNone, tell AdjustText about it
grchunkext.posichnkBeforeTrailing.ichnk = grchunkext.lsgrchnk.clsgrchnk;
grchunkext.posichnkBeforeTrailing.dcp = 0;
lserr = AdjustText(lskjNone, grchunkext.durTotal, grchunkext.durTotal,
dupAvailable,
&(grchunkext.lsgrchnk),
&(grchunkext.posichnkBeforeTrailing),
lstflow,
fFalse, // compress?
grchunkext.cNonTextObjects,
fTrue, // fSuppressWiggle
fTrue, // fExact
fFalse, // fForcedBreak
fFalse, // fSuppressTrailingSpaces
&dupText, &dupTail, &dupToAddToNonTextObjects, &cNonTextObjectsToExtend);
if (lserr != lserrNone)
return lserr;
pdn = grchunkext.plsdnNext;
while (pdn != NULL && (pdn->fTab || FIsDnodeNormalPen(pdn))) /* skip GrpChnk Wall(s) */
pdn = pdn->plsdnNext;
}
/* 3-4. Intelligent rescaling of pens to counteract rounding errors and text non-expansion.
* and adjusting of lower levels. Calling CalcPresentation for non-text objects.
*/
pdn = pdnFirst;
urAlreadyScaled = 0;
upAlreadyScaled = 0;
while (FDnodeBeforeCpLim(pdn, cpLim))
{
if (FIsDnodeReal(pdn))
{
urAlreadyScaled += pdn->u.real.objdim.dur;
upAlreadyScaled += pdn->u.real.dup;
if (IdObjFromDnode(pdn) != iobjText)
{
// It's always lskjNone and not last object on the line for MatchPresSubline
lserr = (*plsc->lsiobjcontext.rgobj[pdn->u.real.lschp.idObj].lsim.pfnCalcPresentation)
(pdn->u.real.pdobj, pdn->u.real.dup, lskjNone, fFalse);
if (lserr != lserrNone)
return lserr;
}
}
else if (FIsDnodeBorder(pdn))
{
// we don't rescale borders to preserve (dupOpeningBorder == dupClosingBorder)
upAlreadyScaled += pdn->u.real.dup;
urAlreadyScaled += pdn->u.pen.dur;
}
else /* pen */
{
urAlreadyScaled += pdn->u.pen.dur;
upAlreadyScaledNew = UpFromUr(lstflow, pdevres, urAlreadyScaled);
pdn->u.pen.dup = upAlreadyScaledNew - upAlreadyScaled;
upAlreadyScaled = upAlreadyScaledNew;
}
pdn = pdn->plsdnNext;
}
plssubl->fDupInvalid = fFalse;
return lserrNone;
}
// %%Function: AdjustSubline
// %%Contact: victork
//
/*
*
* Scale down non-text objects.
* Collect GroupChunk
* It should cover the whole subline or else do MatchPresSubline instead.
* Adjust text by expanding or compressing to given dup.
* Call CalcPresentation for all non-text objects.
*/
LSERR AdjustSubline(PLSSUBL plssubl, LSKJUST lskjust, long dup, BOOL fCompress)
{
LSERR lserr;
const PLSC plsc = plssubl->plsc;
LSTFLOW lstflow = plssubl->lstflow; /* text flow of the subline */
const DWORD iobjText = IobjTextFromLsc(&(plsc->lsiobjcontext));
LSDEVRES* pdevres = &(plsc->lsdocinf.lsdevres);
LSCP cpLim = plssubl->cpLimDisplay;
PLSDNODE pdnFirst = plssubl->plsdnFirst;
GRCHUNKEXT grchunkext;
COLLECTSUBLINES CollectGroupChunkPurpose;
long dupAvailable, durColumnMax; /* input for AdjustText */
long dupText, dupTail, dupToAddToNonTextObjects; /* dummy output for AdjustText */
DWORD cNonTextObjectsToExtend;
BOOL fDummy;
if (plssubl->plsdnFirst == NULL)
{
return lserrNone;
}
Assert(plssubl->fDupInvalid == fTrue);
ScaleDownLevel(plssubl, &fDummy, &(plsc->plslineCur->fCollectVisual));
CollectGroupChunkPurpose = (fCompress) ? CollectSublinesForCompression : CollectSublinesForJustification;
InitGroupChunkExt(plssubl->plschunkcontext, iobjText, &grchunkext); /* prepare GRCHUNKEXT */
lserr = CollectTextGroupChunk(pdnFirst, cpLim, CollectGroupChunkPurpose, &grchunkext);
if (lserr != lserrNone)
return lserr;
if (FDnodeBeforeCpLim(grchunkext.plsdnNext, cpLim)) // more than one GroupChunk -
{
return MatchPresSubline(plssubl); // cancel Expansion
}
dupAvailable = dup - grchunkext.dupNonTextTotal;
if (dupAvailable < 0) // input dup is wrong -
{
return MatchPresSubline(plssubl); // cancel Expansion
}
durColumnMax = UrFromUp(lstflow, pdevres, dup); // get dur by scaling back
lserr = AdjustText(lskjust, durColumnMax, grchunkext.durTotal - grchunkext.durTrailing,
dupAvailable,
&(grchunkext.lsgrchnk),
&(grchunkext.posichnkBeforeTrailing), lstflow,
fCompress, // compress?
grchunkext.cNonTextObjects,
fTrue, // fSuppressWiggle
fTrue, // fExact
fFalse, // fForcedBreak
fFalse, // fSuppressTrailingSpaces
&dupText, &dupTail, &dupToAddToNonTextObjects, &cNonTextObjectsToExtend);
if (lserr != lserrNone)
return lserr;
if (cNonTextObjectsToExtend != 0 && dupToAddToNonTextObjects > 0)
{
WidenNonTextObjects(&grchunkext, dupToAddToNonTextObjects, cNonTextObjectsToExtend);
}
// fLastOnLine is always false on lower levels
lserr = CalcPresChunk(plsc, plssubl->plsdnFirst, plssubl->plsdnLastDisplay,
CollectGroupChunkPurpose, fCompress, lskjust, fFalse);
plssubl->fDupInvalid = fFalse;
return lserrNone;
}