/* * @doc INTERNAL * * @module TEXT.C -- CTxtPtr implementation | * * Authors: * Original RichEdit code: David R. Fulmer * Christian Fortini * Murray Sargent * * History: * 6/25/95 alexgo cleanup and reorganization (use run pointers now) * * Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved. */ #include "_common.h" #include "_text.h" #include "_edit.h" #include "_antievt.h" #include "_clasfyc.h" #include "_txtbrk.h" ASSERTDATA //-----------------------------Internal functions-------------------------------- // Text Block management static void TxDivideInsertion(LONG cch, LONG ichBlock, LONG cchAfter, LONG *pcchFirst, LONG *pcchLast); /* * IsWhiteSpace(ch) * * @func * Used to determine if ch is an EOP char (see IsEOP() for definition), * TAB or blank. This function is used in identifying sentence start * and end. * * @rdesc * TRUE if ch is whitespace */ BOOL IsWhiteSpace(unsigned ch) { return ch == ' ' || IN_RANGE(CELL, ch, CR) || (ch | 1) == PS; } /* * IsSentenceTerminator(ch) * * @func * Used to determine if ch is a standard sentence terminator character, * namely, '?', '.', or '!' * * @rdesc * TRUE if ch is a question mark, period, or exclamation point. */ BOOL IsSentenceTerminator(unsigned ch) { return ch == '?' || ch == '.' || ch == '!'; // Std sentence delimiters } // =========================== Invariant stuff ================================================== #define DEBUG_CLASSNAME CTxtPtr #include "_invar.h" // =============================== CTxtPtr ====================================================== #ifdef DEBUG /* * CTxtPtr::Invariant * * @mfunc invariant check */ BOOL CTxtPtr::Invariant() const { static LONG numTests = 0; numTests++; // Counts how many times we've been called // Make sure _cp is within range Assert(_cp >= 0); Update_pchCp(); CRunPtrBase::Invariant(); if(IsValid()) { // We use less than or equals here so that we can be an insertion // point at the *end* of the currently existing text. Assert(_cp <= GetTextLength()); // Make sure all the blocks are consistent... Assert(GetTextLength() == ((CTxtArray *)_pRuns)->Invariant()); Assert(_cp == CRunPtrBase::CalculateCp()); } else { Assert(_ich == 0); } return TRUE; } /* * CTxtPtr::Update_pchCp () * * @mfunc * Define _pchCp to be ptr to text at _cp */ void CTxtPtr::Update_pchCp() const { LONG cchValid; *(LONG_PTR *)&_pchCp = (LONG_PTR)GetPch(cchValid); if(!cchValid) *(LONG_PTR *)&_pchCp = (LONG_PTR)GetPchReverse(cchValid); } /* * CTxtPtr::MoveGapToEndOfBlock () * * @mfunc * Function to move buffer gap to current block end to aid in debugging */ void CTxtPtr::MoveGapToEndOfBlock () const { CTxtBlk *ptb = GetRun(0); ptb->MoveGap(ptb->_cch); // Move gaps to end of cur block Update_pchCp(); } #endif // DEBUG /* * CTxtPtr::CTxtPtr(ped, cp) * * @mfunc constructor */ CTxtPtr::CTxtPtr ( CTxtEdit *ped, //@parm Ptr to CTxtEdit instance LONG cp) //@parm cp to set the pointer to { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::CTxtPtr"); _ped = ped; _cp = 0; SetRunArray((CRunArray *) &ped->GetTxtStory()->_TxtArray); if(IsValid()) _cp = BindToCp(cp); } /* * CTxtPtr::CTxtPtr(&tp) * * @mfunc Copy Constructor */ CTxtPtr::CTxtPtr ( const CTxtPtr &tp) { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::CTxtPtr"); // copy all the values over *this = tp; } /* * CTxtPtr::GetTextLength() * * @mfunc * Return count of characters in the story pointed to by this * text ptr. Includes the story's final CR in the count * * @rdesc * cch for the story pointed to by this text ptr * * @devnote * This method returns 0 if the text ptr is a zombie, a state * identified by _ped = NULL. */ LONG CTxtPtr::GetTextLength() const { return _ped ? ((CTxtArray *)_pRuns)->_cchText : 0; } /* * CTxtPtr::GetChar() * * @mfunc * Return character at this text pointer, NULL if text pointer is at * end of text * * @rdesc * Character at this text ptr */ WCHAR CTxtPtr::GetChar() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetChar"); LONG cchValid; const WCHAR *pch = GetPch(cchValid); return pch ? *pch : 0; } /* * CTxtPtr::GetPrevChar() * * @mfunc * Return character just before this text pointer, NULL if text pointer * beginning of text * * @rdesc * Character just before this text ptr */ WCHAR CTxtPtr::GetPrevChar() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPrevChar"); LONG cchValid; const WCHAR *pch = GetPchReverse(cchValid); return pch ? *(pch - 1) : 0; } /* * CTxtPtr::GetPch(&cchValid) * * @mfunc * return a character pointer to the text at this text pointer * * @rdesc * a pointer to an array of characters. May be NULL. If non-null, * then cchValid is guaranteed to be at least 1 */ const WCHAR * CTxtPtr::GetPch( LONG & cchValid) const //@parm Count of chars for which ptr is valid { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPch"); // returned pointer is valid LONG ich = _ich; WCHAR * pchBase; CTxtBlk * ptb = IsValid() ? GetRun(0) : NULL; cchValid = 0; // Default nothing valid if(!ptb) return NULL; // If we're at the edge of a run, grab the next run or // stay at the current run. if(_ich == ptb->_cch) { if(_iRun < Count() - 1) { // Set us to the next text block ptb = GetRun(1); ich = 0; } else // At very end of text: return NULL; // just return NULL } AssertSz(CbOfCch(ich) <= ptb->_cbBlock, "CTxtPtr::GetPch(): _ich bigger than block"); pchBase = ptb->_pch + ich; // Check to see if we need to skip over gap. Recall that // the gap may come anywhere in the middle of a block, // so if the current ich (note, no underscore, we want // the active ich) is beyond the gap, then recompute pchBase // by adding in the size of the block. // // cchValid will then be the number of characters left in // the text block (or _cch - ich) if(CbOfCch(ich) >= ptb->_ibGap) { pchBase += CchOfCb(ptb->_cbBlock) - ptb->_cch; cchValid = ptb->_cch - ich; } else { // We're valid until the buffer gap (or see below). cchValid = CchOfCb(ptb->_ibGap) - ich; } AssertSz(cchValid > 0 && GetCp() + cchValid <= GetTextLength(), "CTxtPtr::GetPch: illegal cchValid"); return pchBase; } /* * CTxtPtr::GetPchReverse(&cchValidReverse, pcchValid) * * @mfunc * return a character pointer to the text at this text pointer * adjusted so that there are some characters valid *behind* the * pointer. * * @rdesc * a pointer to an array of characters. May be NULL. If non-null, * then cchValidReverse is guaranteed to be at least 1 */ const WCHAR * CTxtPtr::GetPchReverse( LONG & cchValidReverse, //@parm length for reverse LONG * pcchValid) const //@parm length forward { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPchReverse"); LONG cchTemp; LONG ich = _ich; WCHAR * pchBase; CTxtBlk * ptb = IsValid() ? GetRun(0) : NULL; cchValidReverse = 0; // Default no valid chars in run if(!ptb) return NULL; // If we're at the edge of a run, grab the previous run or // stay at the current run. if(!_ich) { if(_iRun) { ptb = GetRun(-1); // Go to next text block ich = ptb->_cch; } else // At start of text: return NULL; // just return NULL } AssertSz(CbOfCch(ich) <= ptb->_cbBlock, "CTxtPtr::GetPchReverse(): _ich bigger than block"); pchBase = ptb->_pch + ich; // Check to see if we need to skip over gap. Recall that // the game may come anywhere in the middle of a block, // so if the current ich (note, no underscore, we want // the active ich) is at least one char past the gap, then recompute // pchBase by adding the size of the gap (so that it's after // the gap). This differs from GetPch(), which works forward and // wants pchBase to include the gap size if ich is at the gap, let // alone one or more chars past it. // // Also figure out the count of valid characters. It's // either the count of characters from the beginning of the // text block, i.e. ich, or the count of characters from the // end of the buffer gap. cchValidReverse = ich; // Default for ich <= gap offset cchTemp = ich - CchOfCb(ptb->_ibGap); // Calculate displacement if(cchTemp > 0) // Positive: pchBase is after gap { cchValidReverse = cchTemp; pchBase += CchOfCb(ptb->_cbBlock) - ptb->_cch; // Add in gap size } if(pcchValid) // if client needs forward length { if(cchTemp > 0) cchTemp = ich - ptb->_cch; else cchTemp = -cchTemp; *pcchValid = cchTemp; } AssertSz(cchValidReverse > 0 && GetCp() - cchValidReverse >= 0, "CTxtPtr::GetPchReverse: illegal cchValidReverse"); return pchBase; } /* * CTxtPtr::GetCharFlagsInRange(cch, iCharRepDefault) * * @mfunc * return CharFlags for the range of chars starting at this text pointer * for cch chars. * * @rdesc * CharFlags for the range of chars */ QWORD CTxtPtr::GetCharFlagsInRange( LONG cch, BYTE iCharRepDefault) { QWORD qw = 0; QWORD qw0; WCHAR szch[10]; cch = min(cch + 1, 10); cch = GetText(cch, szch); for(WCHAR *pch = szch; cch > 0; cch--, pch++) { qw0 = GetCharFlags(pch, cch, iCharRepDefault); if(qw0 & FSURROGATE) { cch--; pch++; } qw |= qw0; } return qw; } /* * CTxtPtr::BindToCp(cp) * * @mfunc * set cached _cp = cp (or nearest valid value) * * @rdesc * _cp actually set * * @comm * This method overrides CRunPtrBase::BindToCp to keep _cp up to date * correctly. * * @devnote * Do *not* call this method when high performance is needed; use * Move() instead, which moves from 0 or from the cached * _cp, depending on which is closer. */ LONG CTxtPtr::BindToCp( LONG cp) //@parm char position to bind to { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::BindToCp"); _cp = CRunPtrBase::BindToCp(cp, GetTextLength()); // We want to be able to use this routine to fix up things so we don't // check invariants on entry. _TEST_INVARIANT_ return _cp; } /* * CTxtPtr::SetCp(cp) * * @mfunc * 'efficiently' sets cp by advancing from current position or from 0, * depending on which is closer * * @rdesc * cp actually set to */ LONG CTxtPtr::SetCp( LONG cp) //@parm char position to set to { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::SetCp"); Move(cp - _cp); return _cp; } /* * CTxtPtr::Move(cch) * * @mfunc * Move cp by cch characters * * @rdesc * Actual number of characters Moved by * * @comm * We override CRunPtrBase::Move so that the cached _cp value * can be correctly updated and so that the move can be made * from the cached _cp or from 0, depending on which is closer. * * @devnote * It's also easy to bind at the end of the story. So an improved * optimization would bind there if 2*(_cp + cch) > _cp + text length. */ LONG CTxtPtr::Move( LONG cch) // @parm count of chars to move by { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::Move"); if(!IsValid()) // No runs yet, so don't go return 0; // anywhere const LONG cpSave = _cp; // Save entry _cp LONG cp = cpSave + cch; // Requested target cp (maybe < 0) if(cp < cpSave/2) // Closer to 0 than cached cp { cp = max(cp, 0); // Don't undershoot _cp = CRunPtrBase::BindToCp(cp); } else _cp += CRunPtrBase::Move(cch); // exist // NB! the invariant check needs to come at the end; we may be // moving 'this' text pointer in order to make it valid again // (for the floating range mechanism). _TEST_INVARIANT_ return _cp - cpSave; // cch this CTxtPtr moved } /* * CTxtPtr::GetText(cch, pch) * * @mfunc * get a range of cch characters starting at this text ptr. A literal * copy is made, i.e., with no CR -> CRLF and WCH_EMBEDDING -> ' ' * translations. For these translations, see CTxtPtr::GetPlainText() * * @rdesc * count of characters actually copied * * @comm * Doesn't change this text ptr */ LONG CTxtPtr::GetText( LONG cch, //@parm Count of characters to get WCHAR * pch) //@parm Buffer to copy the text into { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetText"); LONG cchSave = cch; LONG cchValid; const WCHAR *pchRead; CTxtPtr tp(*this); _TEST_INVARIANT_ // Use tp to read valid blocks of text until all the requested // text is read or until the end of story is reached. while( cch ) { pchRead = tp.GetPch(cchValid); if(!pchRead) // No more text break; cchValid = min(cchValid, cch); CopyMemory(pch, pchRead, cchValid*sizeof(WCHAR)); pch += cchValid; cch -= cchValid; tp.Move(cchValid); } return cchSave - cch; } #ifndef NOCOMPLEXSCRIPTS /* * OverRideNeutralChar(ch) * * @mfunc * Helper for overriding BiDi neutral character classification. * Option is used in Access Expression Builder. * * @rdesc * Modified character or unmodified input character */ WCHAR OverRideNeutralChar(WCHAR ch) { if(ch < '!') return ch == CELL ? CR : ch; if(ch > '}') return ch; if (IN_RANGE('!', ch, '>')) { // True for !"#&'()*+,-./:;<=> if ((0x00000001 << (ch - TEXT(' '))) & 0x7C00FFCE) ch = 'a'; } if (IN_RANGE('[', ch, '^') || ch == '{' || ch == '}') { // True for [/]^{} ch = 'a'; } return ch; } /* * CTxtPtr::GetTextForUsp(cch, pch, fNeutralOverride) * * @mfunc * get a range of cch characters starting at this text ptr. A literal * copy is made, with translation to fool Uniscribe classification * * @rdesc * count of characters actually copied * * @comm * Doesn't change this text ptr */ LONG CTxtPtr::GetTextForUsp( LONG cch, //@parm Count of characters to get WCHAR * pch, //@parm Buffer to copy the text into BOOL fNeutralOverride) //@parm Neutral override option { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetTextForUsp"); LONG cchSave = cch; LONG cchValid; const WCHAR *pchRead; CTxtPtr tp(*this); int i; WCHAR xltchar; _TEST_INVARIANT_ // Use tp to read valid blocks of text until all the requested // text is read or until the end of story is reached. while( cch ) { pchRead = tp.GetPch(cchValid); if(!pchRead) // No more text break; cchValid = min(cchValid, cch); if (!fNeutralOverride) { for (i = 0; i < cchValid; i++) { xltchar = pchRead[i]; if(xltchar <= '$') { if(xltchar >= '#') xltchar = '@'; if(xltchar == CELL) xltchar = CR; } pch[i] = xltchar; } } else { for (i = 0; i < cchValid; i++) { pch[i] = OverRideNeutralChar(pchRead[i]); } } pch += cchValid; cch -= cchValid; tp.Move(cchValid); } return cchSave - cch; } #endif /* * CTxtPtr::GetPlainText(cchBuff, pch, cpMost, fTextize) * * @mfunc * Copy up to cchBuff characters or up to cpMost, whichever comes * first, translating lone CRs into CRLFs. Move this text ptr just * past the last character processed. If fTextize, copy up to but * not including the first WCH_EMBEDDING char. If not fTextize, * replace WCH_EMBEDDING by a blank since RichEdit 1.0 does. * * @rdesc * Count of characters copied * * @comm * An important feature is that this text ptr is moved just past the * last char copied. In this way, the caller can conveniently read * out plain text in bufferfuls of up to cch chars, which is useful for * stream I/O. This routine won't copy the final CR even if cpMost * is beyond it. */ LONG CTxtPtr::GetPlainText( LONG cchBuff, //@parm Buffer cch WCHAR * pch, //@parm Buffer to copy text into LONG cpMost, //@parm Largest cp to get BOOL fTextize, //@parm True if break on WCH_EMBEDDING BOOL fUseCRLF) //@parm If TRUE, CR or LF -> CRLF { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPlainText"); LONG cch = cchBuff; // Countdown counter LONG cchValid; // Valid ptr cch LONG cchT; // Temporary cch unsigned ch; // Current char unsigned chPrev = 0; // Previous char const WCHAR *pchRead; // Backing-store ptr _TEST_INVARIANT_ AdjustCRLF(); // Be sure we start on an EOP bdy if(_ped->Get10Mode()) // RE 1.0 delivers EOP chars as fUseCRLF = FALSE; // they appear in backing store LONG cchText = _ped->GetAdjustedTextLength(); cpMost = min(cpMost, cchText); // Don't write final CR if(GetCp() >= cpMost) return 0; while(cch > 0) // While room in buffer { if(!(pchRead = GetPch(cchValid))) // No more chars available break; // so we're out of here cchT = GetCp() + cchValid - cpMost; if(cchT > 0) // Don't overshoot { cchValid -= cchT; if(cchValid <= 0) break; // Nothing left before cpMost } for(cchT = 0; cch > 0 && cchT < cchValid; cchT++, cch--, chPrev = ch) { ch = *pch++ = *pchRead++; // Copy next char (but don't if(IN_RANGE(CELL, ch, CR)) // count it yet) { if(IsASCIIEOP(ch)) // LF, VT, FF, CR { if(!fUseCRLF || ch == FF) continue; if (ch == CR && chPrev == ENDFIELD && cchValid - cchT > 1 && *pchRead == STARTFIELD) { *(pch - 1) = ' '; // New table row follows old: continue; // use only 1 CRLF } Move(cchT); // Move up to CR if(cch < 2) // No room for LF, so don't goto done; // count CR either // Bypass EOP w/o worrying about cchT = AdvanceCRLF(FALSE);// buffer gaps and blocks if(cchT > 2) // Translate CRCRLF to ' ' { // Usually copied count exceeds Assert(cchT == 3); // internal count, but CRCRLFs *(pch - 1) = ' '; // reduce the relative increase: } // NB: error for EM_GETTEXTLENGTHEX else // CRLF or lone CR { // Store LF in both cases for *(pch - 1) = CR; // Be sure it's a CR not a VT, *pch++ = LF; // Windows. No LF for Mac cch--; // One less for target buffer } cch--; // CR (or ' ') copied cchT = 0; // Don't Move() more below break; // Go get new pchRead & cchValid } else if(ch == CELL) // Use TAB for cell end markers *(pch - 1) = TAB; } else if(ch >= STARTFIELD) { // Object lives here if(fTextize && ch == WCH_EMBEDDING) // Break on WCH_EMBEDDING { Move(cchT); // Move this text ptr up to goto done; // WCH_EMBEDDING and return } *(pch - 1) = ' '; // Replace embedding char by ' ' } } Move(cchT); } done: return cchBuff - cch; } /* * CTxtPtr::AdvanceCRLF(fMulticharAdvance) * * @mfunc * Move text pointer by one character, safely advancing * over CRLF, CRCRLF, and UTF-16 combinations * * @rdesc * Number of characters text pointer has been moved by */ LONG CTxtPtr::AdvanceCRLF( BOOL fMulticharAdvance) //@parm If TRUE, advance over combining-mark sequences { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::AdvanceCRLF"); _TEST_INVARIANT_ LONG cp; LONG cpSave = _cp; WCHAR ch = GetChar(); // Char on entry WCHAR ch1 = NextChar(); // Advance to and get next char BOOL fTwoCRs = FALSE; BOOL fCombiningMark = FALSE; if(ch == CR) { if(ch1 == CR && _cp < GetTextLength()) { fTwoCRs = TRUE; // Need at least 3 chars to ch1 = NextChar(); // have CRCRLF at end } if(ch1 == LF) Move(1); // Bypass CRLF else if(fTwoCRs) Move(-1); // Only bypass one CR of two AssertSz(_ped->fUseCRLF() || _cp == cpSave + 1, "CTxtPtr::AdvanceCRLF: EOP isn't a single char"); } // Handle Unicode UTF-16 surrogates if(IN_RANGE(0xD800, ch, 0xDBFF)) // Started on UTF-16 lead word { if (IN_RANGE(0xDC00, ch1, 0xDFFF)) Move(1); // Bypass UTF-16 trail word else AssertSz(FALSE, "CTxtPtr::AdvanceCRLF: illegal Unicode surrogate combo"); } if (fMulticharAdvance) { while(IN_RANGE(0x300, ch1, 0x36F)) // Bypass combining diacritical marks { fCombiningMark = TRUE; cp = _cp; ch1 = NextChar(); if (_cp == cp) break; } } if(IN_RANGE(STARTFIELD, ch, ENDFIELD)) Move(1); // Bypass field type LONG cch = _cp - cpSave; AssertSz(!cch || cch == 1 || fCombiningMark || cch == 2 && (IN_RANGE(0xD800, ch, 0xDBFF) || IN_RANGE(STARTFIELD, ch, ENDFIELD)) || (_ped->fUseCRLF() && GetPrevChar() == LF && (cch == 2 || cch == 3 && fTwoCRs)), "CTxtPtr::AdvanceCRLF(): Illegal multichar"); return cch; // # chars bypassed } /* * CTxtPtr::NextChar() * * @mfunc * Increment this text ptr and return char it points at * * @rdesc * Next char */ WCHAR CTxtPtr::NextChar() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::NextChar"); _TEST_INVARIANT_ Move(1); return GetChar(); } /* * CTxtPtr::PrevChar() * * @mfunc * Decrement this text ptr and return char it points at * * @rdesc * Previous char */ WCHAR CTxtPtr::PrevChar() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::PrevChar"); _TEST_INVARIANT_ return Move(-1) ? GetChar() : 0; } /* * CTxtPtr::BackupCRLF(fMulticharBackup) * * @mfunc * Backup text pointer by one character, safely backing up * over CRLF, CRCRLF, and UTF-16 combinations * * @rdesc * Number of characters text pointer has been moved by * * @future * Backup over Unicode combining marks */ LONG CTxtPtr::BackupCRLF( BOOL fMulticharBackup) //@parm If TRUE, backup over combining-mark sequences { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::BackupCRLF"); _TEST_INVARIANT_ LONG cpSave = _cp; WCHAR ch = PrevChar(); // Moves to and get previous char if(fMulticharBackup) { // Bypass combining diacritical marks while(IN_RANGE(0x300, ch, 0x36F)) ch = PrevChar(); } // Handle Unicode UTF-16 surrogates if(_cp && IN_RANGE(0xDC00, ch, 0xDFFF)) { ch = PrevChar(); if (!IN_RANGE(0xD800, ch, 0xDBFF)) { AssertSz(FALSE, "CTxtPtr::BackupCRLF: illegal Unicode surrogate combo"); ch = NextChar(); } } if(ch == LF) // Try to back up 1 char in any case { if(_cp && PrevChar() != CR) // If LF, does prev char = CR? Move(1); // No, leave tp at LF else if(_cp && !IsAfterTRD(0) &&// At CRLF. If not after TRD PrevChar() != CR) // and prev char != CR, leave { // at CRLF Move(1); } } else if(IN_RANGE(STARTFIELD, GetPrevChar(), ENDFIELD)) Move(-1); // Bypass field type AssertSz( _cp == cpSave || ch == LF && GetChar() == CR || !(ch == LF || fMulticharBackup && (IN_RANGE(0x300, ch, 0x36F) || IN_RANGE(0xDC00, ch, 0xDFFF) && IN_RANGE(0xD800, GetPrevChar(), 0xDBFF)) ), "CTxtPtr::BackupCRLF(): Illegal multichar"); return _cp - cpSave; // - # chars this CTxtPtr moved } /* * CTxtPtr::AdjustCRLF(iDir) * * @mfunc * Adjust the position of this text pointer to the beginning of a CRLF * or CRCRLF combination, if it is in the middle of such a combination. * Move text pointer to the beginning/end (for iDir neg/pos) of a Unicode * surrogate pair or a STARTFIELD/ENDFIELD pair if it is in the middle * of such a pair. * * @rdesc * Number of characters text pointer has been moved by * * @future * Adjust to beginning of sequence containing Unicode combining marks */ LONG CTxtPtr::AdjustCRLF( LONG iDir) //@parm Move forward/backward for iDir = 1/-1, respectively { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::AdjustCpCRLF"); _TEST_INVARIANT_ UINT ch = GetChar(); LONG cpSave = _cp; if(!_cp) // Alignment always correct return 0; // at cp 0 iDir = iDir < 0 ? -1 : 1; // Handle Unicode UTF-16 surrogates if(IN_RANGE(0xDC00, ch, 0xDFFF)) // Landed on UTF-16 trail word { AssertSz(IN_RANGE(0xD800, GetPrevChar(), 0xDBFF), "CTxtPtr::AdjustCRLF: illegal Unicode surrogate combo"); return Move(iDir); // Backup to UTF-16 lead word or } // move forward to next char UINT chPrev = GetPrevChar(); if(IN_RANGE(STARTFIELD, chPrev, ENDFIELD) && chPrev != 0xFFFA) return Move(iDir); if(!IsASCIIEOP(ch) || IsAfterTRD(0)) // Early out return 0; if(ch == LF && chPrev == CR) // Landed on LF preceded by CR: Move(-1); // move to CR for CRCRLF test // Leave as adjust-forward only behavior for RE 1.0 compatibility on // CRCRLF and CRLF if(GetChar() == CR) // Land on a CR of CRLF or { // second CR of CRCRLF? CTxtPtr tp(*this); if(tp.NextChar() == LF) { tp.Move(-2); // First CR of CRCRLF ? if(tp.GetChar() == CR) // Yes or CRLF is at start of Move(-1); // story. Try to back up over } // CR (If at BOS, no effect) } return _cp - cpSave; } /* * CTxtPtr::IsAtEOP() * * @mfunc * Return TRUE iff this text pointer is at an end-of-paragraph mark * * @rdesc * TRUE if at EOP * * @devnote * End of paragraph marks for RichEdit 1.0 and the MLE can be CRLF * and CRCRLF. For RichEdit 2.0, EOPs can also be CR, VT (0xB - Shift- * Enter), and FF (0xC - page break or form feed). */ BOOL CTxtPtr::IsAtEOP() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAtEOP"); _TEST_INVARIANT_ unsigned ch = GetChar(); if(IsASCIIEOP(ch)) // See if LF <= ch <= CR { // Clone tp in case CTxtPtr tp(*this); // AdjustCpCRLF moves return !tp.AdjustCRLF(); // Return TRUE unless in } // middle of CRLF or CRCRLF return (ch | 1) == PS || ch == CELL; // Allow Unicode 0x2028/9 also } /* * CTxtPtr::IsAfterEOP() * * @mfunc * Return TRUE iff this text pointer is just after an end-of-paragraph * mark * * @rdesc * TRUE iff text ptr follows an EOP mark */ BOOL CTxtPtr::IsAfterEOP() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAfterEOP"); _TEST_INVARIANT_ if(IsASCIIEOP(GetChar())) { CTxtPtr tp(*this); // If in middle of CRLF if(tp.AdjustCRLF()) // or CRCRLF, return FALSE return FALSE; } return IsEOP(GetPrevChar()); // After EOP if after Unicode } // PS or LF, VT, FF, CR, CELL /* * CTxtPtr::IsAtTRD(ch) * * @mfunc * Return TRUE iff this text pointer is at a table row delimiter (ch CR). * If ch = 0, then match both start and end delimiters. * * @rdesc * TRUE iff text ptr is at a table row end delimiter */ BOOL CTxtPtr::IsAtTRD( WCHAR ch) //@parm Table Row Delimiter { LONG cchValid; const WCHAR *pch = GetPch(cchValid); if(cchValid < 1) return FALSE; WCHAR chNext; if(cchValid < 2) // In case gap splits TRD { // (may happen after undo) CTxtPtr tp(*this); tp.Move(1); chNext = tp.GetChar(); } else chNext = *(pch + 1); if(chNext != CR) return FALSE; if(ch) { AssertSz(ch == STARTFIELD || ch == ENDFIELD, "CTxtPtr::IsAtTRD: illegal argument"); return *pch == ch; } ch = *pch; return ch == STARTFIELD || ch == ENDFIELD; } /* * CTxtPtr::IsAfterTRD(ch) * * @mfunc * Return TRUE iff this text pointer immediately follows a table row * start/end delimiter specified by ch (ch = STARTFIELD/ENDFIELD * followed by CR). If ch = 0, then match both start and end delims. * * @rdesc * TRUE iff text ptr follows an table row start delimiter */ BOOL CTxtPtr::IsAfterTRD( WCHAR ch) //@parm Table Row Delimiter { LONG cchValid; const WCHAR *pch = GetPchReverse(cchValid); if(cchValid < 1 || *(pch - 1) != CR) return FALSE; WCHAR chPrev; if(cchValid < 2) // In case gap splits TRD { // (may happen after undo) CTxtPtr tp(*this); tp.Move(-1); chPrev = tp.GetPrevChar(); } else chPrev = *(pch - 2); if(ch) { AssertSz(ch == STARTFIELD || ch == ENDFIELD, "CTxtPtr::IsAfterTRD: illegal argument"); return chPrev == ch; } return chPrev == STARTFIELD || chPrev == ENDFIELD; } /* * CTxtPtr::IsAtStartOfCell() * * @mfunc * Return TRUE iff this text pointer immediately follows a table row * start delimiter (STARTFIELD CR) or any cell delimiter (CELL) except * the last one in a row. * * @rdesc * TRUE iff text ptr follows an table row start delimiter */ BOOL CTxtPtr::IsAtStartOfCell() { LONG cchValid; const WCHAR *pch = GetPchReverse(cchValid); return cchValid && *(pch - 1) == CELL && !IsAtTRD(ENDFIELD) || cchValid >= 2 && *(pch - 1) == CR && *(pch - 2) == STARTFIELD; } // Needed for CTxtPtr::ReplaceRange() and InsertRange() #if cchGapInitial < 1 #error "cchGapInitial must be at least one" #endif /* * CTxtPtr::MoveWhile(cch, chFirst, chLast, fInRange) * * @mfunc * Move this text ptr 1) to first char (fInRange ? in range : not in range) * chFirst thru chLast or 2) cch chars, which ever comes first. Return * count of chars left in run on return. E.g., chFirst = 0, chLast = 0x7F * and fInRange = TRUE breaks on first nonASCII char. * * @rdesc * cch left in run on return */ LONG CTxtPtr::MoveWhile( LONG cchRun, //@parm Max cch to check WCHAR chFirst, //@parm First ch in range WCHAR chLast, //@parm Last ch in range BOOL fInRange) //@parm break on non0/0 high byte for TRUE/FALSE { LONG cch; LONG i; const WCHAR *pch; while(cchRun) { pch = GetPch(cch); cch = min(cch, cchRun); for(i = 0; i < cch; i++) { if(IN_RANGE(chFirst, *pch++, chLast) ^ fInRange) { Move(i); // Advance to 1st char with 0/non0 masked return cchRun - i; // value } } cchRun -= cch; Move(cch); // Advance to next txt bdy } return 0; } /* * CTxtPtr::FindWordBreak(action, cpMost) * * @mfunc * Find a word break and move this text pointer to it. * * @rdesc * Offset from cp of the word break */ LONG CTxtPtr::FindWordBreak( INT action, //@parm See TxWordBreakProc header LONG cpMost) //@parm Limiting character position { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindWordBreak"); _TEST_INVARIANT_ const INT breakBufSize = 10; LONG bufferSize; LONG cch; LONG cchBuffer; LONG cchChunk; LONG cchText = GetTextLength(); WCHAR ch = GetChar(); WCHAR pchBreakBuf[breakBufSize]; LONG cpSave = _cp; // For calculating break pt LONG ichBreak; WCHAR * pBuf; WCHAR const * pch; LONG t; // Temp for abs() macro BOOL b10ModeWordBreak = (_ped->Get10Mode() && _ped->_pfnWB); if(action == WB_CLASSIFY || action == WB_ISDELIMITER) return ch ? _ped->TxWordBreakProc(&ch, 0, CbOfCch(1), action, GetCp()) : 0; if(action & 1) // Searching forward { // Easiest to handle EOPs if(action == WB_MOVEWORDRIGHT && IsEOP(ch)) // explicitly (spanning { // a class can go too AdjustCRLF(); // far). Go to end of AdvanceCRLF(); // EOP "word" goto done; } // Calc. max search if((DWORD)cpMost > (DWORD)cchText) // Bounds check: get < 0 cpMost = cchText; // as well as too big cch = cpMost - _cp; while(cch > 0) { // The independent buffer cchBuffer = min(cch, breakBufSize - 1); // avoids gaps in BS cch -= bufferSize = cchBuffer; pBuf = pchBreakBuf; // Fill buffer forward // Grab the first character in reverse for fnWB that require 2 // chars. Note, we play with _ich to get single char fnWB // to ignore this character. pch = GetPchReverse(cchChunk); if ( !cchChunk ) pch = L" "; // Any break char *pBuf++ = *pch; // *pBuf++ = (cchChunk ? *(pch - 1) : L' '); while ( cchBuffer ) // Finish filling { pch = GetPch(cchChunk); if (!cchChunk) { Assert(0); break; } cchChunk = min(cchBuffer, cchChunk); Move(cchChunk); wcsncpy(pBuf, pch, cchChunk); pBuf += cchChunk; cchBuffer -= cchChunk; } ichBreak = _ped->TxWordBreakProc(pchBreakBuf, 1, // Find the break CbOfCch(bufferSize+1), action, GetCp()-bufferSize, GetCp()-bufferSize) - 1; // in 1.0 mode some apps will return 0 implying the current cp position is a valid break point if (ichBreak == -1 && b10ModeWordBreak) ichBreak = 0; // Apparently, some fnWBs return ambiguous results if(ichBreak >= 0 && ichBreak <= bufferSize) { // Ambiguous break pt? // Due to the imprecise nature of the word break proc spec, // we've reached an ambiguous condition where we don't know // if this is really a break, or just the end of the data. // By backing up or going forward by 2, we'll know for sure. // NOTE: we'll always be able to advance or go back by 2 // because we guarantee that when !cch that we have // at least breakBufSize (16) characters in the data stream. if (ichBreak < bufferSize || !cch) { Move( ichBreak - bufferSize ); break; } // Need to recalc break pt to disambiguate t = Move(ichBreak - bufferSize - 2); // abs() is a cch += abs(t); // macro } } } else // REVERSE - code dup based on EliK "streams" concept. { if(!_cp) // Can't go anywhere return 0; if(action == WB_MOVEWORDLEFT) // Easiest to handle EOPs { // here if(IsASCIIEOP(ch) && AdjustCRLF()) // In middle of a CRLF or goto done; // CRCRLF "word" ch = PrevChar(); // Check if previous char if(IsEOP(ch)) // is an EOP char { if(ch == LF) // Backspace to start of AdjustCRLF(); // CRLF and CRCRLF goto done; } Move(1); // Move back to start char } // Calc. max search if((DWORD)cpMost > (DWORD)_cp) // Bounds check (also cpMost = _cp; // handles cpMost < 0) cch = cpMost; while(cch > 0) { // The independent buffer cchBuffer = min(cch, breakBufSize - 1); // avoids gaps in BS cch -= bufferSize = cchBuffer; pBuf = pchBreakBuf + cchBuffer; // Fill from the end. // Grab the first character forward for fnWB that require 2 chars. // Note: we play with _ich to get single char fnWB to ignore this // character. pch = GetPch(cchChunk); if ( !cchChunk ) pch = L" "; // Any break char *pBuf = *pch; while ( cchBuffer > 0 ) // Fill rest of buffer { // before going in reverse pch = GetPchReverse(cchChunk ); if (!cchChunk) { Assert(0); break; } cchChunk = min(cchBuffer, cchChunk); Move(-cchChunk); pch -= cchChunk; pBuf -= cchChunk; wcsncpy(pBuf, pch, cchChunk); cchBuffer -= cchChunk; } // Get break left. ichBreak = _ped->TxWordBreakProc(pchBreakBuf, bufferSize, CbOfCch(bufferSize+1), action, GetCp(), GetCp()+bufferSize); // in 1.0 mode some apps will return 0 implying the current cp position is a valid break point if (ichBreak == 0 && b10ModeWordBreak) ichBreak = bufferSize; // Apparently, some fnWBs return ambiguous results if(ichBreak >= 0 && ichBreak <= bufferSize) { // Ambiguous break pt? // NOTE: when going in reverse, we have >= bufsize - 1 // because there is a break-after char (hyphen). if ( ichBreak > 0 || !cch ) { Move(ichBreak); // Move _cp to break point. break; } cch += Move(2 + ichBreak); // Need to recalc break pt } // to disambiguate. } } done: return _cp - cpSave; // Offset of where to break } /* * CTxtPtr::TranslateRange(cch, CodePage, fSymbolCharSet, publdr) * * @mfunc * Translate a range of text at this text pointer to... * * @rdesc * Count of new characters added (should be same as count replaced) * * @devnote * Moves this text pointer to end of replaced text. * May move text block and formatting arrays. */ LONG CTxtPtr::TranslateRange( LONG cch, //@parm length of range to translate UINT CodePage, //@parm CodePage for MBTWC or WCTMB BOOL fSymbolCharSet, //@parm Target charset IUndoBuilder *publdr) //@parm Undo bldr to receive antievents { CTempWcharBuf twcb; CTempCharBuf tcb; UINT ch; BOOL fAllASCII = TRUE; BOOL fNoCodePage; BOOL fUsedDef; //@parm Out parm to receive whether default char used LONG i; char * pastr = tcb.GetBuf(cch); WCHAR * pstr = twcb.GetBuf(cch); WCHAR * pstrT = pstr; i = GetText(cch, pstr); Assert(i == cch); if(fSymbolCharSet) // Target is SYMBOL_CHARSET { WCTMB(CodePage, 0, pstr, cch, pastr, cch, "\0", &fUsedDef, &fNoCodePage, FALSE); if(fNoCodePage) return cch; for(; i && *pastr; i--) // Break if conversion failed { // (NULL default char used) if(*pstr >= 128) fAllASCII = FALSE; *pstr++ = *(BYTE *)pastr++; } cch -= i; if(fAllASCII) return cch; } else // Target isn't SYMBOL_CHARSET { while(i--) { ch = *pstr++; // Source is SYMBOL_CHARSET, so *pastr++ = (char)ch; // all chars should be < 256 if(ch >= 128) // In any event, truncate to BYTE fAllASCII = FALSE; } if(fAllASCII) // All ASCII, so no conversion needed return cch; MBTWC(CodePage, 0, pastr - cch, cch, pstrT, cch, &fNoCodePage); if(fNoCodePage) return cch; } return ReplaceRange(cch, cch, pstrT, publdr, NULL, NULL); } /* * CTxtPtr::ReplaceRange(cchOld, cchNew, *pch, publdr, paeCF, paePF) * * @mfunc * replace a range of text at this text pointer. * * @rdesc * count of new characters added * * @comm SideEffects: * moves this text pointer to end of replaced text * moves text block array */ LONG CTxtPtr::ReplaceRange( LONG cchOld, //@parm length of range to replace // ( 0 means to end of text) LONG cchNew, //@parm length of replacement text WCHAR const *pch, //@parm replacement text IUndoBuilder *publdr, //@parm if non-NULL, where to put an // anti-event for this action IAntiEvent *paeCF, //@parm char format AE IAntiEvent *paePF ) //@parm paragraph formatting AE { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::ReplaceRange"); _TEST_INVARIANT_ LONG cchAdded = 0; LONG cchInBlock; LONG cchNewInBlock; if(cchOld < 0) cchOld = GetTextLength() - _cp; if(publdr) HandleReplaceRangeUndo( cchOld, cchNew, publdr, paeCF, paePF); // Blocks involving replacement while(cchOld > 0 && cchNew > 0) { CTxtBlk *ptb = GetRun(0); // cchOld should never be nonzero if the text run is empty AssertSz(ptb, "CTxtPtr::Replace() - Pointer to text block is NULL !"); ptb->MoveGap(_ich); cchInBlock = min(cchOld, ptb->_cch - _ich); if(cchInBlock > 0) { cchOld -= cchInBlock; ptb->_cch -= cchInBlock; ((CTxtArray *)_pRuns)->_cchText -= cchInBlock; } cchNewInBlock = CchOfCb(ptb->_cbBlock) - ptb->_cch; // if there's room for a gap, leave one if(cchNewInBlock > cchGapInitial) cchNewInBlock -= cchGapInitial; if(cchNewInBlock > cchNew) cchNewInBlock = cchNew; if(cchNewInBlock > 0) { CopyMemory(ptb->_pch + _ich, pch, CbOfCch(cchNewInBlock)); cchNew -= cchNewInBlock; _cp += cchNewInBlock; _ich += cchNewInBlock; pch += cchNewInBlock; cchAdded += cchNewInBlock; ptb->_cch += cchNewInBlock; ptb->_ibGap += CbOfCch(cchNewInBlock); ((CTxtArray *)_pRuns)->_cchText += cchNewInBlock; } if(_iRun >= Count() - 1 || !cchOld ) break; // Go to next block _iRun++; _ich = 0; } if(cchNew > 0) cchAdded += InsertRange(cchNew, pch); else if(cchOld > 0) DeleteRange(cchOld); return cchAdded; } /* * CTxtPtr::HandleReplaceRangeUndo (cchOld, cchNew, publdr, paeCF, paePF) * * @mfunc * worker function for ReplaceRange. Figures out what will happen in * the replace range call and creates the appropriate anti-events * * @devnote * We first check to see if our replace range data can be merged into * an existing anti-event. If it can, then we just return. * Otherwise, we copy the deleted characters into an allocated buffer * and then create a ReplaceRange anti-event. * * In order to handle ordering problems between formatting and text * anti-events (that is, text needs to exist before formatting can * be applied), we have any formatting anti-events passed to us first. */ void CTxtPtr::HandleReplaceRangeUndo( LONG cchOld, //@parm Count of characters to delete LONG cchNew, //@parm Count of new characters to add IUndoBuilder * publdr, //@parm Undo builder to receive anti-event IAntiEvent * paeCF, //@parm char formatting AE IAntiEvent * paePF ) //@parm paragraph formatting AE { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::HandleReplaceRangeUndo"); _TEST_INVARIANT_ IAntiEvent *pae = publdr->GetTopAntiEvent(); WCHAR * pch = NULL; if(pae) { SimpleReplaceRange sr; sr.cpMin = _cp; sr.cpMax = _cp + cchNew; sr.cchDel = cchOld; if(pae->MergeData(MD_SIMPLE_REPLACERANGE, &sr) == NOERROR) { // If the data was merged successfully, then we do // not need these anti-events if(paeCF) DestroyAEList(paeCF); if(paePF) DestroyAEList(paePF); // we've done everything we need to. return; } } // Allocate a buffer and grab the soon-to-be deleted // text (if necessary) if( cchOld > 0 ) { pch = new WCHAR[cchOld]; if( pch ) GetText(cchOld, pch); else cchOld = 0; } // The new range will exist from our current position plus // cchNew (because everything in cchOld gets deleted) pae = gAEDispenser.CreateReplaceRangeAE(_ped, _cp, _cp + cchNew, cchOld, pch, paeCF, paePF); if( !pae ) delete pch; if( pae ) publdr->AddAntiEvent(pae); } /* * CTxtPtr::InsertRange(cch, pch) * * @mfunc * Insert a range of characters at this text pointer * * @rdesc * Count of characters successfully inserted * * @comm Side Effects: * moves this text pointer to end of inserted text * moves the text block array */ LONG CTxtPtr::InsertRange ( LONG cch, //@parm length of text to insert WCHAR const *pch) //@parm text to insert { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::InsertRange"); _TEST_INVARIANT_ LONG cchSave = cch; LONG cchInBlock; LONG cchFirst; LONG cchLast = 0; LONG ctbNew; CTxtBlk *ptb; // Ensure text array is allocated if(!Count()) { LONG cbSize = -1; // If we don't have any blocks, allocate first block to be big enuf // for the inserted text *only* if it's smaller than the normal block // size. This allows us to be used efficiently as a display engine // for small amounts of text. if(cch < CchOfCb(cbBlockInitial)) cbSize = CbOfCch(cch); if(!((CTxtArray *)_pRuns)->AddBlock(0, cbSize)) { _ped->GetCallMgr()->SetOutOfMemory(); goto done; } } ptb = GetRun(0); cchInBlock = CchOfCb(ptb->_cbBlock) - ptb->_cch; AssertSz(ptb->_cbBlock <= cbBlockMost, "block too big"); // Try resizing without splitting... if(cch > cchInBlock && cch <= cchInBlock + CchOfCb(cbBlockMost - ptb->_cbBlock)) { if( !ptb->ResizeBlock(min(cbBlockMost, CbOfCch(ptb->_cch + cch + cchGapInitial))) ) { _ped->GetCallMgr()->SetOutOfMemory(); goto done; } cchInBlock = CchOfCb(ptb->_cbBlock) - ptb->_cch; } if(cch <= cchInBlock) { // All fits into block without any hassle ptb->MoveGap(_ich); CopyMemory(ptb->_pch + _ich, pch, CbOfCch(cch)); _cp += cch; // *this points at end of _ich += cch; // insertion ptb->_cch += cch; ((CTxtArray *)_pRuns)->_cchText += cch; ptb->_ibGap += CbOfCch(cch); return cch; } // Won't all fit in this block, so figure out best division into blocks TxDivideInsertion(cch, _ich, ptb->_cch - _ich,&cchFirst, &cchLast); // Subtract cchLast up front so return value isn't negative // if SplitBlock() fails cch -= cchLast; // Don't include last block in count for middle blocks // Split block containing insertion point // ***** moves _prgtb ***** // if(!((CTxtArray *)_pRuns)->SplitBlock(_iRun, _ich, cchFirst, cchLast, _ped->IsStreaming())) { _ped->GetCallMgr()->SetOutOfMemory(); goto done; } ptb = GetRun(0); // Recompute ptb after (*_pRuns) moves // Copy into first block (first half of split) if(cchFirst > 0) { AssertSz(ptb->_ibGap == CbOfCch(_ich), "split first gap in wrong place"); AssertSz(cchFirst <= CchOfCb(ptb->_cbBlock) - ptb->_cch, "split first not big enough"); CopyMemory(ptb->_pch + _ich, pch, CbOfCch(cchFirst)); cch -= cchFirst; pch += cchFirst; _ich += cchFirst; ptb->_cch += cchFirst; ((CTxtArray *)_pRuns)->_cchText += cchFirst; ptb->_ibGap += CbOfCch(cchFirst); } // Copy into middle blocks // FUTURE: (jonmat) I increased the size for how large a split block // could be and this seems to increase the performance, we should test // the block size difference on a retail build, however. 5/15/1995 ctbNew = cch / cchBlkInsertmGapI /* cchBlkInitmGapI */; if(ctbNew <= 0 && cch > 0) ctbNew = 1; for(; ctbNew > 0; ctbNew--) { cchInBlock = cch / ctbNew; AssertSz(cchInBlock > 0, "nothing to put into block"); // ***** moves _prgtb ***** // if(!((CTxtArray *)_pRuns)->AddBlock(++_iRun, CbOfCch(cchInBlock + cchGapInitial))) { _ped->GetCallMgr()->SetOutOfMemory(); BindToCp(_cp); //force a rebind; goto done; } // NOTE: next line intentionally advances ptb to next CTxtBlk ptb = GetRun(0); AssertSz(ptb->_ibGap == 0, "New block not added correctly"); CopyMemory(ptb->_pch, pch, CbOfCch(cchInBlock)); cch -= cchInBlock; pch += cchInBlock; _ich = cchInBlock; ptb->_cch = cchInBlock; ((CTxtArray *)_pRuns)->_cchText += cchInBlock; ptb->_ibGap = CbOfCch(cchInBlock); } AssertSz(cch == 0, "Didn't use up all text"); // copy into last block (second half of split) if(cchLast > 0) { AssertSz(_iRun < Count()-1, "no last block"); ptb = Elem(++_iRun); AssertSz(ptb->_ibGap == 0, "split last gap in wrong place"); AssertSz(cchLast <= CchOfCb(ptb->_cbBlock) - ptb->_cch, "split last not big enuf"); CopyMemory(ptb->_pch, pch, CbOfCch(cchLast)); // don't subtract cchLast from cch; it's already been done _ich = cchLast; ptb->_cch += cchLast; ((CTxtArray *)_pRuns)->_cchText += cchLast; ptb->_ibGap = CbOfCch(cchLast); cchLast = 0; // Inserted all requested chars } done: AssertSz(cch + cchLast >= 0, "we should have inserted some characters"); AssertSz(cch + cchLast <= cchSave, "don't insert more than was asked for"); cch = cchSave - cch - cchLast; // # chars successfully inserted _cp += cch; AssertSz (GetTextLength() == ((CTxtArray *)_pRuns)->CalcTextLength(), "CTxtPtr::InsertRange(): _pRuns->_cchText screwed up !"); return cch; } /* * TxDivideInsertion(cch, ichBlock, cchAfter, pcchFirst, pcchLast) * * @func * Find best way to distribute an insertion * * @rdesc * nothing */ static void TxDivideInsertion( LONG cch, //@parm length of text to insert LONG ichBlock, //@parm offset within block to insert text LONG cchAfter, //@parm length of text after insertion in block LONG *pcchFirst, //@parm exit: length of text to put in first block LONG *pcchLast) //@parm exit: length of text to put in last block { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "TxDivideInsertion"); LONG cchFirst = max(0, cchBlkCombmGapI - ichBlock); LONG cchLast = max(0, cchBlkCombmGapI - cchAfter); LONG cchPartial; LONG cchT; // Fill first and last blocks to min block size if possible cchFirst = min(cch, cchFirst); cch -= cchFirst; cchLast = min(cch, cchLast); cch -= cchLast; // How much is left over when we divide up the rest? cchPartial = cch % cchBlkInsertmGapI; if(cchPartial > 0) { // Fit as much as the leftover as possible in the first and last // w/o growing the first and last over cbBlockInitial cchT = max(0, cchBlkInsertmGapI - ichBlock - cchFirst); cchT = min(cchT, cchPartial); cchFirst += cchT; cch -= cchT; cchPartial -= cchT; if(cchPartial > 0) { cchT = max(0, cchBlkInsertmGapI - cchAfter - cchLast); cchT = min(cchT, cchPartial); cchLast += cchT; } } *pcchFirst = cchFirst; *pcchLast = cchLast; } /* * CTxtPtr::DeleteRange(cch) * * @mfunc * Delete cch characters starting at this text pointer * * @rdesc * nothing * * @comm Side Effects: * moves text block array */ void CTxtPtr::DeleteRange( LONG cch) //@parm length of text to delete { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::DeleteRange"); _TEST_INVARIANT_ LONG cchInBlock; LONG ctbDel = 0; // Default no blocks to delete LONG itb; CTxtBlk * ptb = GetRun(0); LONG cOldRuns = Count(); AssertSz(ptb, "CTxtPtr::DeleteRange: want to delete, but no text blocks"); if (cch > GetTextLength() - _cp) // Don't delete beyond end of story cch = GetTextLength() - _cp; ((CTxtArray *)_pRuns)->_cchText -= cch; // remove from first block ptb->MoveGap(_ich); cchInBlock = min(cch, ptb->_cch - _ich); cch -= cchInBlock; ptb->_cch -= cchInBlock; #ifdef DEBUG ((CTxtArray *)_pRuns)->Invariant(); #endif // DEBUG for(itb = ptb->_cch ? _iRun + 1 : _iRun; cch && cch >= Elem(itb)->_cch; ctbDel++, itb++) { // More to go: scan for complete blocks to remove cch -= Elem(itb)->_cch; } if(ctbDel) { // ***** moves (*_pRuns) ***** // itb -= ctbDel; ((CTxtArray *)_pRuns)->RemoveBlocks(itb, ctbDel); } // Remove from last block if(cch > 0) { ptb = Elem(itb); AssertSz(cch < ptb->_cch, "last block too small"); ptb->MoveGap(0); ptb->_cch -= cch; #ifdef DEBUG ((CTxtArray *)_pRuns)->Invariant(); #endif // DEBUG } ((CTxtArray *)_pRuns)->CombineBlocks(_iRun); if(cOldRuns > Count() || _iRun >= Count() || !Elem(_iRun)->_cch) BindToCp(_cp); // Empty block: force tp rebind AssertSz (GetTextLength() == ((CTxtArray *)_pRuns)->CalcTextLength(), "CTxtPtr::DeleteRange(): _pRuns->_cchText screwed up !"); } /* * CTxtPtr::FindText (cpLimit, dwFlags, pch, cch) * * @mfunc * Find the text string

of length

starting at this * text pointer. If found, move this text pointer to the end of the * matched string and return the cp of the first character of the matched * string. If not found, return -1 and don't change this text ptr. * * @rdesc * character position of first match * 0 if no match */ LONG CTxtPtr::FindText ( LONG cpLimit, //@parm Limit of search or 0 for end of text DWORD dwFlags, //@parm FR_MATCHCASE case must match // FR_WHOLEWORD match must be a whole word const WCHAR *pch, //@parm Text to find LONG cch) //@parm Length of text to find { LONG cpFirst, cpLast; CTxtFinder tf; if(tf.FindText(*this, cpLimit, dwFlags, pch, cch, cpFirst, cpLast)) { // Set text ptr to char just after last char in found string SetCp(cpLast + 1); // Return cp of first char in found string return cpFirst; } return -1; } /* * CTxtPtr::FindOrSkipWhiteSpaces (cchMax, dwFlags, pdwResult) * * @mfunc * Find a whitespace or a non-whitespace character (skip all whitespaces). * * @rdesc * Signed number of character this ptr was moved by the operation. * In case of moving backward, the return position was already adjusted forward * so the caller doesnt need to. */ LONG CTxtPtr::FindOrSkipWhiteSpaces ( LONG cchMax, //@parm Max signed count of char to search DWORD dwFlags, //@parm Input flags DWORD* pdwResult) //@parm Flag set if found { const WCHAR* pch; CTxtPtr tp(*this); LONG iDir = cchMax < 0 ? -1 : 1; LONG cpSave = _cp; LONG cchChunk, cch = 0; DWORD dwResult = 0; BOOL (*pfnIsWhite)(unsigned) = IsWhiteSpace; if (dwFlags & FWS_BOUNDTOPARA) pfnIsWhite = IsEOP; if (cchMax < 0) cchMax = -cchMax; while (cchMax > 0 && !dwResult) { pch = iDir > 0 ? tp.GetPch(cch) : tp.GetPchReverse(cch); if (!pch) break; // No text available if (iDir < 0) pch--; // Going backward, point at previous char cch = min(cch, cchMax); for(cchChunk = cch; cch > 0; cch--, pch += iDir) { if ((dwFlags & FWS_SKIP) ^ pfnIsWhite(*pch)) { dwResult++; break; } } cchChunk -= cch; cchMax -= cchChunk; tp.Move(iDir * cchChunk); // advance to next chunk } if (pdwResult) *pdwResult = dwResult; cch = tp.GetCp() - cpSave; if (dwFlags & FWS_MOVE) Move(cch); // Auto advance if requested return cch; } /* * CTxtPtr::FindWhiteSpaceBound (cchMin, cpStart, cpEnd, dwFlags) * * @mfunc * Figure the smallest boundary that covers cchMin and limited by * whitespaces (included CR/LF). This is how it works. * * Text: xxx xxx xxx xxx xxx * cp + cchMin: xxxxx * Boundary: xxxxxxxxxxxxx * * @rdesc * cch of white space characters */ LONG CTxtPtr::FindWhiteSpaceBound ( LONG cchMin, // @parm Minimum char count to be covered LONG& cpStart, // @parm Boundary start LONG& cpEnd, // @parm Boundary end DWORD dwFlags) // @parm Input flags { CTxtPtr tp(*this); LONG cch = tp.GetTextLength(); LONG cp = _cp; Assert (cp + cchMin <= cch); cpStart = cpEnd = cp; cpEnd += max(2, cchMin); // make sure it covers minimum requirement. cpEnd = min(cpEnd, cch); // but not too many dwFlags &= FWS_BOUNDTOPARA; // Figure nearest upper bound // tp.SetCp(cpEnd); cpEnd += tp.FindOrSkipWhiteSpaces(cch - cpEnd, dwFlags | FWS_MOVE); // find a whitespaces cpEnd += tp.FindOrSkipWhiteSpaces(cch - cpEnd, dwFlags | FWS_MOVE | FWS_SKIP); // skip whitespaces if (!(dwFlags & FWS_BOUNDTOPARA)) cpEnd += tp.FindOrSkipWhiteSpaces(cch - cpEnd, dwFlags | FWS_MOVE); // find a whitespace // Figure nearest lower bound // tp.SetCp(cpStart); cpStart += tp.FindOrSkipWhiteSpaces(-cpStart, dwFlags | FWS_MOVE); // find a whitespace cpStart += tp.FindOrSkipWhiteSpaces(-cpStart, dwFlags | FWS_MOVE | FWS_SKIP); // skip whitespaces if (!(dwFlags & FWS_BOUNDTOPARA)) cpStart += tp.FindOrSkipWhiteSpaces(-cpStart, dwFlags | FWS_MOVE); // find a whitespace Assert (cpStart <= cpEnd && cpEnd - cpStart >= cchMin); return cpEnd - cpStart; } /* * CTxtPtr::FindEOP(cchMax, pResults) * * @mfunc * Find EOP mark in a range within cchMax chars from this text pointer * and position *this after it. If no EOP is found and cchMax is not * enough to reach the start or end of the story, leave this text ptr * alone and return 0. If no EOP is found and cchMax is sufficient to * reach the start or end of the story, position this text ptr at the * beginning/end of document (BOD/EOD) for cchMax / 0, * respectively, that is, BOD and EOD are treated as a BOP and an EOP, * respectively. * * @rdesc * Return cch this text ptr is moved. Return in *pResults whether a CELL * or EOP was found. The low byte gives the cch of the EOP if moving * forward (else it's just 1). * * @devnote * This function assumes that this text ptr isn't in middle of a CRLF * or CRCRLF (found only in RichEdit 1.0 compatibility mode). Changing * the for loop could speed up ITextRange MoveUntil/While substantially. */ LONG CTxtPtr::FindEOP ( LONG cchMax, //@parm Max signed count of chars to search LONG *pResults) //@parm Flags saying if EOP and CELL are found { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindEOP"); LONG cch = 0, cchStart; // cch's for scans unsigned ch; // Current char LONG cpSave = _cp; // Save _cp for returning delta LONG iDir = 1; // Default forward motion const WCHAR*pch; // Used to walk text chunks LONG Results = 0; // Nothing found yet CTxtPtr tp(*this); // tp to search text with if(cchMax < 0) // Backward search { iDir = -1; // Backward motion cchMax = -cchMax; // Make max count positive cch = tp.AdjustCRLF(); // If in middle of CRLF or if(!cch && IsAfterEOP()) // CRCRLF, or follow any EOP, cch = tp.BackupCRLF(); // backup before EOP cchMax += cch; } while(cchMax > 0) // Scan until get out of search { // range or match an EOP pch = iDir > 0 // Point pch at contiguous text ? tp.GetPch(cch) // chunk going forward or : tp.GetPchReverse(cch); // going backward if(!pch) // No more text to search break; if(iDir < 0) // Going backward, point at pch--; // previous char cch = min(cch, cchMax); // Limit scan to cchMax chars for(cchStart = cch; cch; cch--) // Scan chunk for EOP { ch = *pch; pch += iDir; if(IN_RANGE(CELL, ch, CR) && ch != TAB) { // Note that EOP was found if(ch == CELL) Results |= FEOP_CELL; Results |= FEOP_EOP; break; } } cchStart -= cch; // Get cch of chars passed by cchMax -= cchStart; // Update cchMax AssertSz(iDir > 0 && GetCp() + cchStart <= GetTextLength() || iDir < 0 && GetCp() - cchStart >= 0, "CTxtPtr::FindEOP: illegal advance"); tp.Move(iDir*cchStart); // Update tp if(Results & FEOP_EOP) // Found an EOP break; } // Continue with next chunk LONG cp = tp.GetCp(); if ((Results & FEOP_EOP) || !cp || // Found EOP or cp is at story cp == GetTextLength()) // beginning or end { SetCp(cp); // Set _cp = tp._cp if(iDir > 0) // Going forward, put ptr just Results = (Results & ~255) | AdvanceCRLF(FALSE);// after EOP // (going back already there) } if(pResults) // Report whether EOP and CELL *pResults = Results; // were found return _cp - cpSave; // Return cch this tp moved } /* * CTxtPtr::FindBOSentence(cch) * * @mfunc * Find beginning of sentence in a range within cch chars from this text * pointer and position *this at it. If no sentence beginning is found, * position *this at beginning of document (BOD) for cch 0 and * leave *this unchanged for cch >= 0. * * @rdesc * Count of chars moved *this moves * * @comm * This routine defines a sentence as a character string that ends with * period followed by at least one whitespace character or the EOD. This * should be replacable so that other kinds of sentence endings can be * used. This routine also matches initials like "M. " as sentences. * We could eliminate those by requiring that sentences don't end with * a word consisting of a single capital character. Similarly, common * abbreviations like "Mr." could be bypassed. To allow a sentence to * end with these "words", two blanks following a period could be used * to mean an unconditional end of sentence. */ LONG CTxtPtr::FindBOSentence ( LONG cch) //@parm max signed count of chars to search { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindBOSentence"); _TEST_INVARIANT_ LONG cchWhite = 0; // No whitespace chars yet LONG cp; LONG cpSave = _cp; // Save value for return BOOL fST; // TRUE if sent terminator LONG iDir = cch > 0 ? 1 : -1; // Move() increment CTxtPtr tp(*this); // tp to search with if(iDir > 0) // If going forward in white while(IsWhiteSpace(tp.GetChar()) && // space, backup to 1st non tp.Move(-1)); // whitespace char (in case // inside sentence ending) while(iDir > 0 || tp.Move(-1)) // Need to back up if finding { // backward for(fST = FALSE; cch; cch -= iDir) // Find sentence terminator { fST = IsSentenceTerminator(tp.GetChar()); if(fST || !tp.Move(iDir)) break; } if(!fST) // If FALSE, we ran out of break; // chars while(IsWhiteSpace(tp.NextChar()) && cch) { // Bypass a span of blank cchWhite++; // chars cch--; } if(cchWhite && (cch >= 0 || tp._cp < cpSave))// Matched new sentence break; // break if(cch < 0) // Searching backward { tp.Move(-cchWhite - 1); // Back up to terminator cch += cchWhite + 1; // Fewer chars to search } cchWhite = 0; // No whitespace yet for next } // iteration cp = tp._cp; if(cchWhite || !cp || cp == GetTextLength())// If sentence found or got SetCp(cp); // start/end of story, set // _cp to tp's return _cp - cpSave; // Tell caller cch moved } /* * CTxtPtr::IsAtBOSentence() * * @mfunc * Return TRUE iff *this is at the beginning of a sentence (BOS) as * defined in the description of the FindBOSentence(cch) routine * * @rdesc * TRUE iff this text ptr is at the beginning of a sentence */ BOOL CTxtPtr::IsAtBOSentence() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAtBOSentence"); if(!_cp) // Beginning of story is an return TRUE; // unconditional beginning // of sentence unsigned ch = GetChar(); if (IsWhiteSpace(ch) || // Proper sentences don't IsSentenceTerminator(ch)) // start with whitespace or { // sentence terminators return FALSE; } LONG cchWhite; CTxtPtr tp(*this); // tp to walk preceding chars for(cchWhite = 0; // Backspace over possible IsWhiteSpace(ch = tp.PrevChar()); // span of whitespace chars cchWhite++) ; return cchWhite && IsSentenceTerminator(ch); } /* * CTxtPtr::IsAtBOWord() * * @mfunc * Return TRUE iff *this is at the beginning of a word, that is, * _cp = 0 or the char at _cp is an EOP, or * FindWordBreak(WB_MOVEWORDRIGHT) would break at _cp. * * @rdesc * TRUE iff this text ptr is at the beginning of a Word */ BOOL CTxtPtr::IsAtBOWord() { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAtBOWord"); if(!_cp || IsAtEOP()) // Story beginning is also return TRUE; // a word beginning CTxtPtr tp(*this); tp.Move(-1); tp.FindWordBreak(WB_MOVEWORDRIGHT); return _cp == tp._cp; } /* * CTxtPtr::FindExact(cchMax, pch) * * @mfunc * Find exact text match for null-terminated string pch in a range * starting at this text pointer. Position this just after matched * string and return cp at start of string, i.e., same as FindText(). * * @rdesc * Return cp of first char in matched string and *this pointing at cp * just following matched string. Return -1 if no match * * @comm * Much faster than FindText, but still a simple search, i.e., could * be improved. * * FindText can delegate to this search for search strings in which * each char can only match itself. */ LONG CTxtPtr::FindExact ( LONG cchMax, //@parm signed max # of chars to search WCHAR * pch) //@parm ptr to null-terminated string to find exactly { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindExact"); _TEST_INVARIANT_ LONG cch, cchStart; LONG cchValid; LONG cchText = GetTextLength(); LONG cpMatch; LONG iDir = 1; // Default for forward search const WCHAR *pc; CTxtPtr tp(*this); // tp to search text with if(!*pch) return -1; // Signal null string not found if(cchMax < 0) // Backward search { iDir = -1; cchMax = -cchMax; // Make count positive } while(cchMax > 0) { if(iDir > 0) { if(tp.GetCp() >= cchText) // Can't go further break; pc = tp.GetPch(cchValid); // Characters we can search w/o cch = cchValid; // encountering block end/gap, } // i.e., stay within text chunk else { if(!tp.GetCp()) // Can't back up any more break; tp.Move(-1); pc = tp.GetPchReverse(cchValid); cch = cchValid + 1; } cch = min(cch, cchMax); if(!cch || !pc) break; // No more text to search for(cchStart = cch; // Find first char cch && *pch != *pc; cch--) // Most execution time is spent { // in this loop going forward or pc += iDir; // backward. x86 rep scasb/scasw } // are faster cchStart -= cch; cchMax -= cchStart; // Update cchMax tp.Move( iDir*(cchStart)); // Update tp if(cch && *pch == *pc) // Matched first char { // See if matches up to null cpMatch = tp.GetCp(); // Save cp of matched first char cch = cchMax; for(pc = pch; // Try to match rest of string cch && *++pc==tp.NextChar();// Note: this match goes forward cch--) ; // for both values of iDir if(!cch) break; // Not enuf chars for string if(!*pc) // Matched null-terminated string { // *pch. Set this tp just after SetCp(tp.GetCp()); // matched string and return cp return cpMatch; // at start } tp.SetCp(cpMatch + iDir); // Move to char just following or } // preceding matched first char } // Up-to-date tp: continue search return -1; // Signal string not found } /* * CTxtPtr::NextCharCount(&cch) * * @mfunc * Helper function for getting next char and decrementing abs(*pcch) * * @rdesc * Next char */ WCHAR CTxtPtr::NextCharCount ( LONG& cch) //@parm count to use and decrement { TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtPtr::NextCharCount"); LONG iDelta = (cch > 0) ? 1 : -1; if(!cch || !Move(iDelta)) return 0; cch -= iDelta; // Count down or up return GetChar(); // Return char at _cp } /* * CTxtPtr::Zombie () * * @mfunc * Turn this object into a zombie by NULLing out its _ped member */ void CTxtPtr::Zombie () { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::Zombie"); _ped = NULL; _cp = 0; SetToNull(); } /* * CTxtIStream::CTxtIStream(tp, iDir) * * @mfunc * Creates from the textptr,

, a character input stream with which * to retrieve characters starting from the cp of the

and proceeding * in the direction indicated by

. */ CTxtIStream::CTxtIStream( const CTxtPtr &tp, int iDir ) : CTxtPtr(tp) { _pfnGetChar = (iDir == DIR_FWD ? &CTxtIStream::GetNextChar : &CTxtIStream::GetPrevChar); _cch = 0; _pch = NULL; } /* * CTxtIStream::GetNextChar() * * @mfunc * Returns the next character in the text stream. * Ensures that at least one valid character exists in _pch and then returns * the next character in _pch. * * @rdesc * WCHAR the next character in the character input stream * 0, if end of text stream */ WCHAR CTxtIStream::GetNextChar() { if(!_cch) FillPchFwd(); if(_cch) { _cch--; return *_pch++; } return 0; } /* * CTxtIStream::GetPrevChar() * * @mfunc * Returns the next character in the text stream, where the direction of the * stream is reverse. * Ensures that at least one valid character exists in _pch and then returns * the next character in _pch. Here, _pch points to the end of a string * containing _cch valid characters. * * @rdesc * WCHAR the next character in the character input stream (travelling backwards * along the string pointed to by _pch) * 0, if end of text stream */ WCHAR CTxtIStream::GetPrevChar() { if(!_cch) FillPchRev(); if(_cch) { _cch--; return *(--_pch); } return 0; } /* * CTxtIStream::FillPchFwd() * * @mfunc * Gets the next run of characters and Moves the cp of this CTxtPtr (base * class) just past the run. * This ensures enough chars in _pch to facilitate the next _cch calls to * GetNextChar(). */ void CTxtIStream::FillPchFwd() { _pch = GetPch(_cch); Move(_cch); } /* * CTxtIStream::FillPchRev() * * @mfunc * Gets the run of characters preceding the one previously pointed to by _pch * and moves the cp of this CTxtPtr (base class) to the beginning of the run. * This ensures enough chars in _pch to facilitate the next _cch calls to * GetPrevChar(). */ void CTxtIStream::FillPchRev() { _pch = GetPchReverse(_cch); Move(-_cch); } /* * CTxtFinder::FindText(tp, cpLimit, dwFlags, pchToFind, cchToFind, &cpFirst, &cpLast) * * @mfunc * Find the text string

of length

starting at * this text pointer. If found,

and

are set to the * cp's of the first and last characters in the matched string (wrt tp). * If not found, return FALSE. * * @rdesc * TRUE string matched. First char at tp.GetCp() + cchOffFirst. * Last char at tp.GetCp() + cchOffLast. * FALSE string not found. */ BOOL CTxtPtr::CTxtFinder::FindText ( const CTxtPtr &tp, LONG cpLimit, //@parm Limit of search or 0 for end of text DWORD dwFlags, //@parm FR_MATCHCASE case must match // FR_WHOLEWORD match must be a whole word const WCHAR *pchToFind, //@parm Text to search for LONG cchToFind, //@parm Count of chars to search for LONG &cpFirst, //@parm If string found, returns cp (wrt tp) of first char LONG &cpLast) //@parm If string found, returns cp (wrt tp) of last char { if(!cchToFind) return FALSE; _fSearchForward = dwFlags & FR_DOWN; // Calculate max number of chars we must search for pchToFind if(_fSearchForward) { const LONG cchText = tp.GetTextLength(); if((DWORD)cpLimit > (DWORD)cchText) // NB: catches cpLimit < 0 too cpLimit = cchText; _cchToSearch = cpLimit - tp.GetCp(); } else { if((DWORD)cpLimit > (DWORD)tp.GetCp()) // NB: catches cpLimit < 0 too cpLimit = 0; _cchToSearch = tp.GetCp() - cpLimit; } if(cchToFind > _cchToSearch) { // Not enough chars in requested direction within which // to find string return FALSE; } const BOOL fWholeWord = dwFlags & FR_WHOLEWORD; _fIgnoreCase = !(dwFlags & FR_MATCHCASE); _fMatchAlefhamza = dwFlags & FR_MATCHALEFHAMZA; _fMatchKashida = dwFlags & FR_MATCHKASHIDA; _fMatchDiac = dwFlags & FR_MATCHDIAC; typedef LONG (CTxtPtr::CTxtFinder::*PFNMATCHSTRING)(WCHAR const *pchToFind, LONG cchToFind, CTxtIStream &tistr); // Setup function pointer appropriate for this type of search CTxtEdit* ped = tp._ped; PFNMATCHSTRING pfnMatchString; #define MATCHARABICSPECIALS (FR_MATCHALEFHAMZA | FR_MATCHKASHIDA | FR_MATCHDIAC) // If match all Arabic special characters exactly, then use simpler // MatchString routine. If ignore any and BiDi text exists, use // MatchStringBiDi. pfnMatchString = (ped->IsBiDi() && (dwFlags & MATCHARABICSPECIALS) != MATCHARABICSPECIALS) ? &CTxtFinder::MatchStringBiDi : &CTxtFinder::MatchString; _iDirection = _fSearchForward ? 1 : -1; BOOL fFound = FALSE; WCHAR chFirst = _fSearchForward ? *pchToFind : pchToFind[cchToFind - 1]; const WCHAR *pchRemaining = _fSearchForward ? &pchToFind[1] : &pchToFind[cchToFind - 2]; LONG cchRead; LONG cchReadToFirst = 0; LONG cchReadToLast; CTxtIStream tistr(tp, _fSearchForward ? CTxtIStream::DIR_FWD : CTxtIStream::DIR_REV); while((cchRead = FindChar(chFirst, tistr)) != -1) { cchReadToFirst += cchRead; if(cchToFind == 1) // Only one char in string - we've matched it! { if (_iDirection > 0) // Searching forward { Assert(tp.GetCp() + cchReadToFirst - 1 >= 0); cpLast = cpFirst = tp.GetCp() + cchReadToFirst - 1; } else // Searching backward { Assert(tp.GetCp() - cchReadToFirst >= 0); cpLast = cpFirst = tp.GetCp() - cchReadToFirst; } fFound = TRUE; } else { // Check if this first char begins a match of string CTxtIStream tistrT(tistr); cchRead = (this->*pfnMatchString)(pchRemaining, cchToFind - 1, tistrT); if(cchRead != -1) { cchReadToLast = cchReadToFirst + cchRead; if (_iDirection > 0) // Searching forward { Assert(tp.GetCp() + cchReadToFirst - 1 >= 0); Assert(tp.GetCp() + cchReadToLast - 1 >= 0); cpFirst = tp.GetCp() + cchReadToFirst - 1; cpLast = tp.GetCp() + cchReadToLast - 1; } else // Searching backward { Assert(tp.GetCp() - cchReadToFirst >= 0); Assert(tp.GetCp() - cchReadToLast >= 0); cpFirst = tp.GetCp() - cchReadToFirst; cpLast = tp.GetCp() - cchReadToLast; } fFound = TRUE; } } if(fFound) { Assert(cpLast < tp.GetTextLength()); if(!fWholeWord) break; // Check if matched string is whole word LONG cchT; LONG cpBefore = (_fSearchForward ? cpFirst : cpLast) - 1; LONG cpAfter = (_fSearchForward ? cpLast : cpFirst) + 1; if((cpBefore < 0 || (ped->TxWordBreakProc(const_cast(CTxtPtr(tp._ped, cpBefore).GetPch(cchT)), 0, sizeof(WCHAR), WB_CLASSIFY, cpBefore) & WBF_CLASS) || ped->_pbrk && ped->_pbrk->CanBreakCp(BRK_WORD, cpBefore + 1)) && (cpAfter >= tp.GetTextLength() || (ped->TxWordBreakProc(const_cast(CTxtPtr(tp._ped, cpAfter).GetPch(cchT)), 0, sizeof(WCHAR), WB_CLASSIFY, cpAfter) & WBF_CLASS) || ped->_pbrk && ped->_pbrk->CanBreakCp(BRK_WORD, cpAfter))) { break; } else fFound = FALSE; } } if(fFound && !_fSearchForward) { // For search backwards, first and last are juxtaposed LONG cpTemp = cpFirst; cpFirst = cpLast; cpLast = cpTemp; } return fFound; } /* * CTxtPtr::CTxtFinder::CharCompMatchCase(ch1, ch2) * * @func Character comparison function sensitive to case according to parms * of current search. * * @rdesc TRUE iff characters are equal */ inline BOOL CTxtPtr::CTxtFinder::CharComp( WCHAR ch1, WCHAR ch2) const { // We compare the characters ourselves if ignore case AND the character isn't a surrogate // return (_fIgnoreCase && !IN_RANGE(0xD800, ch1, 0xDFFF)) ? CharCompIgnoreCase(ch1, ch2) : (ch1 == ch2); } /* * CTxtPtr::CTxtFinder::CharCompIgnoreCase(ch1, ch2) * * @func Character comparison function * * @rdesc TRUE iff characters are equal, ignoring case */ inline BOOL CTxtPtr::CTxtFinder::CharCompIgnoreCase( WCHAR ch1, WCHAR ch2) const { return CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE | NORM_IGNOREWIDTH, &ch1, 1, &ch2, 1) == 2; } /* * CTxtPtr::CTxtFinder::FindChar(ch, tistr) * * @mfunc * Steps through the characters returned from

until a character is * found which matches ch or until _cchToSearch characters have been examined. * If found, the return value indicates the number of chars read from

. * If not found, -1 is returned. * * @rdesc * -1, if char not found * n, if char found. n indicates number of chars read from

* to find the char */ LONG CTxtPtr::CTxtFinder::FindChar( WCHAR ch, CTxtIStream &tistr) { LONG cchSave = _cchToSearch; while(_cchToSearch) { _cchToSearch--; WCHAR chComp = tistr.GetChar(); if(CharComp(ch, chComp) || (!_fMatchAlefhamza && IsAlef(ch) && IsAlef(chComp))) { return cchSave - _cchToSearch; } } return -1; } /* * CTxtPtr::CTxtFinder::MatchString(pchToFind, cchToFind, tistr) * * @mfunc * This method compares the characters returned from

against those * found in pchToFind. If the string is found, the return value indicates * how many characters were read from

to match the string. * If the string is not found, -1 is returned. * * @rdesc * -1, if string not found * n, if string found. n indicates number of chars read from

* to find string */ LONG CTxtPtr::CTxtFinder::MatchString( const WCHAR *pchToFind, LONG cchToFind, CTxtIStream &tistr) { if((DWORD)_cchToSearch < (DWORD)cchToFind) return -1; LONG cchT = cchToFind; while(cchT--) { if(!CharComp(*pchToFind, tistr.GetChar())) return -1; pchToFind += _iDirection; } return cchToFind; } /* * CTxtPtr::CTxtFinder::MatchStringBiDi(pchToFind, cchToFind, tistr) * * @mfunc * This method compares the characters returned from

against those * found in pchToFind. If the string is found, the return value indicates * how many characters were read from

to match the string. * If the string is not found, -1 is returned. * Kashida, diacritics and Alefs are matched/not matched according * to the type of search requested. * * @rdesc * -1, if string not found * n, if string found. n indicates number of chars read from

* to find string */ LONG CTxtPtr::CTxtFinder::MatchStringBiDi( const WCHAR *pchToFind, LONG cchToFind, CTxtIStream &tistr) { if((DWORD)_cchToSearch < (DWORD)cchToFind) return -1; LONG cchRead = 0; while(cchToFind) { WCHAR chComp = tistr.GetChar(); cchRead++; if(!CharComp(*pchToFind, chComp)) { if (!_fMatchKashida && chComp == KASHIDA || !_fMatchDiac && IsBiDiDiacritic(chComp)) { continue; } if (!_fMatchAlefhamza && IsAlef(*pchToFind) && IsAlef(chComp)) { // Skip *pchToFind } else return -1; } pchToFind += _iDirection; cchToFind--; } return cchRead; }