|
|
/*
* @doc INTERNAL * * @module RUNPTR.C -- Text run and run pointer class | * * Original Authors: <nl> * Original RichEdit code: David R. Fulmer * Christian Fortini * Murray Sargent * * History: <nl> * 6/25/95 alexgo Commented and Cleaned up. * * Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved. */
#include "_common.h"
#include "_runptr.h"
#include "_text.h"
ASSERTDATA
//
// Invariant stuff
//
#define DEBUG_CLASSNAME CRunPtrBase
#include "_invar.h"
// =========================== CRunPtrBase class ==================================================
#ifdef DEBUG
/*
* CRunPtrBase::Invariant() * * @mfunc * Debug-only function that validates the internal state consistency * for CRunPtrBase * * @rdesc * TRUE always (failures assert) */ BOOL CRunPtrBase::Invariant() const { if(!IsValid()) { Assert(_iRun == 0 && _ich >= 0); // CLinePtr can have _ich > 0
} else { Assert(_iRun < _pRuns->Count()); LONG cch = _pRuns->Elem(_iRun)->_cch; Assert((DWORD)_ich <= (DWORD)cch); } return TRUE; }
/*
* CRunPtrBase::ValidatePtr(pRun) * * @mfunc * Debug-only validation method that asserts if pRun doesn't point * to a valid text run */ void CRunPtrBase::ValidatePtr(void *pRun) const { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::ValidatePtr");
AssertSz(IsValid() && pRun >= _pRuns->Elem(0) && pRun <= _pRuns->Elem(Count() - 1), "CRunPtr::ValidatePtr: illegal ptr"); }
/*
* CRunPtrBase::CalcTextLength() * * @mfunc * Calculate length of text by summing text runs accessible by this * run ptr * * @rdesc * length of text so calculated, or -1 if failed */ LONG CRunPtrBase::CalcTextLength() const { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::CalcTextLength"); AssertSz(_pRuns, "CTxtPtr::CalcTextLength() - Invalid operation on single run CRunPtr");
LONG i = Count(); if(!i) return 0;
LONG cb = _pRuns->Size(); LONG cchText = 0; CTxtRun *pRun = _pRuns->Elem(0);
while(i--) { cchText += pRun->_cch; pRun = (CTxtRun *)((BYTE *)pRun + cb); } return cchText; }
#endif
/*
* CRunPtrBase::GetCchLeft() * * @mfunc * Calculate count of chars left in run starting at current cp. * Complements GetIch(), which is length of text up to this cp. * * @rdesc * Count of chars so calculated */ LONG CRunPtrBase::GetCchLeft() const { return GetRun(0)->_cch - GetIch(); }
/*
* CRunPtrBase::CRunPtrBase(pRuns) * * @mfunc constructor */ CRunPtrBase::CRunPtrBase( CRunArray *pRuns) //@parm The Run array for the run ptr
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::CRunPtrBase");
_pRuns = pRuns; _iRun = 0; _ich = 0;
//make sure everything has been initialized
Assert(sizeof(CRunPtrBase) == (sizeof(_pRuns) + sizeof(_iRun) + sizeof(_ich))); }
/*
* CRunPtrBase::CRunPtrBase(rp) * * Copy Constructor */ CRunPtrBase::CRunPtrBase( CRunPtrBase& rp) //@parm Other run pointer to initialize from
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::CRunPtrBase");
*this = rp; }
/*
* CRunPtrBase::SetRun(iRun, ich) * * @mfunc * Sets this run ptr to the given run. If it does not * exist, then we set ourselves to the closest valid run * * @rdesc * TRUE if moved to iRun */ BOOL CRunPtrBase::SetRun( LONG iRun, //@parm Run index to use
LONG ich) //@parm Char index within run to use
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::SetRun");
_TEST_INVARIANT_
BOOL bRet = TRUE; LONG count = Count();
// Set the run
if(!IsValid()) // No runs instantiated:
return FALSE; // leave this rp alone
if(iRun >= count) // Validate iRun
{ bRet = FALSE; iRun = count - 1; // If (!count), negative iRun
} // is handled by following if
if(iRun < 0) { bRet = FALSE; iRun = 0; } _iRun = iRun;
// Set the offset
_ich = ich; CTxtRun *pRun = _pRuns->Elem(iRun); _ich = min(ich, pRun->_cch);
return bRet; } /*
* CRunPtrBase::NextRun() * * @mfunc * Change this RunPtr to that for the next text run * * @rdesc * TRUE if succeeds, i.e., target run exists */ BOOL CRunPtrBase::NextRun() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::NextRun");
_TEST_INVARIANT_
if(_pRuns && _iRun < Count() - 1) { ++_iRun; _ich = 0; return TRUE; } return FALSE; }
/*
* CRunPtrBase::PrevRun() * * @mfunc * Change this RunPtr to that for the previous text run * * @rdesc * TRUE if succeeds, i.e., target run exists */ BOOL CRunPtrBase::PrevRun() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::PrevRun");
_TEST_INVARIANT_
if(_pRuns) { _ich = 0; if(_iRun > 0) { _iRun--; return TRUE; } } return FALSE; }
/*
* CRunPtrBase::GetRun(cRun) * * @mfunc * Get address of the TxtRun that is cRun runs away from the run * pointed to by this RunPtr * * @rdesc * ptr to the CTxtRun cRun's away */ CTxtRun* CRunPtrBase::GetRun( LONG cRun) const //@parm signed count of runs to reach target CTxtRun
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::GetRun");
_TEST_INVARIANT_ Assert(IsValid()); // Common problem...
return _pRuns->Elem(_iRun + cRun); }
/*
* CRunPtrBase::CalculateCp() * * @mfunc * Get cp of this RunPtr * * @rdesc * cp of this RunPtr * * @devnote * May be computationally expensive if there are many elements * in the array (we have to run through them all to sum cch's. * Used by TOM collections and Move commands, so needs to be fast. */ LONG CRunPtrBase::CalculateCp () const { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::GetCp");
_TEST_INVARIANT_
Assert(IsValid());
LONG cb = _pRuns->Size(); LONG cp = _ich; // Correct result if _iRun = 0
LONG iRun = _iRun;
if(!iRun) return cp;
CTxtRun *pRun = GetRun(0);
while(iRun--) { Assert(pRun); pRun = (CTxtRun *)((BYTE *)pRun - cb); cp += pRun->_cch; } return cp; }
/*
* CRunPtrBase::BindToCp(cp, cchText) * * @mfunc * Set this RunPtr to correspond to a cp. * * @rdesc * the cp actually set to */ LONG CRunPtrBase::BindToCp( LONG cp, //@parm Character position to move this RunPtr to
LONG cchText) //@parm cch in story or 0
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::BindToCp");
if(cp > cchText/2 && _pRuns) // Start from end of story
{ _iRun = Count() - 1; if(_iRun >= 0) { _ich = _pRuns->Elem(_iRun)->_cch; return cchText + Move(cp - cchText); } } _iRun = 0; _ich = 0; return Move(cp); }
/*
* CRunPtrBase::Move(cch) * * @mfunc * Move this RunPtr by (signed) cch chars. If it lands on the * end of a run, it automatically goes to the start of the next run * (if one exists). * * @rdesc * Count of characters actually moved */ LONG CRunPtrBase::Move( LONG cch) //@parm signed count of chars to move this RunPtr by
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::Move");
if(!cch || !IsValid()) return cch;
LONG cchSave = cch;
if(cch < 0) while(cch < 0) { if(-cch <= _ich) { _ich += cch; // Target is in this run
cch = 0; break; } // Otherwise, we need to go to previous run. First add count
// of chars from start of current run to current postion.
cch += _ich; if(_iRun <= 0) // Already in first run
{ _iRun = 0; _ich = 0; // Move to run beginning
break; } _ich = _pRuns->Elem(--_iRun)->_cch; // Move to previous run
} else { LONG cchRun; CTxtRun *pRun = GetRun(0);
while(cch > 0) // Move forward
{ cchRun = pRun->_cch; _ich += cch;
if(_ich < cchRun) // Target is in this run
{ cch = 0; // Signal countdown completed
break; // (if _ich = cchRun, go to
} // next run)
cch = _ich - cchRun; // Move to next run
if(++_iRun >= Count()) // Ran past end, back up to
{ // end of story
--_iRun; Assert(_iRun == Count() - 1); Assert(_pRuns->Elem(_iRun)->_cch == cchRun); _ich = cchRun; break; } _ich = 0; // Start at new BOL
pRun = (CTxtRun *)((BYTE *)pRun + _pRuns->Size()); } }
// NB! we check the invariant at the end to handle the case where
// we are updating the cp for a floating range (i.e., we know that
// the cp is invalid, so we fix it up). So we have to check for
// validity *after* the fixup.
_TEST_INVARIANT_
return cchSave - cch; // Return TRUE if countdown
} // completed
/*
* CRunPtrBase::CountRuns(&cRun, cchMax, cp, cchText) * * @mfunc * Count characters up to <p cRun> runs away or <p cchMax> chars, * whichever comes first. If the target run and <p cchMax> are both * beyond the corresponding end of the document, count up thru the * closest run (0 or Count() - 1). The direction of counting is * determined by the sign of <p cRun>. To count without being limited * by <p cchMax>, set it equal to tomForward. An initial partial * run counts as a run, i.e., if cRun > 0 and _ich < cch in current * run or if cRun < 0 and _ich > 0, that counts as a run. * * @rdesc * Return the signed cch counted and set <p cRun> equal to count of runs * actually counted. If no runs are allocated, the text is treated as * a single run. If <p cRun> = 0, -_ich is returned. If <p cRun> <gt> 0 * and this run ptr points to the end of the last run, no change is made * and 0 is returned. * * @devnote * The maximum count capability is included to be able to count units in * a range. The <p tp> argument is needed for getting the text length * when no runs exist and <p cRun> selects forward counting. */ LONG CRunPtrBase::CountRuns ( LONG & cRun, //@parm Count of runs to get cch for
LONG cchMax, //@parm Maximum char count
LONG cp, //@parm CRchTxtPtr::GetCp()
LONG cchText) const //@parm Text length of associated story
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::CountRuns");
_TEST_INVARIANT_
LONG cch;
if(!cRun) // Nothing to do
return 0;
// Simple special single-run case
if(!IsValid()) // No runs instantiated: act as a
{ // single run
if(cRun > 0) // Count forward
{ cch = cchText - cp; // Partial count to end of run
cRun = 1; // No more than one run
} else // Count backward
{ cch = -cp; // Partial count to start of run
cRun = -1; // No less than minus one run
} if(!cch) // No partial run, so no runs
cRun = 0; // counted
return cch; }
// General case for which runs are instantiated
LONG cb = _pRuns->Size(); // Size of text run element
LONG iDir; LONG iRun = _iRun; // Cache run index for current run
LONG j, k; // Handy integers
CTxtRun * pRun = GetRun(0); // Not NULL since runs exist
if(cRun < 0) // Try to count backward cRun runs
{ iDir = -1; cb = -cb; // Byte count to previous element
cch = _ich; // Remaining cch in current run
if(cch) // If cch != 0, initial run counts
cRun++; // as a run: 1 less for for loop
cRun = max(cRun, -iRun); // Don't let for loop overshoot
} else { // Try to count forward cRun runs
Assert(cRun > 0); iDir = 1; cch = pRun->_cch - _ich; // Remaining cch in current run
if(cch) // If cch != 0, initial run counts
cRun--; // as a run: 1 less for for loop
k = Count() - iRun - 1; // k = # runs following current run
cRun = min(cRun, k); // Don't let for loop overshoot
}
k = cch; // Remember if initial cch != 0
for(j = cRun; j && cch < cchMax; j -= iDir) { pRun = (CTxtRun *)((BYTE *)pRun + cb); // Point at following run
cch += pRun->_cch; // Add in its count
} if(k) // Initial partial run counts as
cRun += iDir; // a run
cRun -= j; // Discount any runs not counted
// if |cch| >= cchMax
return iDir*cch; // Return total cch bypassed
}
/*
* CRunPtrBase::FindRun (pcpMin, pcpMost, cpMin, cch) * * @mfunc * Set *<p pcpMin> = closest run cpMin <lt>= range cpMin, and * set *<p pcpMost> = closest run cpMost <gt>= range cpMost * * @devnote * This routine plays a role analogous to CTxtRange::FindParagraph * (pcpMin, pcpMost), but needs extra arguments since this run ptr does * not know the range cp's. This run ptr is located at the range active * end, which is determined by the range's signed length <p cch> in * conjunction with <p cpMin>. */ void CRunPtrBase::FindRun ( LONG *pcpMin, //@parm Out parm for bounding-run cpMin
LONG *pcpMost, //@parm Out parm for bounding-run cpMost
LONG cpMin, //@parm Range cpMin
LONG cch, //@parm Range signed length
LONG cchText) const //@parm Story length
{ TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::FindRun");
if(!IsValid()) { if(pcpMin) // Run is whole story
*pcpMin = 0; if(pcpMost) *pcpMost = cchText; return; }
BOOL fMove; // Controls Move for pcpMost
CRunPtrBase rp((CRunPtrBase&)(*this)); // Clone this runptr to keep it const
rp.AdjustForward(); // Select forward run
if(pcpMin) { // If cch != 0, rp is sure to end up
fMove = cch; // at cpMin, so pcpMost needs Move
if(cch > 0) // rp is at cpMost, so move it to
rp.Move(-cch); // cpMin
*pcpMin = cpMin - rp._ich; // Subtract off offset in this run
} else fMove = cch < 0; // Need to move to get pcpMost
if(pcpMost) { cch = abs(cch); if(fMove) // Move to cpMost = cpMin + cch,
rp.Move(cch); // i.e., range's cpMost
if(cch) rp.AdjustBackward(); // Since nondegenerate range
*pcpMost = cpMin + cch // Add remaining cch in run to range's
+ rp.GetCchLeft(); // cpMost
} }
/*
* CRunPtrBase::AdjustBackward() * * @mfunc * If the cp for this run ptr is at the "boundary" or edge between two * runs, then make sure this run ptr points to the end of the first run. * * @comm * This function does nothing unless this run ptr points to the beginning * or the end of a run. This function may be needed in those cases * because a cp at the beginning of a run is identical to the cp for the * end of the previous run (if it exists), i.e., such an "edge" cp is * ambiguous, and you may need to be sure that this run ptr points to the * end of the first run. * * For example, consider a run that describes characters at cp's 0 to 10 * followed by a run that describes characters at cp's 11 through 12. For * a cp of 11, it is possible for the run ptr to be either at the *end* * of the first run or at the *beginning* of the second run. * * @rdesc nothing */ void CRunPtrBase::AdjustBackward() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::AdjustBackward");
_TEST_INVARIANT_
if(!_ich && PrevRun()) // If at start of run that isn't
_ich = _pRuns->Elem(_iRun)->_cch; // the first, go to end of
} // previous run
/*
* CRunPtrBase::AdjustForward() * * @mfunc * If the cp for this run ptr is at the "boundary" or edge between two * runs, then make sure this run ptr points to the start of the second * run. * * @rdesc * nothing * * @xref * <mf CRunPtrBase::AdjustBackward> */ void CRunPtrBase::AdjustForward() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::AdjustForward");
_TEST_INVARIANT_
if(!IsValid()) return;
CTxtRun *pRun = _pRuns->Elem(_iRun);
if(pRun->_cch == _ich) // If at end of run, go to start
NextRun(); // of next run (if it exists)
}
/*
* CRunPtrBase::SetToNull() * * @mfunc * Sets all run pointer information to be NULL. This * is designed to handle the response to clearing document runs * such as when we convert from rich text to plain text. * * @rdesc * VOID */ void CRunPtrBase::SetToNull() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CRunPtrBase::SetToNull");
_pRuns = NULL; _iRun = 0; _ich = 0; }
|