/* * 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::SetRun(0, 0); // Be sure current state is valid _pRuns = (CRunArray *)pla; // for new _pRuns } CRunPtr::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::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::GetRun(dRun); AssertSz(dRun + _iRun == 0 , "LP::[]: inconsistent line ptr"); return *(CLine *)CRunPtr::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::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

pages away or

chars, * whichever comes first. If the target page and

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

. To count without being limited by

, set it * equal to tomForward. An initial partial page counts as a page. * * @rdesc * Return the signed cch counted and set

equal to count of * pages actually counted. If no pages are allocated, the text is * treated as a single page. If

= 0, -cch to the start of the * current page is returned. If

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 *

= closest page cpMin = range cpMin, and * set *

= closest page cpMost = 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

in * conjunction with

. 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; } }