|
|
/*
* LINE.CPP * * Purpose: * CLine class * * Authors: * RichEdit 1.0 code: David R. Fulmer * Christian Fortini (initial conversion to C++) * Murray Sargent * * Copyright (c) 1995-2000 Microsoft Corporation. All rights reserved. */
#include "_common.h"
#include "_line.h"
#include "_measure.h"
#include "_render.h"
#include "_disp.h"
#include "_dispml.h"
#include "_edit.h"
ASSERTDATA
extern BOOL g_OLSBusy;
/*
* CLine::Measure(&me, 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, CchFromUp, * 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
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; }
me.NewLine(fFirstInPara);
if(fFirstInPara) me._li._fFirstInPara = TRUE;
BYTE bNumber = me._wNumber < 256 // Store current para # offset
? me._wNumber : 255; me._li._bNumber = bNumber; me._fMeasure = TRUE;
//REVIEW (keithcu) uiFlags aren't needed in LS model? Can I remove
//from the other model, too?
#ifndef NOLINESERVICES
COls * pols = me.GetPols(); // Try for LineServices object
if(pols) { // Got it: use LineServices
fRet = pols->MeasureLine(pliTarget); g_OLSBusy = FALSE; } else // LineServices not active
#endif
fRet = me.MeasureLine(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(IsHeadingStyle(me._pPF->_sStyle)) // Store heading number if relevant
_nHeading = (BYTE)(-me._pPF->_sStyle - 1);
if(me.IsInOutlineView() && me._pPF->_wEffects & PFE_COLLAPSED) // Cache collapsed bit
_fCollapsed = TRUE;
_bNumber = bNumber; if(_fHasEOP) // Check for new para number
{ const CParaFormat *pPF = me.GetPF();
me._wNumber = (WORD)pPF->UpdateNumber(me._wNumber, me._pPF); } if(me.GetPrevChar() == FF) _fHasFF = TRUE;
return TRUE; } /*
* CLine::Render(&re, fLastLine) * * @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
BOOL fLastLine) //@parm TRUE iff last line in layout
{ if(_fCollapsed) // Line is collapsed in Outline view
{ re.Move(_cch); // Bypass line
return TRUE; }
BOOL fRet; CLock lock; POINTUV pt = re.GetCurPoint();
#ifndef NOLINESERVICES
COls *pols = re.GetPols(); // Try for LineServices object
if(pols) { fRet = pols->RenderLine(*this, fLastLine); g_OLSBusy = FALSE; } else #endif
fRet = re.RenderLine(*this, fLastLine);
pt.v += GetHeight(); // Advance to next line position
re.SetCurPoint(pt); return fRet; }
/*
* CLine::CchFromUp(&me, pt, pdispdim, pHit, pcpActual) * * @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::CchFromUp( CMeasurer& me, //@parm Measurer position at start of line
POINTUV pt, //@parm pt.u is u coord to search for
CDispDim*pdispdim, //@parm Returns display dimensions
HITTEST *phit, //@parm Returns hit type at x
LONG *pcpActual) const //@parm actual CP mouse is above
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLine::CchFromUp"); CLock lock; const BOOL fFirst = _fFirstInPara; *phit = HT_Text; LONG cpActual = me.GetCp(); CDispDim dispdim; me._li = *this; *phit = me.HitTest(pt.u); me._li._cch = 0; // Default zero count
if(*phit == HT_Text || *phit == HT_RightOfText) // To right of left margin
{ me.NewLine(*this);
#ifndef NOLINESERVICES
COls *pols = me.GetPols(); // Try for LineServices object
if(pols) // Got it: use LineServices
{ pols->CchFromUp(pt, &dispdim, &cpActual); g_OLSBusy = FALSE; } else #endif
if(me.Measure(me.DUtoLU(pt.u - _upStart), _cch, MEASURE_BREAKBEFOREWIDTH | MEASURE_IGNOREOFFSET | (fFirst ? MEASURE_FIRSTINPARA : 0)) >= 0) { LONG dupBefore = me._li._dup; cpActual = me.GetCp(); if (me._li._cch < _cch) { LONG dup = pt.u - _upStart - dupBefore; dispdim.dup = me._dupAddLast; if(dup > dispdim.dup / 2 || dup > W32->GetDupSystemFont()/2 && me.GetChar() == WCH_EMBEDDING) { me.Move(1); me._li._cch++; me._li._dup += dispdim.dup; } } }
me._rpCF.AdjustForward(); if(cpActual < me.GetCp() || pt.u >= _upStart + _dup) me._rpCF.AdjustBackward(); DWORD dwEffects = me.GetCF()->_dwEffects; if(dwEffects & CFE_LINK) { if(cpActual < me.GetTextLength()) *phit = HT_Link; } else if(dwEffects & CFE_ITALIC) *phit = HT_Italic; }
if (pdispdim) *pdispdim = dispdim; if (pcpActual) *pcpActual = cpActual;
return me._li._cch; }
/*
* CLine::UpFromCch(&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::UpFromCch( 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 dup; BOOL fPols = FALSE; CDispDim dispdim; LONG dy = 0;
#ifndef NOLINESERVICES
COls *pols = me.GetPols(); // 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
dup = pols->MeasureText(cch, taMode, &dispdim); fPols = TRUE; g_OLSBusy = FALSE; } else #endif
{ dup = me.MeasureText(cch) + _upStart; dispdim.dup = me._dupAddLast; }
if(taMode != TA_TOP) { // Check for vertical calculation request
if(taMode & TA_BASELINE) // Matches TA_BOTTOM and
{ // TA_BASELINE
if(!_fCollapsed) { dy = _dvpHeight; AssertSz(_dvpHeight != -1, "control has no height; used to use default CHARFORMAT"); if((taMode & TA_BASELINE) == TA_BASELINE) { dy -= _dvpDescent; // Need "== TA_BASELINE" to
if(!_dvpDescent) // distinguish from TA_BOTTOM
dy--; // Compensate for weird fonts
} } } }
LONG dupAdd = 0;
if((taMode & TA_CENTER) == TA_CENTER) dupAdd = dispdim.dup / 2; else if (taMode & TA_RIGHT) dupAdd = dispdim.dup;
if (dispdim.lstflow == lstflowWS && (taMode & TA_LOGICAL)) dupAdd = -dupAdd;
dup += dupAdd;
if (pdispdim) *pdispdim = dispdim; if (pdy) *pdy = dy;
return max(dup, 0); } /*
* 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 { if (_fCollapsed) return 0;
return IsNestedLayout() ? _plo->_dvp : _dvpHeight; }
/*
* CLine::GetDescent() * * @mfunc * Return descent of line. Assumed not to be collapsed * * @rdesc */ LONG CLine::GetDescent() const { return IsNestedLayout() ? 0 : _dvpDescent; }
BOOL CLine::IsEqual(CLine& li) { return _upStart == li._upStart && _plo == li._plo && //checks _yHeight, _yDescent OR _plo
_dup == li._dup && _cch == li._cch; }
// ===================== 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::Set( LONG iRun, LONG ich, CLineArray *pla) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpSet");
// See if this is a multi-line ptr
if(_pRuns) { if(pla) { CRunPtr<CLine>::SetRun(0, 0); // Be sure current state is valid
_pRuns = (CRunArray *)pla; // for new _pRuns
} CRunPtr<CLine>::SetRun(iRun, ich); // Now set to desired run & ich
} else { // single line, just reinit and set _ich
AssertSz(iRun == 0, "CLinePtr::Set() - single line and iRun != 0"); _pdp->InitLinePtr(* this); // to line 0
_ich = ich; } }
// Move runptr by a certain number of cch/runs
BOOL CLinePtr::Move( LONG cch) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpMove");
// See if this is a multi-line ptr
if(_pRuns) return (cch == CRunPtr<CLine>::Move(cch));
return MoveSL(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::MoveSL(cch) * * @mfunc * move this line pointer forward or backward on the line * * @rdesc * TRUE iff could Move cch chars within current line */ BOOL CLinePtr::MoveSL( LONG cch) //@parm signed count of chars to Move by
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpMoveSL");
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) * * @mfunc * Implement line-ptr ++ and -- operators for single-line case * * @rdesc * TRUE iff this line ptr is valid */ BOOL CLinePtr::OperatorPostDeltaSL( LONG Delta) //@parm 1 for ++ and -1 for --
{ 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() const { return !_pRuns ? _pLine != NULL : CRunPtrBase::IsValid(); }
/*
* CLinePtr::SetCp(cp, fAtEnd, lNest) * * @mfunc * Set this line ptr to cp allowing for ambigous cp and taking advantage * of _cpFirstVisible and _iliFirstVisible * * @rdesc * TRUE iff able to set to cp */ BOOL CLinePtr::SetCp( LONG cp, //@parm Position to set this line ptr to
BOOL fAtEnd, //@parm If ambiguous cp: if fAtEnd = TRUE, set this
// line ptr to end of prev line; else to line start
LONG lNest) //@parm Set to deep CLine in nested layouts
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLinePtr::RpSetCp");
_ich = 0; if(!_pRuns) { // This is a single line so just go straight to the single
// line Move logic. It is important to note that the
// first visible character is irrelevent to the cp Move
// for single line displays.
return MoveSL(cp); }
BOOL fRet; LONG cpFirstVisible = _pdp->GetFirstVisibleCp();
if(cp > cpFirstVisible / 2) { // cpFirstVisible closer than 0
_iRun = _pdp->GetFirstVisibleLine(); fRet = Move(cp - cpFirstVisible); } else fRet = (cp == CRunPtr<CLine>::BindToCp(cp));// Start from 0
if(lNest) { CLayout *plo; while(plo = GetLine()->GetPlo()) { LONG cch = _ich; if(plo->IsTableRow()) { if(cch <= 2 && lNest == 1) // At start of table row:
break; // leave this rp there
cch -= 2; // Bypass table row start code
} Set(0, 0, (CLineArray *)plo); // Goto start of layout plo
Move(cch); // Move to parent _ich
} }
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 * pli = GetLine();
if(!fForward) // Go to para start
{ cch = 0; // Default already at para start
if (_ich != pli->_cch || !(pli->_fHasEOP)) // It isn't at para start
{ cch = -_ich; // Go to start of current line
while(!(pli->_fFirstInPara) && _iRun > 0) { pli--; _iRun--; cch -= pli->_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
if(!_pRuns) return cch; // Single line
LONG cLine = _pRuns->Count(); BOOL fNested = _pRuns->Elem(0) != ((CDisplayML *)_pdp)->Elem(0);
while((_iRun < cLine - 1 || !fNested && _pdp->WaitForRecalcIli(_iRun + 1)) && !(pli->_fHasEOP)) { pli++; // Go to start of next line
_iRun++; cch += pli->_cch; // Add # chars in line
} _ich = pli->_cch; // Leave *this at para end
} return cch; }
/*
* CLinePtr::GetAdjustedLineLength() * * @mfunc returns length of line _without_ EOP markers * * @rdesc LONG; length of 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; }
/*
* CLinePtr::CountPages(&cPage, cchMax, cp, cchText) * * @mfunc * Count characters up to <p cPages> pages away or <p cchMax> chars, * whichever comes first. If the target page and <p cchMax> are both * beyond the corresponding end of the document, count up thru the * closest page. The direction of counting is determined by the sign * of <p cPage>. To count without being limited by <p cchMax>, set it * equal to tomForward. An initial partial page counts as a page. * * @rdesc * Return the signed cch counted and set <p cPage> equal to count of * pages actually counted. If no pages are allocated, the text is * treated as a single page. If <p cPage> = 0, -cch to the start of the * current page is returned. If <p cPage> <gt> 0 and cp is at the end * of the document, 0 is returned. * * @devnote * The maximum count capability is included to be able to count units in * a range. * * @todo * EN_PAGECHANGE */ LONG CLinePtr::CountPages ( LONG &cPage, //@parm Count of pages to get cch for
LONG cchMax, //@parm Maximum char count
LONG cp, //@parm CRchTxtPtr::GetCp()
LONG cchText) const { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CLinePtr::CountPages");
if(!_pdp->IsInPageView()) { cPage = 0; return tomBackward; // Signal error
} Assert(IsValid());
LONG cch; LONG j = cPage; CLine *pli = (CLine *)GetLine(); // Not NULL since lines exist
if(cPage < 0) // Try to count backward cPage pages
{ // TODO: eliminate cchText and cp (currently only used for validation)
Assert(cchMax <= cp); // Don't undershoot
for(cch = _ich; j && cch <= cchMax; cch += pli->_cch) { if(pli->_fFirstOnPage && cch) // !cch prevents counting current
j++; // page if at start of that page
if(cch >= cchMax) { Assert(cch == cp); break; // At beginning of doc, so done
}
if (!j) break; // Done counting backward
pli--; VALIDATE_PTR(pli); } cPage -= j; // Discount any pages not counted
return -cch; }
Assert(cPage > 0 && cchMax <= cchText - cp);
for(cch = GetCchLeft(); cch < cchMax; cch += pli->_cch) { pli++; VALIDATE_PTR(pli); if(pli->_fFirstOnPage && cch) // !cch prevents counting current
{ // page if at start of that page
j--; if(!j) break; } } cPage -= j; // Discount any pages not counted
return cch; }
/*
* CLinePtr::FindPage (pcpMin, pcpMost, cpMin, cch, cchText) * * @mfunc * Set *<p pcpMin> = closest page cpMin <lt>= range cpMin, and * set *<p pcpMost> = closest page cpMost <gt>= range cpMost * * @devnote * This routine plays a role analogous to CTxtRange::FindParagraph * (pcpMin, pcpMost), but needs extra arguments since this line ptr does * not know the range cp's. This line ptr is located at the range active * end, which is determined by the range's signed length <p cch> in * conjunction with <p cpMin>. See also the very similar function * CRunPtrBase::FindRun(). The differences seem to make a separate * encoding simpler. */ void CLinePtr::FindPage ( LONG *pcpMin, //@parm Out parm for bounding-page cpMin
LONG *pcpMost, //@parm Out parm for bounding-page cpMost
LONG cpMin, //@parm Range cpMin
LONG cch, //@parm Range signed length
LONG cchText) //@parm Story length
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CLinePtr::FindPage");
Assert(_pdp->IsMultiLine() && _pdp->IsInPageView());
LONG cp; BOOL fMove; // Controls Move for pcpMost
LONG i; CLine *pli;
AdjustForward(); // Select forward line
if(pcpMin) { // If cch != 0, rp is sure to end up
fMove = cch; // at cpMin, so pcpMost needs advance
if(cch > 0) // rp is at cpMost, so move it to
Move(-cch); // cpMin
cp = cpMin - _ich; // Subtract off line offset in this run
pli = (CLine *)GetLine(); for(i = GetLineIndex(); i > 0 && !pli->_fFirstOnPage; i--) { pli--; cp -= pli->_cch; } *pcpMin = cp; } else fMove = cch < 0; // Need to advance to get pcpMost
if(pcpMost) { LONG cLine = ((CDisplayML *)_pdp)->Count();
cch = abs(cch); if(fMove) // Advance to cpMost = cpMin + cch,
Move(cch); // i.e., range's cpMost
cp = cpMin + cch; pli = (CLine *)GetLine(); i = GetLineIndex(); if(pcpMin && cp == *pcpMin) // Expand IP to next page
{ Assert(!_ich); cp += pli->_cch; // Include first line even if it starts
pli++; // a new page (pli->_fFirstOnPage = 1)
i++; } else if (_ich) { // If not at start of line, add
cp += GetCchLeft(); // remaining cch in run to cpMost, and
pli++; // skip to next line
i++; }
while(i < cLine && !pli->_fFirstOnPage) { cp += pli->_cch; // Add in next line's
pli++; i++; } *pcpMost = cp; } }
|