windows-nt/Source/XPSP1/NT/windows/richedit/re41/layout.cpp
2020-09-26 16:20:57 +08:00

1406 lines
38 KiB
C++

/*
* @doc INTERNAL
*
* @module LAYOUT.CPP -- CLayout class |
*
* Recursive structure which contains an array of lines.
*
* Owner:<nl>
* Murray Sargent: Initial table implementation
* Keith Curtis: Factored into a separate class for
* performance, simplicity
*
* Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved.
*/
//FUTURE: (KeithCu) More stuff should be put into here, e.g., RecalcLines,
//The CDisplayML should just be a class that knows about Device descriptors,
//pagination and scrolling, etc., i.e., things that are the same for all
//layouts and things that apply only to the outermost layout. This code knows
//how to manage and update recursive arrays of lines.
#include "_common.h"
#include "_dispml.h"
#include "_select.h"
#include "_measure.h"
#include "_render.h"
void CLayout::DeleteSubLayouts(
LONG ili,
LONG cLine)
{
CLine *pli = Elem(ili);
if(cLine < 0)
cLine = Count();
LONG cLineMax = Count() - ili;
cLine = min(cLine, cLineMax);
AssertSz(ili >= 0 && cLine >= 0, "DeleteSubLayouts: illegal line count");
// Delete sublayouts
for(; cLine--; pli++)
delete pli->GetPlo();
}
/*
* CLayout::VposFromLine(pdp, ili)
*
* @mfunc
* Computes top of line position
*
* @rdesc
* top position of given line (relative to the first line)
*/
LONG CLayout::VposFromLine(
CDisplayML *pdp, //@parm Parent display
LONG ili) //@parm Line we're interested in
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::VposFromLine");
LONG cli = 0, vPos = 0;
CLine *pli = 0;
if (IsNestedLayout())
{
Assert(!IsTableRow()); // _iPFCell layouts are horizontal
Assert(ili < Count());
cli = ili;
pli = Elem(0);
vPos = 0;
}
else
{
if(!pdp->WaitForRecalcIli(ili)) // out of range, use last valid line
{
ili = Count() - 1;
ili = (ili > 0) ? ili : 0;
}
cli = ili - pdp->_iliFirstVisible;
pli = Elem(pdp->_iliFirstVisible);
vPos = pdp->_vpScroll + pdp->_dvpFirstVisible;
}
while(cli > 0)
{
vPos += pli->GetHeight();
cli--;
pli++;
}
while(cli < 0)
{
pli--;
vPos -= pli->GetHeight();
cli++;
}
AssertSz(vPos >= 0, "VposFromLine height less than 0");
return vPos;
}
/*
* CLayout::LineFromVPos(pdp, vPos, pdvpLine, pcpFirst)
*
* @mfunc
* Computes line at given y position. Returns top of line vPos
* cp at start of line cp, and line index.
*
* @rdesc
* index of line found
*/
LONG CLayout::LineFromVpos(
CDisplayML *pdp, //@parm Parent display
LONG vPos, //@parm Vpos to look for (relative to first line)
LONG *pdvpLine, //@parm Returns vPos at top of line /r first line (can be NULL)
LONG *pcpFirst) //@parm Returns cp at start of line (can be NULL)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::LineFromVpos");
LONG cpLi;
LONG dy;
LONG ili = 0;
LONG yLi;
CLine *pli;
if(IsNestedLayout())
goto BindFrom0;
yLi = pdp->_vpScroll;
if(!pdp->WaitForRecalc(-1, pdp->_vpScroll))
{
yLi = 0;
cpLi = 0;
goto done;
}
cpLi = pdp->_cpFirstVisible;
ili = pdp->_iliFirstVisible;
if(!pdp->IsInPageView())
yLi += pdp->_dvpFirstVisible;
dy = vPos - yLi;
if(dy < 0 && -dy <= pdp->_vpScroll)
{
// Closer to first visible line than to first line:
// go backwards from first visible line.
while(vPos < yLi && ili > 0)
{
pli = Elem(--ili);
yLi -= pli->GetHeight();
cpLi -= pli->_cch;
}
}
else
{
if(dy < 0)
{
// Closer to first line than to first visible line:
// so start at first line.
BindFrom0:
cpLi = _cpMin;
yLi = 0;
ili = 0;
}
pli = Elem(ili);
while(vPos > yLi && ili < Count()-1)
{
yLi += pli->GetHeight();
cpLi += pli->_cch;
ili++;
pli++;
}
if(vPos < yLi && ili > 0)
{
ili--;
pli--;
yLi -= pli->GetHeight();
cpLi -= pli->_cch;
}
}
done:
if(pdvpLine)
*pdvpLine = yLi;
if(pcpFirst)
*pcpFirst = cpLi;
return ili;
}
/*
* CLayout::FindTopCell(&cch, pli, &ili, dul, &dy, pdvp, pliMain,iliMain, pcLine)
*
* @mfunc
* Find cch and height change back to current position in
* top cell corresponding to the current vertically merged cell.
* Enter with cch = cch from current cell back to start of row.
*
* @rdesc
* target line in top cell
*/
CLine * CLayout::FindTopCell(
LONG & cch, //@parm In/out parm for cch back to top
CLine * pli, //@parm Table-row line
LONG & ili, //@parm Corresponding line index & return ili
LONG dul, //@parm Current cell x offset
LONG & dy, //@parm In/Out parm for y offset in top cell
LONG * pdvp, //@parm TopCellHeight - heights of inbetween rows
CLine * pliMain, //@parm Line preceding first line accessible by pli
LONG iliMain, //@parm Line index corresponding to pliMain
LONG * pcLine) //@parm Count() of possible CLayout for returned pli
{
LONG cCell;
LONG iCell;
CLayout * plo;
const CELLPARMS *prgCellParms;
const CParaFormat *pPF;
#ifdef DEBUG
BYTE bTableLevel = pli->GetPlo()->GetPFCells()->_bTableLevel;
#endif
if(pcLine)
*pcLine = 0; // Default no lines in case of error
// Need to use uCell to identify cell rather than iCell, since
// horizontal merge can change iCell from row to row
do // Backup row by row
{
if(ili > 0)
{
pli--; // Go to previous row
ili--;
}
else if(pliMain)
{
pli = pliMain;
ili = iliMain;
pliMain = NULL; // Switch to pliMain only once!
}
else
{
AssertSz(FALSE, "CLayout::FindTopCell: no accessible top cell");
return NULL;
}
plo = pli->GetPlo(); // Get its cell display
if(!plo || !plo->IsTableRow()) // Illegal structure or not table row
{
AssertSz(FALSE, "CLayout::FindTopCell: no accessible top cell");
return NULL;
}
pPF = plo->GetPFCells();
AssertSz(pPF->_bTableLevel == bTableLevel,
"CLayout::FindTopCell: no accessible top cell");
prgCellParms = pPF->GetCellParms();
cCell = plo->Count();
iCell = prgCellParms->ICellFromUCell(dul, cCell);
dy += pli->GetHeight(); // Add row height
cch += pli->_cch; // Add in cch for whole row
}
while(!IsTopCell(prgCellParms[iCell].uCell));
cch -= 2; // Sub cch for StartRow delim
pli = plo->Elem(0); // Point at 1st cell in row
for(ili = 0; ili < iCell; ili++)// Sub cch's for cells
cch -= (pli++)->_cch; // preceding iCellth cell
if(pdvp) // Return top-cell height - heights of
*pdvp = pli->GetHeight() - dy;// cells in between
LONG cLine = 0;
LONG dvpBrdrTop = plo->_dvpBrdrTop;
ili = 0;
dy -= dvpBrdrTop;
plo = pli->GetPlo();
if(plo) // Top cell is multiline
{
cLine = plo->Count();
pli = plo->Elem(0); // Advance pli to line in plo
if(pli->IsNestedLayout())
dy += dvpBrdrTop;
while(ili < cLine && dy >= pli->GetHeight()) // nearest to input position
{
dy -= pli->GetHeight();
ili++;
if(ili == cLine) // Done: leave pli pointing at last line
break;
cch -= pli->_cch;
pli++;
}
}
if(pcLine)
*pcLine = cLine;
return pli;
}
/*
* CLayout::FindTopRow(pli, ili, pliMain, iliMain, pPF)
*
* @mfunc
* Find CLine for top row in a table
*
* @rdesc
* CLine for top row in table
*/
CLine * CLayout::FindTopRow(
CLine * pli, //@parm Entry table-row line
LONG ili, //@parm Corresponding line index
CLine * pliMain, //@parm Line preceding first line accessible by pli
LONG iliMain, //@parm Line index corresponding to pliMain
const CParaFormat *pPF) //@parm CParaFormat for entry plo
{
BYTE bAlignment = pPF->_bAlignment; // Target row must have same
BYTE bTableLevel = pPF->_bTableLevel; // alignment and level
CLine * pliLast;
CLayout *plo;
do // Backup row by row
{
pliLast = pli; // Last line pointing at row in table
if(ili > 0)
{
pli--; // Go to previous line
ili--;
}
else if(pliMain) // More lines to go back to
{
pli = pliMain;
ili = iliMain;
pliMain = NULL; // Switch to pliMain only once!
}
else
break;
plo = pli->GetPlo(); // Get its cell display
if(!plo || !plo->IsTableRow())
break;
pPF = plo->GetPFCells();
}
while(pPF->_bAlignment == bAlignment && pPF->_bTableLevel == bTableLevel);
return pliLast;
}
/*
* CLayout::GetCFCells()
*
* @mfunc
* Return CCharFormat for the table row described by this CLayout
*
* @rdesc
* Table row CCharFormat
*/
const CCharFormat* CLayout::GetCFCells()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CLayout::GetCFCells");
Assert(_iCFCells >= 0);
const CCharFormat *pCF;
if(FAILED(GetCharFormatCache()->Deref(_iCFCells, &pCF)))
{
AssertSz(FALSE, "CLayout::GetCFCells: couldn't deref _iCFCells");
pCF = NULL;
}
return pCF;
}
/*
* CLayout::GetPFCells()
*
* @mfunc
* Return CParaFormat for the table row described by this CLayout
*
* @rdesc
* Table row CParaFormat
*/
const CParaFormat* CLayout::GetPFCells() const
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CLayout::GetPFCells");
Assert(_iPFCells >= 0);
const CParaFormat *pPF;
if(FAILED(GetParaFormatCache()->Deref(_iPFCells, &pPF)))
{
AssertSz(FALSE, "CLayout::GetPF: couldn't deref _iPFCells");
pPF = NULL;
}
return pPF;
}
/*
* CLayout::GetLORowAbove(pli, ili, pliMain, iliMain)
*
* @mfunc
* Return CLayout for the table row described by the line above pli.
* If not a table row, return NULL.
*
* @rdesc
* Table row CLayout for row above pli's
*/
const CLayout* CLayout::GetLORowAbove(
CLine * pli, //@parm Entry table-row line
LONG ili, //@parm Corresponding line index
CLine * pliMain, //@parm Line preceding first line accessible by pli
LONG iliMain) //@parm Line index corresponding to pliMain
{
if(!ili && pliMain && iliMain) // More lines to go back to
{
pli = pliMain;
ili = iliMain;
}
if(ili)
{
CLayout *plo = (pli - 1)->GetPlo(); // Get cell display for row above
if(plo && plo->IsTableRow())
return plo;
}
return NULL; // No line above
}
/*
* CLayout::CpFromPoint(&me, pt, prcClient, prtp, prp, fAllowEOL, phit,
* pdispdim, pcpActual, pliParent, iliParent)
* @mfunc
* Determine cp at given point
*
* @devnote
* --- Use when in-place active only ---
*
* @rdesc
* Computed cp, -1 if failed
*/
LONG CLayout::CpFromPoint(
CMeasurer &me, //@parm Measurer
POINTUV pt, //@parm Point to compute cp at (client coords)
const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active).
CRchTxtPtr * const prtp,//@parm Returns text pointer at cp (may be NULL)
CLinePtr * const prp, //@parm Returns line pointer at cp (may be NULL)
BOOL fAllowEOL, //@parm Click at EOL returns cp after CRLF
HITTEST * phit, //@parm Out parm for hit-test value
CDispDim * pdispdim, //@parm Out parm for display dimensions
LONG *pcpActual, //@parm Out cp that pt is above
CLine * pliParent, //@parm Parent pli for table row displays
LONG iliParent) //@parm Parent ili corresponding to pli
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::CpFromPoint");
LONG cch = 0;
LONG cp = 0;
HITTEST hit = HT_Nothing;
LONG ili;
CLine * pli;
CLayout *plo = NULL;
RECTUV rcView;
int v = pt.v; // Save input y coordinate
LONG yLine = 0;
CDisplayML *pdp = (CDisplayML*) me.GetPdp();
if (IsNestedLayout())
rcView = *prcClient;
else
{
pdp->GetViewRect(rcView, prcClient);
pt.v += pdp->GetVpScroll();
if(pt.u >= 0) // If x coordinate is within view,
pt.u += pdp->GetUpScroll(); // adjust by scroll value
}
if(phit)
*phit = HT_Nothing; // Default in case early return
// Get line under hit
if(IsTableRow()) // This display is a table row
{ // Shrink to cell text boundaries
pli = Elem(0); // Point at starting cell CLine
// Move over to start of cells
const CParaFormat *pPFCells = GetPFCells();
LONG dul = 0;
LONG dulRTLRow = pPFCells->GetRTLRowLength();
LONG dup = 0;
BOOL fCellLow;
LONG h = me.LUtoDU(pPFCells->_dxOffset);
const CELLPARMS *prgCellParms = pPFCells->GetCellParms();
LONG u; // Tracks start of text in cell
LONG u0 = pli->_upStart;
LONG uCell = 0;
pt.v -= _dvpBrdrTop; // Subtract off border top
cp = _cpMin;
if(dulRTLRow)
u0 += me.LUtoDU(dulRTLRow);
ili = 0;
while(1)
{
u = u0 + dup + h; // Indent in current cell
cch = cp - _cpMin;
uCell = prgCellParms[ili].uCell;
fCellLow = IsLowCell(uCell);
dul += GetCellWidth(uCell);
me.SetDulLayout(GetCellWidth(uCell) - 2*pPFCells->_dxOffset);
dup = me.LUtoDU(dul);
if(!dulRTLRow && pt.u < u0 + dup ||// pt.u is inside current cell
dulRTLRow && pt.u > u0 - dup)
{
LONG ili0 = iliParent;
if(fCellLow) // Cell merged vertically
{ // with the one above it
LONG dy = pt.v;
CLine *pli0 = FindTopCell(cch, pliParent, ili0, dul, dy,
NULL, NULL, 0, NULL);
if(pli0)
{ // Found top cell
cch += 2; // Include cch of row-start delim
pli = pli0; // Use its pli and backup
ili = ili0;
cp -= cch; // Backup to start of pli
pt.v += dy;
}
}
if(!dulRTLRow && pt.u < u)
{ // In cell gap, so select cell
hit = HT_LeftOfText;
cch = 0; // Setup for start of row
goto finish;
}
break;
}
cp += pli->_cch; // Add in cell's cch
ili++;
if(ili == Count())
{
hit = HT_RightOfText;
goto finish;
}
pli++;
}
LONG dupCell = me.LUtoDU(GetCellWidth(uCell));
if(dulRTLRow)
pt.u -= me.LUtoDU(dulRTLRow - dul) + h;
else
pt.u -= dup - dupCell + h;
rcView.right = dupCell - 2*h;
pt.v -= GetVertAlignShift(uCell, pli->GetHeight());
}
else // This display isn't a table row
{
// Adjust coordinates relative to view origin
rcView.right -= rcView.left;
pt.u -= rcView.left;
pt.v -= rcView.top;
ili = LineFromVpos(pdp, pt.v, &yLine, &cp);
if(ili < 0)
return -1;
pli = Elem(ili);
if(yLine + pli->GetHeight() < pt.v)
hit = HT_BelowText; // Return hit below text
}
rcView.left = 0;
rcView.top = 0;
AssertSz(pli || !ili, "CLayout::CpFromPoint invalid line pointer");
if(pli) // Line exists, even it it's
{ // above or below current screen
HITTEST hit0;
if(v < rcView.top) // Note if hit occurs above or
hit = HT_AboveScreen; // below text
if(v > rcView.bottom && !IsNestedLayout())
hit = HT_BelowText;
plo = pli->GetPlo();
pt.v -= yLine;
if(plo) // Child layout
{
pt.u -= pli->_upStart;
plo->_cpMin = cp; // Update child's _cpMin
if(plo->IsTableRow()) // Table row
{
plo->_cpMin += 2; // Bypass TR start delimiter
if(pt.u < 0)
{
plo = NULL;
hit = HT_LeftOfText; // Return hit left of text
Assert(cch >= 0); // (should be row)
goto finish;
}
}
cp = plo->CpFromPoint(me, pt, &rcView, prtp, prp, fAllowEOL,
&hit0, pdispdim, pcpActual, pli, ili);
if(cp == -1)
return -1;
cch = cp - _cpMin;
}
else // Leaf line
{
me.SetLayout(this);
me.SetCp(cp);
// Support khyphChangeAfter
me.SetIhyphPrev(ili > 0 ? (pli - 1)->_ihyph : 0);
// Get character in line
cch = pli->CchFromUp(me, pt, pdispdim, &hit0, pcpActual);
// Don't allow click at EOL to select EOL marker and take into
// account single line edits as well
if(cch == pli->_cch && pli->_cchEOP && (!fAllowEOL || me.GetPrevChar() == CELL))
{
// Adjust position on line by amount backed up. OK for
// me._rpCF and me._rpPF to get out of sync with me._rpTX,
// since they're not needed for me.GetCp().
cch += me._rpTX.BackupCRLF();
}
cp = me.GetCp();
}
if(hit != HT_BelowText && hit != HT_AboveScreen || hit0 == HT_RightOfText)
hit = hit0;
}
finish:
if(!plo) // Store info from leaf line
{
if(prtp)
prtp->SetCp(cp);
if(prp)
{
Assert(cch >= 0);
prp->Set(ili, cch, this);
}
}
if (phit)
*phit = hit;
return cp;
}
/*
* CLayout::PointFromTp(&me, rtp, prcClient, fAtEnd, pt, prp, taMode, pdispdim)
*
* @mfunc
* Determine coordinates at given tp
*
* @devnote
* --- Use when in-place active only ---
*
* @rdesc
* line index at cp, -1 if error
*/
LONG CLayout::PointFromTp(
CMeasurer &me, //@parm Measurer
const CRchTxtPtr &rtp, //@parm Text ptr to get coordinates at
const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active).
BOOL fAtEnd, //@parm Return end of prev line for ambiguous cp
POINTUV & pt, //@parm Returns point at cp in client coords
CLinePtr * const prp, //@parm Returns line pointer at tp (may be null)
UINT taMode, //@parm Text Align mode: top, baseline, bottom
CDispDim * pdispdim) //@parm Out parm for display dimensions
{
LONG cp = rtp.GetCp();
LONG dy = 0;
RECTUV rcView;
CDisplayML *pdp = (CDisplayML*) me.GetPdp();
CLinePtr rp(pdp);
if(!pdp->WaitForRecalc(cp, -1))
return -1;
if(!IsNestedLayout()) // Main display
{
if(!rp.SetCp(cp, fAtEnd))
return -1;
pdp->GetViewRect(rcView, prcClient);
pt.u = rcView.left - pdp->_upScroll;
pt.v = rcView.top - pdp->_vpScroll;
}
else // Subdisplay
{
rp.Init(*this);
rp.BindToCp(cp - _cpMin);
if(fAtEnd && !IsTableRow()) // Ambiguous-cp caret position
rp.AdjustBackward(); // belongs at prev EOL
rcView = *prcClient;
pt.u = rcView.left;
pt.v = rcView.top;
}
AssertSz(pdp->_ped->_fInPlaceActive || prcClient, "Invalid client rect");
LONG ili = rp.GetLineIndex();
CLine *pli = NULL;
CLayout *plo = NULL;
LONG xEnd = -1; // pt.u to use at end of table row
if(IsTableRow()) // This layout is a table row
{ // Shrink to cell text boundaries
const CParaFormat *pPFCells = GetPFCells();
const CELLPARMS * prgCellParms = pPFCells->GetCellParms();
LONG dul = 0;
LONG dulRTLRow = pPFCells->GetRTLRowLength();
LONG h = me.LUtoDU(pPFCells->_dxOffset);
LONG i;
cp = _cpMin;
pli = Elem(0);
for(i = 0; i < ili; i++, pli++)
{
dul += GetCellWidth(prgCellParms[i].uCell);
cp += pli->_cch;
}
LONG uCell = prgCellParms[ili].uCell;
me.SetDulLayout(GetCellWidth(uCell) - 2 * pPFCells->_dxOffset);
if(dulRTLRow)
{
if(dul < dulRTLRow)
{
uCell = prgCellParms[ili + 1].uCell;
dul += GetCellWidth(prgCellParms[i].uCell);
}
dul = dulRTLRow - dul;
}
rcView.left = pt.u + me.LUtoDU(dul) + h;
rcView.right = pt.u + me.LUtoDU(dul + GetCellWidth(uCell)) - h;
pt.u = rcView.left;
if(!GetCellWidth(uCell))
{
pt.v += _dvp;
goto done;
}
if(ili + 1 == Count() && rp->_cch == rp.GetIch())
{
xEnd = rcView.right + h + 1;
if(dulRTLRow)
xEnd = rcView.left - h - 1;
}
pt.v += GetVertAlignShift(uCell, pli->GetHeight());
if(!(taMode & TA_CELLTOP))
pt.v += _dvpBrdrTop;
}
else // This layout isn't a table row
{
pt.v += VposFromLine(pdp, ili);
cp -= rp.GetIch();
}
pli = Elem(ili);
plo = pli->GetPlo();
if(plo) // Line has child display
{ // Define child rcView and delegate
RECTUV rc; // to child
pt.u += pli->_upStart;
rc.left = pt.u;
rc.right = pt.u + rcView.right - rcView.left;
rc.top = pt.v;
rc.bottom = pt.v + pli->GetHeight();
plo->_cpMin = cp; // Update child display's _cpMin
if(plo->IsTableRow())
plo->_cpMin += 2; // Bypass table row start code
if(plo->PointFromTp(me, rtp, &rc, fAtEnd, pt, prp, taMode, pdispdim) == -1)
return -1;
}
else // Line is a leaf line
{
me.SetLayout(this);
me.Move(-rp.GetIch()); // Backup to start of line
me.NewLine(*rp); // Measure from there to where we are
//Support khyphChangeAfter
me.SetIhyphPrev(ili > 0 ? (pli - 1)->_ihyph : 0);
LONG xCalc = rp->UpFromCch(me, rp.GetIch(), taMode, pdispdim, &dy);
if(pt.u + xCalc <= rcView.right || !pdp->GetWordWrap() || pdp->GetTargetDev())
{
// Width is in view or there is no wordwrap so just
// add the length to the point.
pt.u += xCalc;
}
else
pt.u = rcView.right; //Hit-test went too far, limit it.
pt.v += dy;
}
if(xEnd != -1)
pt.u = xEnd; // Return x coord at end of table row
done:
if(prp && !plo)
*prp = rp; // Return innermost rp
return rp; // Return outermost iRun
}
/*
* CLayout::Measure(&me, pli, ili, uiFlags, pliTarget, iliMain, pliMain, pdvpExtra)
*
* @mfunc
* Computes line break (based on target device) and fills
* in *pli with resulting metrics on rendering device
*
* @rdesc
* TRUE if OK
*/
BOOL CLayout::Measure (
CMeasurer& me, //@parm Measurer pointing at text to measure
CLine * pli, //@parm Line to store result in
LONG ili, //@parm Line index corresponding to pli
UINT uiFlags, //@parm Flags
CLine * pliTarget, //@parm Returns target-device line metrics (optional)
LONG iliMain, //@parm Line index corresponding to pliMain
CLine * pliMain, //@parm Line preceding 1st line in pli layout (optional)
LONG * pdvpExtra) //@parm Returns extra line height for vmrged cells (opt)
//REVIEW (keithcu) pliTarget is busted in the recursive case.
{
CTxtEdit * ped = me.GetPed();
LONG cchText = ped->GetTextLength();
LONG cpSave = me.GetCp();
CLine * pliNew;
const CDisplayML * pdp = (const CDisplayML *)me.GetPdp();
const CParaFormat *pPF = me.GetPF();
// Measure one line, which is either a table row or a line in a paragraph
if(pPF->IsTableRowDelimiter())
{
// Measure table row, which is modeled as a CLayout with one
// CLine per cell. In the backing store, table rows start with
// the two chars STARTFIELD CR and end with ENDFIELD CR. Cells
// are delimited by CELL.
LONG cpStart = me.GetCp();
LONG dul = 0;
LONG dxCell = 0;
LONG dvp = 0;
LONG dvpMax = 0;
CLayout * plo = new CLayout();
const CLayout * ploAbove = GetLORowAbove(pli, ili, pliMain, iliMain);
const CELLPARMS *prgCellParms = pPF->GetCellParms();
if(!plo)
return FALSE;
plo->_iCFCells = me.Get_iCF();
plo->_iPFCells = me.Get_iPF();
pli->SetPlo(plo);
AssertSz(pPF->_bTabCount && me.GetChar() == STARTFIELD, "Invalid table-row header");
me.Move(2);
AssertSz(me.GetPrevChar() == CR, "Invalid table-row header");
plo->_cpMin = me.GetCp();
// Save current values
LONG dulLayoutOld = me.GetDulLayout();
LONG dvlBrdrTop = 0;
LONG dvlBrdrBot = 0;
const CLayout *ploOld = me.GetLayout();
CArray <COleObject*> rgpobjWrapOld;
me._rgpobjWrap.TransferTo(rgpobjWrapOld);
// Create CLines for each cell and measure them
for(LONG iCell = 0; iCell < pPF->_bTabCount; iCell++)
{
me.SetNumber(0);
LONG uCell = prgCellParms[iCell].uCell;
dxCell = GetCellWidth(uCell);
dul += dxCell;
// Add a line for the next cell
pliNew = plo->Add(1, NULL);
if(!pliNew)
return FALSE;
LONG dvl = prgCellParms[iCell].GetBrdrWidthTop();
dvlBrdrTop = max(dvlBrdrTop, dvl);
dvl = prgCellParms[iCell].GetBrdrWidthBottom();
dvlBrdrBot = max(dvlBrdrBot, dvl);
if(!ploAbove)
uCell &= ~fLowCell; // Can't be a low cell if no row above
AssertSz(!IsLowCell(uCell) || me.GetChar() == NOTACHAR,
"CLayout::Measure: invalid low cell");
me.SetLayout(plo);
me.SetDulLayout(dxCell - 2*pPF->_dxOffset);
plo->Measure(me, pliNew, iCell, uiFlags | MEASURE_FIRSTINPARA, pliTarget, iliMain, pliMain);
if(IsLowCell(uCell))
{
// If a low cell in set of vertically merged cells, check
// if corresponding cell on next row is also merged
CPFRunPtr rp(me);
rp.FindRowEnd(pPF->_bTableLevel);
const CParaFormat *pPF1 = rp.GetPF();
BOOL fBottomCell = !pPF1->IsTableRowDelimiter();
if(!fBottomCell)
{
const CELLPARMS *prgCellParms1 = pPF1->GetCellParms();
LONG iCell1 = prgCellParms1->ICellFromUCell(dul, pPF1->_bTabCount);
if(iCell1 >= 0 && !IsLowCell(prgCellParms1[iCell1].uCell))
fBottomCell = TRUE;
}
if(fBottomCell)
{
// Need to include top cell in current row height
// calculation
LONG cch = me.GetCp() - cpStart;
LONG dy1 = 0;
LONG iliT = ili;
LONG dvpCell = 0;
if(!FindTopCell(cch, pli, iliT, dul, dy1, &dvpCell, pliMain, iliMain, NULL))
uCell &= ~fLowCell; // Not a valid low cell
else if(dvpCell > 0)
dvp = max(dvp, dvpCell);
}
}
if(!IsVertMergedCell(uCell) && dxCell || !dvp && iCell == pPF->_bTabCount - 1)
dvp = max(pliNew->GetHeight(), dvp);
dvpMax = max(dvpMax, pliNew->GetHeight());
}
//Restore original values
me.SetDulLayout(dulLayoutOld);
me.SetLayout(ploOld);
me.SetIhyphPrev(0);
me._rgpobjWrap.Clear(AF_DELETEMEM);
rgpobjWrapOld.TransferTo(me._rgpobjWrap);
#ifdef DEBUG
// Bypass table-row terminator
if(me.GetChar() != ENDFIELD)
me._rpTX.MoveGapToEndOfBlock();
AssertSz(me.GetPrevChar() == CELL && pPF->_bTabCount == plo->Count(),
"Incorrect table cell count");
AssertSz(me.GetChar() == ENDFIELD,
"CLayout::Measure: invalid table-row terminator");
me._rpPF.AdjustForward();
const CParaFormat *pPFme = me.GetPF();
AssertSz(pPFme->IsTableRowDelimiter(),
"CLayout::Measure: invalid table-row terminator");
#endif
me.UpdatePF(); // me._pPF points at TRD PF
me.Move(2); // Bypass table row terminator
AssertSz(me.GetPrevChar() == CR,
"CLayout::Measure: invalid table-row terminator");
if(me.IsHidden())
{
CCFRunPtr rp(me);
me.Move(rp.FindUnhiddenForward());
}
if(me.GetChar() == CELL) // Bypass possible CELL delimeter
{ // at end of table row (happens
Assert(pPF->_bTableLevel > 1); // when table row is last line
CTxtSelection *psel = ped->GetSelNC(); // of cell
if(!psel || psel->GetCch() || // Don't bypass CELL if selection
psel->GetCp() !=me.GetCp() ||// is an IP at this position,
!psel->GetShowCellLine()) // i.e., display a blank line
{
me.Move(1);
pli->_fIncludeCell = TRUE;
}
}
plo->_dvpBrdrBot = me.GetPBorderWidth(dvlBrdrBot);
plo->_dvpBrdrTop = me.GetPBorderWidth(dvlBrdrTop);
if(ploAbove)
plo->_dvpBrdrTop = max(plo->_dvpBrdrTop, ploAbove->_dvpBrdrBot);
dvp += plo->_dvpBrdrTop; // Add top border width
if(!me.GetPF()->IsTableRowDelimiter())// End of table: add in
dvp += plo->_dvpBrdrBot; // bottom border width
// Define CLine parameters for table row
if(pPF->_dyLineSpacing)
{
LONG dvpLine = me.LUtoDU(pPF->_dyLineSpacing);
if(dvpLine < 0) // Negative row height means use
dvp = -dvpLine; // the magnitude exactly
else
dvp = max(dvp, dvpLine); // Positive row height means
} // "at least"
plo->_dvp = dvp;
dvpMax = max(dvpMax, dvp);
if(pdvpExtra)
*pdvpExtra = dvpMax - dvp;
// Fill in CLine structure for row
pli->_cch = me.GetCp() - cpSave;
pli->_fFirstInPara = pli->_fHasEOP = TRUE;
pli->_dup = me.LUtoDU(dul);
me._li._fFirstInPara = TRUE;
pli->_upStart = me.MeasureLeftIndent();
me.MeasureRightIndent(); // Define me._upEnd
pli->_cObjectWrapLeft = me._li._cObjectWrapLeft;
pli->_cObjectWrapRight = me._li._cObjectWrapRight;
USHORT dvpLine = plo->_dvp;
USHORT dvpDescent = 0;
me.UpdateWrapState(dvpLine, dvpDescent);
pli->_fFirstWrapLeft = me._li._fFirstWrapLeft;
pli->_fFirstWrapRight = me._li._fFirstWrapRight;
if(!pdp->IsInOutlineView() && IN_RANGE(PFA_RIGHT, pPF->_bAlignment, PFA_CENTER))
{
// Normal view with center or flush-right para. Move right accordingly
// If not top row of like-aligned rows, use indent of top row
CLine *pliFirst = FindTopRow(pli, ili, pliMain, iliMain, pPF);
if(pli != pliFirst)
pli->_upStart = pliFirst->_upStart;
else
{
LONG uShift = me.LUtoDU(dulLayoutOld - dul);
uShift = max(uShift, 0); // Don't allow alignment to go < 0
// Can happen with a target device
if(pPF->_bAlignment == PFA_CENTER)
uShift /= 2;
pli->_upStart = uShift;
}
}
me.SetNumber(0); // Update me._wNumber in case next
} // para is numbered
else if(!pli->Measure(me, uiFlags, pliTarget)) // Not a table row
return FALSE; // Measure failed
if(pli->_fFirstInPara && pPF->_wEffects & PFE_PAGEBREAKBEFORE)
pli->_fPageBreakBefore = TRUE;
me.SetIhyphPrev(pli->_ihyph);
if(!IsTableRow() || me.GetPrevChar() == CELL)// Not a table row display or
return TRUE; // cell text fits on 1 line
// Multiline table cell: allocate its CLayout
CLayout *plo = new CLayout();
if(!plo)
return FALSE; // Not enuf RAM
plo->_cpMin = cpSave;
pliNew = plo->Add(1, NULL);
if(!pliNew)
{
ped->GetCallMgr()->SetOutOfMemory();
TRACEWARNSZ("Out of memory Recalc'ing lines");
return FALSE;
}
*pliNew = *pli; // Copy first line of cell layout
pli->SetPlo(plo); // Turn line into a layout line
// Calculate remaining lines in cell.
// Eventually would be nice to share this code with RecalcLines()
BOOL fFirstInPara;
LONG dvp = pliNew->GetHeight();
LONG iliNew = 0;
while(me.GetCp() < cchText)
{
fFirstInPara = pliNew->_fHasEOP;
pliNew = plo->Add(1, NULL);
iliNew++;
if(!pliNew)
{
ped->GetCallMgr()->SetOutOfMemory();
TRACEWARNSZ("Out of memory Recalc'ing lines");
return FALSE;
}
// New table row can start after EOP, i.e., allow recursion here
uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0);
if(!plo->Measure(me, pliNew, iliNew, uiFlags, pliTarget))
{
Assert(FALSE);
return FALSE;
}
dvp += pliNew->GetHeight();
if(me.GetPrevChar() == CELL)
break; // Done with current cell
}
pli->_upStart = 0;
plo->_dvp = dvp;
pli->_cch = me.GetCp() - cpSave;
return TRUE;
}
/*
* CLayout::Render(&re, pli, prcView, fLastLine, ili, cLine)
*
* @mfunc
* Render visible part of the line *pli
*
* @rdesc
* TRUE iff successful
*
* @devnote
* re is moved past line (to beginning of next line).
* FUTURE: the RenderLine functions return success/failure.
* Could do something on failure, e.g., be specific and fire
* appropriate notifications like out of memory or character
* not in font. Note that CLayout::_cpMin isn't used in
* rendering, so we don't have to update it the way we do in
* the query functions.
*/
BOOL CLayout::Render(
CRenderer & re, //@parm Renderer to use
CLine * pli, //@parm Line to render
const RECTUV *prcView, //@parm View rect to use
BOOL fLastLine,//@parm TRUE iff last line of control
LONG ili, //@parm Line index of pli
LONG cLine) //@parm # lines in pli's CLayout
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::Render");
CLayout *plo = pli->GetPlo();
if(!plo)
return pli->Render(re, fLastLine); // Render leaf line
LONG cLine1 = plo->Count(); // Count of lines in sublayout
LONG ili1; // Index of first line in sublayout
CLine * pli1 = plo->Elem(0); // Ptr to first line in sublayout
POINTUV pt;
if(plo->IsTableRow()) // Line's nested display is a table row
{
// Render table row, which is modeled as a CLayout with one
// CLine per cell. In the backing store, table rows start with
// the two chars STARTFIELD CR and end with ENDFIELD CR. Cells
// are terminated by CELL.
const CLayout * ploAbove = GetLORowAbove(pli, ili);
const CParaFormat * pPF = plo->GetPFCells();
const CELLPARMS * prgCellParms = pPF->GetCellParms();
LONG cpStart = re.GetCp();
LONG dul = 0;
BOOL fSetErase = FALSE;
LONG hl = pPF->_dxOffset; // Logical half gap
LONG h = re.LUtoDU(hl); // Device half gap
RECTUV rcView;
LONG u = prcView->left + pli->_upStart - re._pdp->GetUpScroll();
// Bypass table-row start
AssertSz(pPF->_bTabCount && re.GetChar() == STARTFIELD, "Invalid table-row header");
AssertSz(pPF == re.GetPF(), "Invalid table-row pPF");
re.Move(2);
AssertSz(re.GetPrevChar() == CR, "Invalid table-row header");
// Save current state
LONG crBackOld = re.GetDefaultBackColor();
LONG crTextOld = re.GetDefaultTextColor();
LONG dulLayoutOld = re.GetDulLayout();
LONG dulRTLRow = pPF->GetRTLRowLength();
LONG dvpBrdrTop = plo->_dvpBrdrTop;
CLine * pli0;
POINTUV ptOld = re.GetCurPoint();
RECTUV rcRender;
RECTUV rcRenderOld = re.GetRcRender();
RECTUV rcViewOld = re.GetRcView();
const CLayout *ploOld = re.GetLayout();
rcView.left = u + h; // Default for LTR row
rcView.right = rcView.left; // Suppress compiler warning
rcView.top = ptOld.v;
rcRender.top = rcView.top;
rcView.bottom = rcView.top + pli->GetHeight();
rcRender.bottom = rcView.bottom;
if(dulRTLRow)
rcView.right = u + re.LUtoDU(dulRTLRow);
// Render each cell
for(ili1 = 0; ili1 < cLine1; ili1++, pli1++)
{
LONG dvp = 0; // Additional cell height if
LONG uCell = prgCellParms[ili1].uCell;
dul += GetCellWidth(uCell);
re.SetLayout(pli1->GetPlo());
re.SetDulLayout(GetCellWidth(uCell) - 2*hl);
// Reduce roundoff by converting dul instead of multiple uCell
if(dulRTLRow) // Right-To-Left row
rcView.left = u + h + re.LUtoDU(dulRTLRow - dul); // Convert horizontal coords
else
rcView.right = u + re.LUtoDU(dul);
rcRender.left = rcView.left - h;
rcRender.right = rcView.right;
//Set state
re.StartRender(rcView, rcRender);
pt.u = rcView.left;
pt.v = rcView.top + plo->GetVertAlignShift(uCell, pli1->GetHeight());
if(!IsLowCell(uCell))
pt.v += dvpBrdrTop;
re.SetRcViewTop(pt.v); // Clear to top of cell
re.SetCurPoint(pt);
if(IsTopCell(uCell))
{
// Calculate bottom of set of vertically merged cells
LONG ili0;
LONG iCell;
CLayout *plo0;
const CELLPARMS *prgCellParms0;
for(ili0 = ili + 1, pli0 = pli + 1; ili0 < cLine; ili0++, pli0++)
{
plo0 = pli0->GetPlo();
if(!plo0 || !plo0->IsTableRow())
break;
prgCellParms0 = plo0->GetPFCells()->GetCellParms();
iCell = prgCellParms0->ICellFromUCell(dul, plo0->Count());
if(iCell < 0 || !IsLowCell(prgCellParms0[iCell].uCell))
break;
dvp += pli0->GetHeight(); // Add row height
}
if(dvp)
{
rcView.bottom += dvp;
rcRender.bottom += dvp;
re.SetRcBottoms(rcView.bottom, rcRender.bottom);
}
}
COLORREF crf = crTextOld;
LONG icrf = prgCellParms[ili1].GetColorIndexForegound();
LONG icrb = prgCellParms[ili1].GetColorIndexBackgound();
if(icrf | icrb) // If any nonzero bits,
{ // calc special color
BYTE bS = prgCellParms[ili1].bShading;
COLORREF crb = re.GetShadedColorFromIndices(icrf, icrb, bS, pPF);
fSetErase = re.EraseRect(&rcRender, crb);
if(IsTooSimilar(crf, crb))
crf = re.GetShadedColorFromIndices(icrb, icrf, bS, pPF);
}
else
re.SetDefaultBackColor(crBackOld);
re.SetDefaultTextColor(crf);
if(!ploAbove)
uCell &= ~fLowCell; // Can't be low cell if no row above
if(IsLowCell(uCell)) // Cell merged vertically with
{ // the one above it
LONG cch = re.GetCp() -cpStart; // Use cLine0, ili0, pli0 to
LONG cLine0; // refer to text in set
LONG cpNext = re.GetCp() // of vert merged cells
+ (re.GetChar() == NOTACHAR ? 2 : 1);
LONG dy = 0;
LONG ili0 = ili;
// Get target line to display
pli0 = FindTopCell(cch, pli, ili0, dul, dy, NULL, NULL, 0, &cLine0);
if(!pli0)
uCell &= ~fLowCell; // Whoops, no cell above
else
{
pt.v -= dy;
re.SetCurPoint(pt);
re.Move(-cch);
for(; ili0 < cLine0; ili0++, pli0++)
{
//Support khyphChangeAfter
re.SetIhyphPrev(ili0 > 0 ? (pli0 - 1)->_ihyph : 0);
if(!Render(re, pli0, &rcView, ili0 == cLine0 - 1, ili0, cLine0))
return FALSE;
}
re.SetCp(cpNext); // Bypass [NOTACHAR] CELL
}
}
if(!IsLowCell(uCell)) // Solo cell or top cell of
{ // vertically merged set
if(!Render(re, pli1, &rcView, !pli1->GetPlo(), ili1, cLine1))
return FALSE;
if(dvp) // Rendered set of vmerged cells
{
rcView.bottom -= dvp; // Restore rcView/rcRender bottoms
rcRender.bottom -= dvp;
re.SetRcBottoms(rcView.bottom, rcRender.bottom);
}
}
if(fSetErase)
re.SetErase(TRUE); // Restore CRenderer::_fErase
re.SetRcViewTop(rcView.top); // Restore re._rcView.top in case changed
if(dulRTLRow) // Restore rcView.right
rcView.right = rcView.left - h;
else
rcView.left = rcView.right + h;
}
//Restore previous state
re.SetLayout(ploOld);
re.SetDulLayout(dulLayoutOld);
re.SetDefaultBackColor(crBackOld);
re.SetDefaultTextColor(crTextOld);
re.StartRender(rcViewOld, rcRenderOld);
re.SetCurPoint(ptOld);
// Bypass table-row terminator
AssertSz(re.GetPrevChar() == CELL && pPF->_bTabCount == plo->Count(),
"CLayout::Render:: incorrect table cell count");
AssertSz(re.GetChar() == ENDFIELD, "CLayout::Render: invalid table-row terminator");
re.Move(2); // Bypass table row terminator
AssertSz(re.GetPrevChar() == CR, "invalid table-row terminator");
BOOL fDrawBottomLine = !re._rpTX.IsAtTRD(STARTFIELD);
LONG dvp = re.DrawTableBorders(pPF, u, plo->_dvp,
fDrawBottomLine | fLastLine*2, dul,
ploAbove ? ploAbove->GetPFCells() : NULL);
if(re.IsHidden())
{
CCFRunPtr rp(re);
re.Move(rp.FindUnhiddenForward());
}
if(re.GetChar() == CELL && pli->_fIncludeCell)
{
Assert(pPF->_bTableLevel > 1);
re.Move(1); // Bypass CELL at end of cell
} // containing a table
ptOld.v += pli->GetHeight() + dvp; // Advance to next line position
re.SetCurPoint(ptOld);
if(fLastLine)
re.EraseToBottom();
return TRUE;
}
RECTUV rcRender = re.GetRcRender();
LONG dvpBottom = min(prcView->bottom, rcRender.bottom);
LONG dvpTop = max(prcView->top, rcRender.top);
LONG v0;
dvpTop = max(dvpTop, 0);
// Line's nested layout is a regular layout galley, i.e., not a table row
for(ili1 = 0; ili1 < cLine1; ili1++, pli1++)
{
pt = re.GetCurPoint();
v0 = pt.v + pli1->GetHeight();
fLastLine = ili1 == cLine1 - 1 || v0 >= dvpBottom;
//Support khyphChangeAfter
re.SetIhyphPrev(ili1 > 0 ? (pli1 - 1)->_ihyph : 0);
if(v0 < dvpTop)
{
pt.v = v0; // Advance to next line position
re.SetCurPoint(pt);
re.Move(pli1->_cch);
}
else if(pt.v >= dvpBottom)
re.Move(pli1->_cch); // Get to end of nested display
else if(!Render(re, pli1, prcView, fLastLine, ili1, cLine1))
return FALSE;
}
return TRUE;
}
/*
* CLayout::GetVertAlignShift(uCell, dypText)
*
* @mfunc
* Render visible part of the line *pli
*
* @rdesc
* Vertical shift for cell text
*
* @devnote
* Calculating this shift for vertically merged cells is tricky because
* dypCell = sum of the cell heights of all cells in the vertically
* merged set. In particular, if the table is not nested, one needs to
* wait for recalc of all rows in the set. dypText is relatively easy
* since it's the height of the top cell in the set.
*/
LONG CLayout::GetVertAlignShift(
LONG uCell, //@parm uCell to use
LONG dypText) //@parm Text height in cell
{
LONG dyp = 0;
if(IsVertMergedCell(uCell))
{
}
else if(GetCellVertAlign(uCell))
{
dyp = _dvp - _dvpBrdrTop - _dvpBrdrBot - dypText;
if(dyp > 0 && IsCellVertAlignCenter(uCell))
dyp /= 2;
}
return dyp;
}