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

696 lines
16 KiB
C++

/*
* LINE.CPP
*
* Purpose:
* CLine class
*
* Authors:
* RichEdit 1.0 code: David R. Fulmer
* Christian Fortini (initial conversion to C++)
* Murray Sargent
*
* Copyright (c) 1995-1998 Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_line.h"
#include "_measure.h"
#include "_render.h"
#include "_disp.h"
#include "_edit.h"
ASSERTDATA
/*
* CLine::Measure(&me, cchMax, xWidth, uiFlags, pliTarget)
*
* @mfunc
* Computes line break (based on target device) and fills
* in this CLine with resulting metrics on rendering device
*
* @rdesc
* TRUE if OK
*
* @devnote
* me is moved past line (to beginning of next line). Note: CLock is
* needed in the main four routines (Measure, MeasureText, CchFromXPos,
* and RenderLine), since they use the global (shared) fc().GetCcs()
* facility and may use the LineServices global g_plsc and g_pols.
*/
BOOL CLine::Measure(
CMeasurer& me, //@parm Measurer pointing at text to measure
LONG cchMax, //@parm Max cch to measure
LONG xWidth, //@parm Width of line in device units
UINT uiFlags, //@parm Flags
CLine * pliTarget) //@parm Returns target-device line metrics (optional)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLine::Measure");
CLock lock;
BOOL fFirstInPara = uiFlags & MEASURE_FIRSTINPARA;
BOOL fMultiLine = me.GetPdp()->IsMultiLine();
BOOL fRet;
if(fMultiLine && fFirstInPara && me.GetPrevChar() == VT)
{
fFirstInPara = FALSE;
uiFlags &= ~MEASURE_FIRSTINPARA;
}
if(!(uiFlags & MEASURE_DONTINIT))
me.NewLine(fFirstInPara);
else if(fFirstInPara)
me._li._bFlags |= fliFirstInPara;
BYTE bNumber = me._wNumber < 256 // Store current para # offset
? me._wNumber : 255;
me._li._bNumber = bNumber;
#ifdef LINESERVICES
CMeasurer *pmeSave;
COls * pols = me.GetPols(&pmeSave); // Try for LineServices object
if(pols) // Got it: use LineServices
{
fRet = pols->MeasureLine(xWidth, pliTarget);
pols->SetMeasurer(pmeSave); // Restore previous pme
}
else // LineServices not active
#endif
fRet = me.MeasureLine(cchMax, xWidth, uiFlags, pliTarget);
if(!fRet)
return FALSE;
*this = me._li; // Copy over line info
if(!fMultiLine) // Single-line controls can't
return TRUE; // have paragraph numbering
if(me.IsInOutlineView())
{
if(IsHeadingStyle(me._pPF->_sStyle)) // Store heading number if relevant
_nHeading = (BYTE)(-me._pPF->_sStyle - 1);
if(me._pPF->_wEffects & PFE_COLLAPSED) // Cache collapsed bit
_fCollapsed = TRUE;
}
_bNumber = bNumber;
if(_bFlags & fliHasEOP) // Check for new para number
{
const CParaFormat *pPF = me.GetPF();
me._wNumber = (WORD)pPF->UpdateNumber(me._wNumber, me._pPF);
_fNextInTable = pPF->InTable() && me.GetCp() < me.GetTextLength();
}
return TRUE;
}
/*
* CLine::Render(&re)
*
* @mfunc
* Render visible part of this line
*
* @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.
*/
BOOL CLine::Render(
CRenderer& re) //@parm Renderer to use
{
if(_fCollapsed) // Line is collapsed in Outline view
{
re.Advance(_cch); // Bypass line
return TRUE;
}
BOOL fRet;
CLock lock;
POINT pt = re.GetCurPoint();
#ifdef LINESERVICES
CMeasurer *pmeSave;
COls *pols = re.GetPols(&pmeSave); // Try for LineServices object
if(pols)
{
fRet = pols->RenderLine(*this);
pols->SetMeasurer(pmeSave); // Restore previous pme
}
else
#endif
fRet = re.RenderLine(*this);
pt.y += GetHeight(); // Advance to next line position
re.SetCurPoint(pt);
return fRet;
}
/*
* CLine::CchFromXPos(&me, x, pdispdim, pHit)
*
* @mfunc
* Computes cch corresponding to x position in a line.
* Used for hit testing.
*
* @rdesc
* cch found up to the x coordinate x
*
* @devnote
* me is moved to the cp at the cch offset returned
*/
LONG CLine::CchFromXpos(
CMeasurer& me, //@parm Measurer position at start of line
POINT pt, //@parm pt.x is x coord to search for
CDispDim*pdispdim, //@parm Returns display dimensions
HITTEST *phit, //@parm Returns hit type at x
LONG *pcpActual) const //@parm actual CP above with display dimensions
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLine::CchFromXpos");
#ifdef Boustrophedon
//if(_pPF->_wEffects & PFE_BOUSTROPHEDON)
{
RECT rcView;
me.GetPed()->_pdp->GetViewRect(rcView, NULL);
pt.x = rcView.right - pt.x;
}
#endif
CLock lock;
const BOOL fFirst = _bFlags & fliFirstInPara;
*phit = HT_Text;
LONG cpActual = me.GetCp();
CDispDim dispdim;
me._li = *this;
me._li._cch = 0; // Default zero count
*phit = me.HitTest(pt.x);
if(*phit == HT_Text || *phit == HT_RightOfText) // To right of left margin
{
me.NewLine(fFirst);
#ifdef LINESERVICES
CMeasurer *pmeSave;
COls *pols = me.GetPols(&pmeSave);// Try for LineServices object
if(pols) // Got it: use LineServices
{
pols->CchFromXpos(pt, &dispdim, &cpActual);
pols->SetMeasurer(pmeSave); // Restore previous pme
}
else
#endif
if(me.Measure(pt.x - _xLeft, _cch,
MEASURE_BREAKBEFOREWIDTH | MEASURE_IGNOREOFFSET
| (fFirst ? MEASURE_FIRSTINPARA : 0)) >= 0)
{
LONG xWidthBefore = me._li._xWidth;
cpActual = me.GetCp();
if (me._li._cch < _cch)
{
dispdim.dx = me._xAddLast;
if (pt.x - _xLeft > xWidthBefore + dispdim.dx / 2)
{
me.Advance(1);
me._li._cch++;
me._li._xWidth += dispdim.dx;
}
}
}
me._rpCF.AdjustBackward();
DWORD dwEffects = me.GetCF()->_dwEffects;
if(dwEffects & CFE_LINK)
*phit = HT_Link;
else if(dwEffects & CFE_ITALIC)
*phit = HT_Italic;
#ifdef UNICODE_SURROGATES
// Until we support UTF-16 surrogate characters, don't allow hit in
// middle of a surrogate pair
if(IN_RANGE(0xDC00, me.GetChar(), 0xDFFF))
{
me.Advance(1);
me._li._cch++;
}
#endif
}
if (pdispdim)
*pdispdim = dispdim;
if (pcpActual)
*pcpActual = cpActual;
return me._li._cch;
}
/*
* CLine::XposFromCch(&me, cch, taMode, pdispdim, pdy)
*
* @mfunc
* Measures cch characters starting from this text ptr, returning
* the width measured and setting yOffset = y offset relative to
* top of line and dx = halfwidth of character at me.GetCp() + cch.
* Used for caret placement and object location. pdx returns offset
* into the last char measured (at me.GetCp + cch) if taMode includes
* TA_CENTER (dx = half the last char width) or TA_RIGHT (dx = whole
* char width). pdy returns the vertical offset relative to the top
* of the line if taMode includes TA_BASELINE or TA_BOTTOM.
*
* @rdesc
* width of measured text
*
* @devnote
* me may be moved.
*/
LONG CLine::XposFromCch(
CMeasurer& me, //@parm Measurer pointing at text to measure
LONG cch, //@parm Max cch to measure
UINT taMode, //@parm Text-align mode
CDispDim * pdispdim, //@parm display dimensions
LONG * pdy) const //@parm dy offset due to taMode
{
CLock lock;
LONG xWidth;
BOOL fPols = FALSE;
CDispDim dispdim;
LONG dy = 0;
#ifdef LINESERVICES
CMeasurer *pmeSave;
COls *pols = me.GetPols(&pmeSave); // Try for LineServices object
if(pols)
{ // Got it: use LineServices
if(cch)
taMode &= ~TA_STARTOFLINE; // Not start of line
if(cch != _cch)
taMode &= ~TA_ENDOFLINE; // Not end of line
xWidth = pols->MeasureText(cch, taMode, &dispdim);
pols->SetMeasurer(pmeSave); // Restore previous pme
fPols = TRUE;
}
else
#endif
xWidth = me.MeasureText(cch) + _xLeft;
if(taMode != TA_TOP)
{
// Check for vertical calculation request
if(taMode & TA_BASELINE) // Matches TA_BOTTOM and
{ // TA_BASELINE
if(!_fCollapsed)
{
dy = _yHeight;
AssertSz(_yHeight != -1, "control has no height; used to use default CHARFORMAT");
if((taMode & TA_BASELINE) == TA_BASELINE)
dy -= _yDescent; // Need "== TA_BASELINE" to
} // distinguish from TA_BOTTOM
}
// Check for horizontal calculation request
if(taMode & TA_CENTER && !fPols) // If align to center or right of
{
if (cch == 0)
dispdim.dx = me.MeasureText(1) + _xLeft - xWidth;
else
dispdim.dx = me._xAddLast; // char, get char width
}
}
if (!fPols)
{
if((taMode & TA_CENTER) == TA_CENTER)
xWidth += dispdim.dx / 2;
else if (taMode & TA_RIGHT)
xWidth += dispdim.dx;
}
if (pdispdim)
*pdispdim = dispdim;
if (pdy)
*pdy = dy;
return xWidth;
}
/*
* CLine::GetHeight()
*
* @mfunc
* Get line height unless in outline mode and collasped, in
* which case get 0.
*
* @rdesc
* Line height (_yHeight), unless in outline mode and collapsed,
* in which case 0.
*/
LONG CLine::GetHeight() const
{
return _fCollapsed ? 0 : _yHeight;
}
BOOL CLine::IsEqual(CLine& li)
{
// CF - I dont know which one is faster
// MS3 - CompareMemory is certainly smaller
// return !CompareMemory (this, pli, sizeof(CLine) - 4);
return _xLeft == li._xLeft &&
_xWidth == li._xWidth &&
_yHeight == li._yHeight &&
_yDescent == li._yDescent &&
_cch == li._cch &&
_cchWhite == li._cchWhite;
}
// ===================== CLinePtr: Line Run Pointer ==========================
CLinePtr::CLinePtr(CDisplay *pdp)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::CLinePtr");
_pdp = pdp;
_pLine = NULL;
_pdp->InitLinePtr(* this);
}
void CLinePtr::Init (CLine & line)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::Init");
_pRuns = 0;
_pLine = &line;
_iRun = 0;
_ich = 0;
}
void CLinePtr::Init (CLineArray & line_arr)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::Init");
_pRuns = (CRunArray *) & line_arr;
_iRun = 0;
_ich = 0;
}
void CLinePtr::RpSet(LONG iRun, LONG ich)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpSet");
// See if this is a multi-line ptr
if(_pRuns)
CRunPtr<CLine>::SetRun(iRun, ich);
else
{
// single line, just reinit and set _ich
AssertSz(iRun == 0, "CLinePtr::RpSet() - single line and iRun != 0");
_pdp->InitLinePtr(* this); // to line 0
_ich = ich;
}
}
// Move runptr by a certain number of cch/runs
BOOL CLinePtr::RpAdvanceCp(LONG cch)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpAdvanceCp");
// See if this is a multi-line ptr
if(_pRuns)
return (cch == CRunPtr<CLine>::AdvanceCp(cch));
return RpAdvanceCpSL(cch);
}
BOOL CLinePtr::operator --(int)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::operator --");
return _pRuns ? PrevRun() : OperatorPostDeltaSL(-1);
}
BOOL CLinePtr::operator ++(int)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::operator ++");
return _pRuns ? NextRun() : OperatorPostDeltaSL(+1);
}
/*
* CLinePtr::RpAdvanceCpSL(cch)
*
* @mfunc
* move this line pointer forward or backward on the line
*
* @rdesc
* TRUE iff could advance cch chars within current line
*/
BOOL CLinePtr::RpAdvanceCpSL(
LONG cch) //@parm signed count of chars to advance by
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpAdvanceCpSL");
Assert(!_pRuns);
if(!_pLine)
return FALSE;
_ich += cch;
if(_ich < 0)
{
_ich = 0;
return FALSE;
}
if(_ich > _pLine->_cch)
{
_ich = _pLine->_cch;
return FALSE;
}
return TRUE;
}
/*
* CLinePtr::OperatorPostDeltaSL(Delta)
*
* Purpose:
* Implement line-ptr ++ and -- operators for single-line case
*
* Arguments:
* Delta 1 for ++ and -1 for --
*
* Return:
* TRUE iff this line ptr is valid
*/
BOOL CLinePtr::OperatorPostDeltaSL(LONG Delta)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::OperatorPostDeltaSL");
AssertSz(_iRun <= 1 && !_pRuns,
"LP::++: inconsistent line ptr");
if(_iRun == -Delta) // Operation validates an
{ // invalid line ptr by moving
_pdp->InitLinePtr(* this); // to line 0
return TRUE;
}
_iRun = Delta; // Operation invalidates this line
_ich = 0; // ptr (if it wasn't already)
return FALSE;
}
CLine * CLinePtr::operator ->() const
{
return _pRuns ? (CLine *)_pRuns->Elem(_iRun) : _pLine;
}
CLine * CLinePtr::GetLine() const
{
return _pRuns ? (CLine *)_pRuns->Elem(_iRun) : _pLine;
}
CLine & CLinePtr::operator *() const
{
return *(_pRuns ? (CLine *)_pRuns->Elem(_iRun) : _pLine);
}
CLine & CLinePtr::operator [](LONG dRun)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::operator []");
if(_pRuns)
return *(CLine *)CRunPtr<CLine>::GetRun(dRun);
AssertSz(dRun + _iRun == 0 ,
"LP::[]: inconsistent line ptr");
return *(CLine *)CRunPtr<CLine>::GetRun(_iRun);
}
BOOL CLinePtr::IsValid()
{
return !_pRuns ? _pLine != NULL : CRunPtrBase::IsValid();
}
/*
* CLinePtr::RpSetCp(cp, fAtEnd)
*
* Purpose
* Set this line ptr to cp allowing for ambigous cp and taking advantage
* of _cpFirstVisible and _iliFirstVisible
*
* Arguments:
* cp position to set this line ptr to
* fAtEnd if ambiguous cp:
* if fAtEnd = TRUE, set this line ptr to end of prev line;
* else set to start of line (same cp, hence ambiguous)
* Return:
* TRUE iff able to set to cp
*/
BOOL CLinePtr::RpSetCp(
LONG cp,
BOOL fAtEnd)
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpSetCp");
_ich = 0;
if(!_pRuns)
{
// This is a single line so just go straight to the single
// line advance logic. It is important to note that the
// first visible character is irrelevent to the cp advance
// for single line displays.
return RpAdvanceCpSL(cp);
}
BOOL fRet;
LONG cpFirstVisible = _pdp->GetFirstVisibleCp();
if(cp > cpFirstVisible / 2)
{ // cpFirstVisible closer than 0
_iRun = _pdp->GetFirstVisibleLine();
fRet = RpAdvanceCp(cp - cpFirstVisible);
}
else
fRet = (cp == CRunPtr<CLine>::BindToCp(cp)); // Start from 0
if(fAtEnd) // Ambiguous-cp caret position
AdjustBackward(); // belongs at prev EOL
return fRet;
}
/*
* CLinePtr::FindParagraph(fForward)
*
* @mfunc
* Move this line ptr to paragraph (fForward) ? end : start,
* and return change in cp
*
* @rdesc
* change in cp
*/
LONG CLinePtr::FindParagraph(
BOOL fForward) //@parm TRUE move to para end; else to para start
{
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::FindParagraph");
LONG cch;
CLine * pLine = GetLine();
if(!fForward) // Go to para start
{
cch = 0; // Default already at para start
if (RpGetIch() != pLine->_cch ||
!(pLine->_bFlags & fliHasEOP)) // It isn't at para start
{
cch = -RpGetIch(); // Go to start of current line
while(!(pLine->_bFlags & fliFirstInPara) && (*this) > 0)
{
(*this)--; // Go to start of prev line
pLine = GetLine();
cch -= pLine->_cch; // Subtract # chars in line
}
_ich = 0; // Leave *this at para start
}
}
else // Go to para end
{
cch = GetCchLeft(); // Go to end of current line
while(((*this) < _pdp->LineCount() - 1 ||
_pdp->WaitForRecalcIli((LONG)*this + 1))
&& !((*this)->_bFlags & fliHasEOP))
{
(*this)++; // Go to start of next line
cch += (*this)->_cch; // Add # chars in line
}
_ich = (*this)->_cch; // Leave *this at para end
}
return cch;
}
/*
* CLinePtr::GetAdjustedLineLength
*
* @mfunc returns the length of the line _without_ EOP markers
*
* @rdesc LONG; the length of the line
*/
LONG CLinePtr::GetAdjustedLineLength()
{
CLine * pline = GetLine();
return pline->_cch - pline->_cchEOP;
}
/*
* CLinePtr::GetCchLeft()
*
* @mfunc
* Calculate length of text left in run starting at the current cp.
* Complements GetIch(), which is length of text up to this cp.
*
* @rdesc
* length of text so calculated
*/
LONG CLinePtr::GetCchLeft() const
{
return _pRuns ? CRunPtrBase::GetCchLeft() : _pLine->_cch - _ich;
}
/*
* CLinePtr::GetNumber()
*
* @mfunc
* Get paragraph number
*
* @rdesc
* paragraph number
*/
WORD CLinePtr::GetNumber()
{
if(!IsValid())
return 0;
_pLine = GetLine();
if(!_iRun && _pLine->_bNumber > 1)
_pLine->_bNumber = 1;
return _pLine->_bNumber;
}