mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1956 lines
56 KiB
1956 lines
56 KiB
#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;
|
|
}
|
|
|