Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2724 lines
73 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module TEXT.C -- CTxtPtr implementation |
  5. *
  6. * Authors: <nl>
  7. * Original RichEdit code: David R. Fulmer <nl>
  8. * Christian Fortini <nl>
  9. * Murray Sargent <nl>
  10. *
  11. * History: <nl>
  12. * 6/25/95 alexgo cleanup and reorganization (use run pointers now)
  13. *
  14. * Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
  15. */
  16. #include "_common.h"
  17. #include "_text.h"
  18. #include "_edit.h"
  19. #include "_antievt.h"
  20. #include "_clasfyc.h"
  21. #include "_txtbrk.h"
  22. ASSERTDATA
  23. //-----------------------------Internal functions--------------------------------
  24. // Text Block management
  25. static void TxDivideInsertion(LONG cch, LONG ichBlock, LONG cchAfter,
  26. LONG *pcchFirst, LONG *pcchLast);
  27. /*
  28. * IsWhiteSpace(ch)
  29. *
  30. * @func
  31. * Used to determine if ch is an EOP char (see IsEOP() for definition),
  32. * TAB or blank. This function is used in identifying sentence start
  33. * and end.
  34. *
  35. * @rdesc
  36. * TRUE if ch is whitespace
  37. */
  38. BOOL IsWhiteSpace(unsigned ch)
  39. {
  40. return ch == ' ' || IN_RANGE(CELL, ch, CR) || (ch | 1) == PS;
  41. }
  42. /*
  43. * IsSentenceTerminator(ch)
  44. *
  45. * @func
  46. * Used to determine if ch is a standard sentence terminator character,
  47. * namely, '?', '.', or '!'
  48. *
  49. * @rdesc
  50. * TRUE if ch is a question mark, period, or exclamation point.
  51. */
  52. BOOL IsSentenceTerminator(unsigned ch)
  53. {
  54. return ch == '?' || ch == '.' || ch == '!'; // Std sentence delimiters
  55. }
  56. // =========================== Invariant stuff ==================================================
  57. #define DEBUG_CLASSNAME CTxtPtr
  58. #include "_invar.h"
  59. // =============================== CTxtPtr ======================================================
  60. #ifdef DEBUG
  61. /*
  62. * CTxtPtr::Invariant
  63. *
  64. * @mfunc invariant check
  65. */
  66. BOOL CTxtPtr::Invariant() const
  67. {
  68. static LONG numTests = 0;
  69. numTests++; // Counts how many times we've been called
  70. // Make sure _cp is within range
  71. Assert(_cp >= 0);
  72. LONG cchValid;
  73. *(LONG_PTR *)&_pchCp = (LONG_PTR)GetPch(cchValid);
  74. CRunPtrBase::Invariant();
  75. if(IsValid())
  76. {
  77. // We use less than or equals here so that we can be an insertion
  78. // point at the *end* of the currently existing text.
  79. Assert(_cp <= GetTextLength());
  80. // Make sure all the blocks are consistent...
  81. Assert(GetTextLength() == ((CTxtArray *)_pRuns)->Invariant());
  82. Assert(_cp == CRunPtrBase::CalculateCp());
  83. }
  84. else
  85. {
  86. Assert(_ich == 0);
  87. }
  88. return TRUE;
  89. }
  90. /*
  91. * CTxtPtr::MoveGapToEndOfBlock ()
  92. *
  93. * @mfunc
  94. * Function to move buffer gap to current block end to aid in debugging
  95. */
  96. void CTxtPtr::MoveGapToEndOfBlock () const
  97. {
  98. CTxtBlk *ptb = GetRun(0);
  99. ptb->MoveGap(ptb->_cch); // Move gaps to end of cur block
  100. }
  101. #endif // DEBUG
  102. /*
  103. * CTxtPtr::CTxtPtr(ped, cp)
  104. *
  105. * @mfunc constructor
  106. */
  107. CTxtPtr::CTxtPtr (
  108. CTxtEdit *ped, //@parm Ptr to CTxtEdit instance
  109. LONG cp) //@parm cp to set the pointer to
  110. {
  111. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::CTxtPtr");
  112. _ped = ped;
  113. _cp = 0;
  114. SetRunArray((CRunArray *) &ped->GetTxtStory()->_TxtArray);
  115. if(IsValid())
  116. _cp = BindToCp(cp);
  117. }
  118. /*
  119. * CTxtPtr::CTxtPtr(&tp)
  120. *
  121. * @mfunc Copy Constructor
  122. */
  123. CTxtPtr::CTxtPtr (
  124. const CTxtPtr &tp)
  125. {
  126. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::CTxtPtr");
  127. // copy all the values over
  128. *this = tp;
  129. }
  130. /*
  131. * CTxtPtr::GetTextLength()
  132. *
  133. * @mfunc
  134. * Return count of characters in the story pointed to by this
  135. * text ptr. Includes the story's final CR in the count
  136. *
  137. * @rdesc
  138. * cch for the story pointed to by this text ptr
  139. *
  140. * @devnote
  141. * This method returns 0 if the text ptr is a zombie, a state
  142. * identified by _ped = NULL.
  143. */
  144. LONG CTxtPtr::GetTextLength() const
  145. {
  146. return _ped ? ((CTxtArray *)_pRuns)->_cchText : 0;
  147. }
  148. /*
  149. * CTxtPtr::GetChar()
  150. *
  151. * @mfunc
  152. * Return character at this text pointer, NULL if text pointer is at
  153. * end of text
  154. *
  155. * @rdesc
  156. * Character at this text ptr
  157. */
  158. TCHAR CTxtPtr::GetChar()
  159. {
  160. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetChar");
  161. LONG cchValid;
  162. const TCHAR *pch = GetPch(cchValid);
  163. return pch ? *pch : 0;
  164. }
  165. /*
  166. * CTxtPtr::GetPrevChar()
  167. *
  168. * @mfunc
  169. * Return character just before this text pointer, NULL if text pointer
  170. * beginning of text
  171. *
  172. * @rdesc
  173. * Character just before this text ptr
  174. */
  175. TCHAR CTxtPtr::GetPrevChar()
  176. {
  177. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPrevChar");
  178. LONG cchValid;
  179. const TCHAR *pch = GetPchReverse(cchValid);
  180. return pch ? *(pch - 1) : 0;
  181. }
  182. /*
  183. * CTxtPtr::GetPch(&cchValid)
  184. *
  185. * @mfunc
  186. * return a character pointer to the text at this text pointer
  187. *
  188. * @rdesc
  189. * a pointer to an array of characters. May be NULL. If non-null,
  190. * then cchValid is guaranteed to be at least 1
  191. */
  192. const TCHAR * CTxtPtr::GetPch(
  193. LONG & cchValid) const //@parm Count of chars for which ptr is valid
  194. {
  195. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPch");
  196. // returned pointer is valid
  197. LONG ich = _ich;
  198. TCHAR * pchBase;
  199. CTxtBlk * ptb = IsValid() ? GetRun(0) : NULL;
  200. cchValid = 0; // Default nothing valid
  201. if(!ptb)
  202. return NULL;
  203. // If we're at the edge of a run, grab the next run or
  204. // stay at the current run.
  205. if(_ich == ptb->_cch)
  206. {
  207. if(_iRun < Count() - 1)
  208. {
  209. // set us to the next text block
  210. ptb = GetRun(1);
  211. ich = 0;
  212. }
  213. else // At very end of text:
  214. return NULL; // just return NULL
  215. }
  216. AssertSz(CbOfCch(ich) <= ptb->_cbBlock,
  217. "CTxtPtr::GetPch(): _ich bigger than block");
  218. pchBase = ptb->_pch + ich;
  219. // Check to see if we need to skip over gap. Recall that
  220. // the game may come anywhere in the middle of a block,
  221. // so if the current ich (note, no underscore, we want
  222. // the active ich) is beyond the gap, then recompute pchBase
  223. // by adding in the size of the block.
  224. //
  225. // cchValid will then be the number of characters left in
  226. // the text block (or _cch - ich)
  227. if(CbOfCch(ich) >= ptb->_ibGap)
  228. {
  229. pchBase += CchOfCb(ptb->_cbBlock) - ptb->_cch;
  230. cchValid = ptb->_cch - ich;
  231. }
  232. else
  233. {
  234. // We're valid until the buffer gap (or see below).
  235. cchValid = CchOfCb(ptb->_ibGap) - ich;
  236. }
  237. AssertSz(cchValid > 0 && GetCp() + cchValid <= GetTextLength(),
  238. "CTxtPtr::GetPch: illegal cchValid");
  239. return pchBase;
  240. }
  241. /*
  242. * CTxtPtr::GetPchReverse(&cchValidReverse, pcchValid)
  243. *
  244. * @mfunc
  245. * return a character pointer to the text at this text pointer
  246. * adjusted so that there are some characters valid *behind* the
  247. * pointer.
  248. *
  249. * @rdesc
  250. * a pointer to an array of characters. May be NULL. If non-null,
  251. * then cchValidReverse is guaranteed to be at least 1
  252. */
  253. const TCHAR * CTxtPtr::GetPchReverse(
  254. LONG & cchValidReverse, //@parm length for reverse
  255. LONG * pcchValid) //@parm length forward
  256. {
  257. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPchReverse");
  258. _TEST_INVARIANT_
  259. LONG cchTemp;
  260. LONG ich = _ich;
  261. TCHAR * pchBase;
  262. CTxtBlk * ptb = IsValid() ? GetRun(0) : NULL;
  263. cchValidReverse = 0; // Default no valid chars in run
  264. if(!ptb)
  265. return NULL;
  266. // If we're at the edge of a run, grab the previous run or
  267. // stay at the current run.
  268. if(!_ich)
  269. {
  270. if(_iRun)
  271. {
  272. ptb = GetRun(-1); // Go to next text block
  273. ich = ptb->_cch;
  274. }
  275. else // At start of text:
  276. return NULL; // just return NULL
  277. }
  278. AssertSz(CbOfCch(ich) <= ptb->_cbBlock,
  279. "CTxtPtr::GetPchReverse(): _ich bigger than block");
  280. pchBase = ptb->_pch + ich;
  281. // Check to see if we need to skip over gap. Recall that
  282. // the game may come anywhere in the middle of a block,
  283. // so if the current ich (note, no underscore, we want
  284. // the active ich) is at least one char past the gap, then recompute
  285. // pchBase by adding the size of the gap (so that it's after
  286. // the gap). This differs from GetPch(), which works forward and
  287. // wants pchBase to include the gap size if ich is at the gap, let
  288. // alone one or more chars past it.
  289. //
  290. // Also figure out the count of valid characters. It's
  291. // either the count of characters from the beginning of the
  292. // text block, i.e. ich, or the count of characters from the
  293. // end of the buffer gap.
  294. cchValidReverse = ich; // Default for ich <= gap offset
  295. cchTemp = ich - CchOfCb(ptb->_ibGap); // Calculate displacement
  296. if(cchTemp > 0) // Positive: pchBase is after gap
  297. {
  298. cchValidReverse = cchTemp;
  299. pchBase += CchOfCb(ptb->_cbBlock) - ptb->_cch; // Add in gap size
  300. }
  301. if(pcchValid) // if client needs forward length
  302. {
  303. if(cchTemp > 0)
  304. cchTemp = ich - ptb->_cch;
  305. else
  306. cchTemp = -cchTemp;
  307. *pcchValid = cchTemp;
  308. }
  309. AssertSz(cchValidReverse > 0 && GetCp() - cchValidReverse >= 0,
  310. "CTxtPtr::GetPchReverse: illegal cchValidReverse");
  311. return pchBase;
  312. }
  313. /*
  314. * CTxtPtr::BindToCp(cp)
  315. *
  316. * @mfunc
  317. * set cached _cp = cp (or nearest valid value)
  318. *
  319. * @rdesc
  320. * _cp actually set
  321. *
  322. * @comm
  323. * This method overrides CRunPtrBase::BindToCp to keep _cp up to date
  324. * correctly.
  325. *
  326. * @devnote
  327. * Do *not* call this method when high performance is needed; use
  328. * AdvanceCp() instead, which advances from 0 or from the cached
  329. * _cp, depending on which is closer.
  330. */
  331. LONG CTxtPtr::BindToCp(
  332. LONG cp) //@parm char position to bind to
  333. {
  334. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::BindToCp");
  335. _cp = CRunPtrBase::BindToCp(cp);
  336. // We want to be able to use this routine to fix up things so we don't
  337. // check invariants on entry.
  338. _TEST_INVARIANT_
  339. return _cp;
  340. }
  341. /*
  342. * CTxtPtr::SetCp(cp)
  343. *
  344. * @mfunc
  345. * 'efficiently' sets cp by advancing from current position or from 0,
  346. * depending on which is closer
  347. *
  348. * @rdesc
  349. * cp actually set to
  350. */
  351. LONG CTxtPtr::SetCp(
  352. LONG cp) //@parm char position to set to
  353. {
  354. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::SetCp");
  355. AdvanceCp(cp - _cp);
  356. return _cp;
  357. }
  358. /*
  359. * CTxtPtr::AdvanceCp(cch)
  360. *
  361. * @mfunc
  362. * Advance cp by cch characters
  363. *
  364. * @rdesc
  365. * Actual number of characters advanced by
  366. *
  367. * @comm
  368. * We override CRunPtrBase::AdvanceCp so that the cached _cp value
  369. * can be correctly updated and so that the advance can be made
  370. * from the cached _cp or from 0, depending on which is closer.
  371. *
  372. * @devnote
  373. * It's also easy to bind at the end of the story. So an improved
  374. * optimization would bind there if 2*(_cp + cch) > _cp + text length.
  375. */
  376. LONG CTxtPtr::AdvanceCp(
  377. LONG cch) // @parm count of chars to advance by
  378. {
  379. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::AdvanceCp");
  380. if(!IsValid()) // No runs yet, so don't go
  381. return 0; // anywhere
  382. const LONG cpSave = _cp; // Save entry _cp
  383. LONG cp = cpSave + cch; // Requested target cp (maybe < 0)
  384. if(cp < cpSave/2) // Closer to 0 than cached cp
  385. {
  386. cp = max(cp, 0); // Don't undershoot
  387. _cp = CRunPtrBase::BindToCp(cp);
  388. }
  389. else
  390. _cp += CRunPtrBase::AdvanceCp(cch); // exist
  391. // NB! the invariant check needs to come at the end; we may be
  392. // moving 'this' text pointer in order to make it valid again
  393. // (for the floating range mechanism).
  394. _TEST_INVARIANT_
  395. return _cp - cpSave; // cch this CTxtPtr moved
  396. }
  397. /*
  398. * CTxtPtr::GetText(cch, pch)
  399. *
  400. * @mfunc
  401. * get a range of cch characters starting at this text ptr. A literal
  402. * copy is made, i.e., with no CR -> CRLF and WCH_EMBEDDING -> ' '
  403. * translations. For these translations, see CTxtPtr::GetPlainText()
  404. *
  405. * @rdesc
  406. * count of characters actually copied
  407. *
  408. * @comm
  409. * Doesn't change this text ptr
  410. */
  411. LONG CTxtPtr::GetText(
  412. LONG cch, //@parm Count of characters to get
  413. TCHAR * pch) //@parm Buffer to copy the text into
  414. {
  415. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetText");
  416. LONG cchSave = cch;
  417. LONG cchValid;
  418. const TCHAR *pchRead;
  419. CTxtPtr tp(*this);
  420. _TEST_INVARIANT_
  421. // Use tp to read valid blocks of text until all the requested
  422. // text is read or until the end of story is reached.
  423. while( cch )
  424. {
  425. pchRead = tp.GetPch(cchValid);
  426. if(!pchRead) // No more text
  427. break;
  428. cchValid = min(cchValid, cch);
  429. CopyMemory(pch, pchRead, cchValid*sizeof(TCHAR));
  430. pch += cchValid;
  431. cch -= cchValid;
  432. tp.AdvanceCp(cchValid);
  433. }
  434. return cchSave - cch;
  435. }
  436. /*
  437. * OverRideNeutralChar(ch)
  438. *
  439. * @mfunc
  440. * Helper for overriding BiDi neutral character classification.
  441. * Option is used in Access Expression Builder.
  442. *
  443. * @rdesc
  444. * Modified character or unmodified input character
  445. */
  446. WCHAR OverRideNeutralChar(WCHAR ch)
  447. {
  448. if (ch < '!' || ch > '}')
  449. return ch;
  450. if (IN_RANGE('!', ch, '>'))
  451. {
  452. // True for !"#&'()*+,-./:;<=>
  453. if ((0x00000001 << (ch - TEXT(' '))) & 0x7C00FFCE)
  454. ch = 'a';
  455. }
  456. if (IN_RANGE('[', ch, '^') || ch == '{' || ch == '}')
  457. {
  458. // True for [/]^{}
  459. ch = 'a';
  460. }
  461. return ch;
  462. }
  463. /*
  464. * CTxtPtr::GetTextForUsp(cch, pch)
  465. *
  466. * @mfunc
  467. * get a range of cch characters starting at this text ptr. A literal
  468. * copy is made, with translation to fool Uniscribe classification
  469. *
  470. * @rdesc
  471. * count of characters actually copied
  472. *
  473. * @comm
  474. * Doesn't change this text ptr
  475. */
  476. LONG CTxtPtr::GetTextForUsp(
  477. LONG cch, //@parm Count of characters to get
  478. TCHAR * pch, //@parm Buffer to copy the text into
  479. BOOL fNeutralOverride) //@parm Neutral override option
  480. {
  481. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetTextForUsp");
  482. LONG cchSave = cch;
  483. LONG cchValid;
  484. const TCHAR *pchRead;
  485. CTxtPtr tp(*this);
  486. int i;
  487. TCHAR xltchar;
  488. _TEST_INVARIANT_
  489. // Use tp to read valid blocks of text until all the requested
  490. // text is read or until the end of story is reached.
  491. while( cch )
  492. {
  493. pchRead = tp.GetPch(cchValid);
  494. if(!pchRead) // No more text
  495. break;
  496. cchValid = min(cchValid, cch);
  497. if (!fNeutralOverride)
  498. {
  499. for (i = 0; i < cchValid; i++)
  500. {
  501. xltchar = pchRead[i];
  502. if (IN_RANGE('#', xltchar, '-'))
  503. {
  504. /* #$+- */
  505. if ((0x1 << (xltchar - '#')) & 0x503)
  506. xltchar = '@';
  507. }
  508. pch[i] = xltchar;
  509. }
  510. }
  511. else
  512. {
  513. for (i = 0; i < cchValid; i++)
  514. {
  515. pch[i] = OverRideNeutralChar(pchRead[i]);
  516. }
  517. }
  518. pch += cchValid;
  519. cch -= cchValid;
  520. tp.AdvanceCp(cchValid);
  521. }
  522. return cchSave - cch;
  523. }
  524. /*
  525. * CTxtPtr::GetPlainText(cchBuff, pch, cpMost, fTextize, fAdjustCRLF)
  526. *
  527. * @mfunc
  528. * Copy up to cchBuff characters or up to cpMost, whichever comes
  529. * first, translating lone CRs into CRLFs. Move this text ptr just
  530. * past the last character processed. If fTextize, copy up to but
  531. * not including the first WCH_EMBEDDING char. If not fTextize,
  532. * replace WCH_EMBEDDING by a blank since RichEdit 1.0 does.
  533. *
  534. * @rdesc
  535. * Count of characters copied
  536. *
  537. * @comm
  538. * An important feature is that this text ptr is moved just past the
  539. * last char copied. In this way, the caller can conveniently read
  540. * out plain text in bufferfuls of up to cch chars, which is useful for
  541. * stream I/O. This routine won't copy the final CR even if cpMost
  542. * is beyond it.
  543. */
  544. LONG CTxtPtr::GetPlainText(
  545. LONG cchBuff, //@parm Buffer cch
  546. TCHAR * pch, //@parm Buffer to copy text into
  547. LONG cpMost, //@parm Largest cp to get
  548. BOOL fTextize, //@parm True if break on WCH_EMBEDDING
  549. BOOL fAdjustCRLF) //@parm TRUE to call AdjustCpCRLF()
  550. {
  551. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::GetPlainText");
  552. LONG cch = cchBuff; // Countdown counter
  553. LONG cchValid; // Valid ptr cch
  554. LONG cchT; // Temporary cch
  555. unsigned ch; // Current char
  556. const TCHAR *pchRead; // Backing-store ptr
  557. _TEST_INVARIANT_
  558. if(fAdjustCRLF)
  559. AdjustCpCRLF(); // Be sure we start on an EOP bdy
  560. LONG cchText = _ped->GetAdjustedTextLength();
  561. cpMost = min(cpMost, cchText); // Don't write final CR
  562. if(GetCp() >= cpMost)
  563. return 0;
  564. while(cch > 0) // While room in buffer
  565. {
  566. if(!(pchRead = GetPch(cchValid))) // No more chars available
  567. break; // so we're out of here
  568. cchT = GetCp() + cchValid - cpMost;
  569. if(cchT > 0) // Don't overshoot
  570. {
  571. cchValid -= cchT;
  572. if(cchValid <= 0)
  573. break; // Nothing left before cpMost
  574. }
  575. for(cchT = 0; cch > 0 && cchT < cchValid; cchT++, cch--)
  576. {
  577. ch = *pch++ = *pchRead++; // Copy next char (but don't
  578. if(IsASCIIEOP(ch) && !_ped->Get10Mode() && ch != FF) // count it yet)
  579. {
  580. AdvanceCp(cchT); // Move up to CR
  581. if(cch < 2) // No room for LF, so don't
  582. goto done; // count CR either
  583. // Bypass EOP w/o worrying about
  584. cchT = AdvanceCpCRLF(); // buffer gaps and blocks
  585. if(cchT > 2) // Translate CRCRLF to ' '
  586. { // Usually copied count exceeds
  587. Assert(cchT == 3); // internal count, but CRCRLFs
  588. *(pch - 1) = ' '; // reduce the relative increase:
  589. } // NB: error for EM_GETTEXTLENGTHEX
  590. else // CRLF or lone CR
  591. { // Store LF in both cases for
  592. *(pch - 1) = CR; // Be sure it's a CR not a VT,
  593. #ifndef MACPORT // FF, or lone LF
  594. *pch++ = LF; // Windows. No LF for Mac
  595. cch--; // One less for target buffer
  596. #endif
  597. }
  598. cch--; // CR (or ' ') copied
  599. cchT = 0; // Don't AdvanceCp() more below
  600. break; // Go get new pchRead & cchValid
  601. }
  602. else if(ch == WCH_EMBEDDING) // Object lives here
  603. {
  604. if(fTextize) // Break on WCH_EMBEDDING
  605. {
  606. AdvanceCp(cchT); // Move this text ptr up to
  607. goto done; // WCH_EMBEDDING and return
  608. }
  609. *(pch - 1) = ' '; // Replace embedding char by ' '
  610. } // since RichEdit 1.0 does
  611. }
  612. AdvanceCp(cchT);
  613. }
  614. done:
  615. return cchBuff - cch;
  616. }
  617. /*
  618. * CTxtPtr::AdvanceCpCRLF()
  619. *
  620. * @mfunc
  621. * Advance text pointer by one character, safely advancing
  622. * over CRLF, CRCRLF, and UTF-16 combinations
  623. *
  624. * @rdesc
  625. * Number of characters text pointer has been moved by
  626. *
  627. */
  628. LONG CTxtPtr::AdvanceCpCRLF(
  629. BOOL fMulticharAdvance)
  630. {
  631. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::AdvanceCpCRLF");
  632. _TEST_INVARIANT_
  633. LONG cp;
  634. LONG cpSave = _cp;
  635. TCHAR ch = GetChar(); // Char on entry
  636. TCHAR ch1 = NextChar(); // Advance to and get next char
  637. BOOL fTwoCRs = FALSE;
  638. BOOL fCombiningMark = FALSE;
  639. if(ch == CR)
  640. {
  641. if(ch1 == CR && _cp < GetTextLength())
  642. {
  643. fTwoCRs = TRUE; // Need at least 3 chars to
  644. ch1 = NextChar(); // have CRCRLF at end
  645. }
  646. if(ch1 == LF)
  647. AdvanceCp(1); // Bypass CRLF
  648. else if(fTwoCRs)
  649. AdvanceCp(-1); // Only bypass one CR of two
  650. AssertSz(_ped->fUseCRLF() || _cp == cpSave + 1,
  651. "CTxtPtr::AdvanceCpCRLF: EOP isn't a single char");
  652. }
  653. // Handle Unicode UTF-16 surrogates
  654. if(IN_RANGE(0xD800, ch, 0xDBFF)) // Started on UTF-16 lead word
  655. {
  656. if (IN_RANGE(0xDC00, ch1, 0xDFFF))
  657. AdvanceCp(1); // Bypass UTF-16 trail word
  658. else
  659. AssertSz(FALSE, "CTxtPtr::AdvanceCpCRLF: illegal Unicode surrogate combo");
  660. }
  661. if (fMulticharAdvance)
  662. {
  663. while(IN_RANGE(0x300, ch1, 0x36F)) // Bypass combining diacritical marks
  664. {
  665. fCombiningMark = TRUE;
  666. cp = _cp;
  667. ch1 = NextChar();
  668. if (_cp == cp)
  669. break;
  670. }
  671. }
  672. LONG cch = _cp - cpSave;
  673. AssertSz(!cch || cch == 1 || fCombiningMark ||
  674. cch == 2 && IN_RANGE(0xD800, ch, 0xDBFF) ||
  675. (_ped->fUseCRLF() && GetPrevChar() == LF &&
  676. (cch == 2 || cch == 3 && fTwoCRs)),
  677. "CTxtPtr::AdvanceCpCRLF(): Illegal multichar");
  678. return cch; // # chars bypassed
  679. }
  680. /*
  681. * CTxtPtr::NextChar()
  682. *
  683. * @mfunc
  684. * Increment this text ptr and return char it points at
  685. *
  686. * @rdesc
  687. * Next char
  688. */
  689. TCHAR CTxtPtr::NextChar()
  690. {
  691. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::NextChar");
  692. _TEST_INVARIANT_
  693. AdvanceCp(1);
  694. return GetChar();
  695. }
  696. /*
  697. * CTxtPtr::PrevChar()
  698. *
  699. * @mfunc
  700. * Decrement this text ptr and return char it points at
  701. *
  702. * @rdesc
  703. * Previous char
  704. */
  705. TCHAR CTxtPtr::PrevChar()
  706. {
  707. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::PrevChar");
  708. _TEST_INVARIANT_
  709. return AdvanceCp(-1) ? GetChar() : 0;
  710. }
  711. /*
  712. * CTxtPtr::BackupCpCRLF(fDiacriticCheck)
  713. *
  714. * @mfunc
  715. * Backup text pointer by one character, safely backing up
  716. * over CRLF, CRCRLF, and UTF-16 combinations
  717. *
  718. * @rdesc
  719. * Number of characters text pointer has been moved by
  720. *
  721. */
  722. LONG CTxtPtr::BackupCpCRLF(
  723. BOOL fMulticharBackup)
  724. {
  725. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::BackupCpCRLF");
  726. _TEST_INVARIANT_
  727. LONG cpSave = _cp;
  728. WCHAR ch = PrevChar(); // Advance to and get previous char
  729. if(fMulticharBackup)
  730. { // Bypass combining diacritical marks
  731. while(IN_RANGE(0x300, ch, 0x36F))
  732. ch = PrevChar();
  733. }
  734. // Handle Unicode UTF-16 surrogates
  735. if(_cp && IN_RANGE(0xDC00, ch, 0xDFFF))
  736. {
  737. ch = PrevChar();
  738. if (!IN_RANGE(0xD800, ch, 0xDBFF))
  739. {
  740. AssertSz(FALSE, "CTxtPtr::BackupCpCRLF: illegal Unicode surrogate combo");
  741. ch = NextChar();
  742. }
  743. }
  744. if (ch == LF && // Try to back up 1 char in any case
  745. (_cp && PrevChar() != CR || // If LF, does prev char = CR?
  746. _cp && PrevChar() != CR)) // If so, does its prev char = CR?
  747. { // (CRLF at BOD checks CR twice; OK)
  748. AdvanceCp(1); // Backed up too far
  749. }
  750. AssertSz( _cp == cpSave ||
  751. ch == LF && GetChar() == CR ||
  752. !(ch == LF || fMulticharBackup &&
  753. (IN_RANGE(0x300, ch, 0x36F) ||
  754. IN_RANGE(0xDC00, ch, 0xDFFF) && IN_RANGE(0xD800, GetPrevChar(), 0xDBFF)) ),
  755. "CTxtPtr::BackupCpCRLF(): Illegal multichar");
  756. return _cp - cpSave; // - # chars this CTxtPtr moved
  757. }
  758. /*
  759. * CTxtPtr::AdjustCpCRLF()
  760. *
  761. * @mfunc
  762. * Adjust the position of this text pointer to the beginning of a CRLF,
  763. * CRCRLF, or UTF-16 combination if it is in the middle of such a
  764. * combination
  765. *
  766. * @rdesc
  767. * Number of characters text pointer has been moved by
  768. *
  769. * @future
  770. * Adjust to beginning of sequence containing Unicode combining marks
  771. */
  772. LONG CTxtPtr::AdjustCpCRLF()
  773. {
  774. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::AdjustCpCRLF");
  775. _TEST_INVARIANT_
  776. LONG cpSave = _cp;
  777. unsigned ch = GetChar();
  778. // Handle Unicode UTF-16 surrogates
  779. if(IN_RANGE(0xDC00, ch, 0xDFFF)) // Landed on UTF-16 trail word
  780. {
  781. AdvanceCp(-1); // Backup to UTF-16 lead word
  782. AssertSz(IN_RANGE(0xD800, GetChar(), 0xDBFF),
  783. "CTxtPtr::AdvanceCpCRLF: illegal Unicode surrogate combo");
  784. return -1;
  785. }
  786. if(!IsASCIIEOP(ch)) // Early out
  787. return 0;
  788. if(ch == LF && cpSave && PrevChar() != CR) // Landed on LF not preceded
  789. { // by CR, so go back to LF
  790. AdvanceCp(1); // Else on CR of CRLF or
  791. } // second CR of CRCRLF
  792. // Note: since we replace all CRCRLFs by blanks on input, the following
  793. // code is probably archaic
  794. if(GetChar() == CR) // Land on a CR of CRLF or
  795. { // second CR of CRCRLF?
  796. CTxtPtr tp(*this);
  797. if(tp.NextChar() == LF)
  798. {
  799. tp.AdvanceCp(-2); // First CR of CRCRLF ?
  800. if(tp.GetChar() == CR) // Yes or CRLF is at start of
  801. AdvanceCp(-1); // story. Try to back up over
  802. } // CR (If at BOS, no effect)
  803. }
  804. return _cp - cpSave;
  805. }
  806. /*
  807. * CTxtPtr::IsAtEOP()
  808. *
  809. * @mfunc
  810. * Return TRUE iff this text pointer is at an end-of-paragraph mark
  811. *
  812. * @rdesc
  813. * TRUE if at EOP
  814. *
  815. * @devnote
  816. * End of paragraph marks for RichEdit 1.0 and the MLE can be CRLF
  817. * and CRCRLF. For RichEdit 2.0, EOPs can also be CR, VT (0xB - Shift-
  818. * Enter), and FF (0xC - page break or form feed).
  819. */
  820. BOOL CTxtPtr::IsAtEOP()
  821. {
  822. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAtEOP");
  823. _TEST_INVARIANT_
  824. unsigned ch = GetChar();
  825. if(IsASCIIEOP(ch)) // See if LF <= ch <= CR
  826. { // Clone tp in case
  827. CTxtPtr tp(*this); // AdjustCpCRLF moves
  828. return !tp.AdjustCpCRLF(); // Return TRUE unless in
  829. } // middle of CRLF or CRCRLF
  830. return (ch | 1) == PS; // Allow Unicode 0x2028/9 also
  831. }
  832. /*
  833. * CTxtPtr::IsAfterEOP()
  834. *
  835. * @mfunc
  836. * Return TRUE iff this text pointer is just after an end-of-paragraph
  837. * mark
  838. *
  839. * @rdesc
  840. * TRUE iff text ptr follows an EOP mark
  841. */
  842. BOOL CTxtPtr::IsAfterEOP()
  843. {
  844. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAfterEOP");
  845. _TEST_INVARIANT_
  846. if(IsASCIIEOP(GetChar()))
  847. {
  848. CTxtPtr tp(*this); // If in middle of CRLF
  849. if(tp.AdjustCpCRLF()) // or CRCRLF, return FALSE
  850. return FALSE;
  851. }
  852. return IsEOP(GetPrevChar()); // After EOP if after Unicode
  853. } // PS or LF, VT, FF, or CR
  854. // Needed for CTxtPtr::ReplaceRange() and InsertRange()
  855. #if cchGapInitial < 1
  856. #error "cchGapInitial must be at least one"
  857. #endif
  858. /*
  859. * CTxtPtr::MoveWhile(cch, chFirst, chLast, fInRange)
  860. *
  861. * @mfunc
  862. * Move this text ptr 1) to first char (fInRange ? in range : not in range)
  863. * chFirst thru chLast or 2) cch chars, which ever comes first. Return
  864. * count of chars left in run on return. E.g., chFirst = 0, chLast = 0x7F
  865. * and fInRange = TRUE breaks on first nonASCII char.
  866. *
  867. * @rdesc
  868. * cch left in run on return
  869. */
  870. LONG CTxtPtr::MoveWhile(
  871. LONG cchRun, //@parm Max cch to check
  872. WCHAR chFirst, //@parm First ch in range
  873. WCHAR chLast, //@parm Last ch in range
  874. BOOL fInRange) //@parm break on non0/0 high byte for TRUE/FALSE
  875. {
  876. LONG cch;
  877. LONG i;
  878. const WCHAR *pch;
  879. while(cchRun)
  880. {
  881. pch = GetPch(cch);
  882. cch = min(cch, cchRun);
  883. for(i = 0; i < cch; i++)
  884. {
  885. if(IN_RANGE(chFirst, *pch++, chLast) ^ fInRange)
  886. {
  887. AdvanceCp(i); // Advance to 1st char with 0/non0 masked
  888. return cchRun - i; // value
  889. }
  890. }
  891. cchRun -= cch;
  892. AdvanceCp(cch); // Advance to next txt bdy
  893. }
  894. return 0;
  895. }
  896. /*
  897. * CTxtPtr::FindWordBreak(action, cpMost)
  898. *
  899. * @mfunc
  900. * Find a word break and move this text pointer to it.
  901. *
  902. * @rdesc
  903. * Offset from cp of the word break
  904. */
  905. LONG CTxtPtr::FindWordBreak(
  906. INT action, //@parm See TxWordBreakProc header
  907. LONG cpMost) //@parm Limiting character position
  908. {
  909. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindWordBreak");
  910. _TEST_INVARIANT_
  911. const INT breakBufSize = 16;
  912. LONG bufferSize;
  913. LONG cch;
  914. LONG cchBuffer;
  915. LONG cchChunk;
  916. LONG cchText = GetTextLength();
  917. TCHAR ch = GetChar();
  918. TCHAR pchBreakBuf[breakBufSize];
  919. LONG cpSave = _cp; // For calculating break pt
  920. LONG ichBreak;
  921. TCHAR * pBuf;
  922. TCHAR const * pch;
  923. LONG t; // Temp for abs() macro
  924. BOOL b10ModeWordBreak = (_ped->Get10Mode() && _ped->_pfnWB);
  925. if(action == WB_CLASSIFY || action == WB_ISDELIMITER)
  926. return ch ? _ped->TxWordBreakProc(&ch, 0, CbOfCch(1), action, GetCp()) : 0;
  927. if(action & 1) // Searching forward
  928. { // Easiest to handle EOPs
  929. if(action == WB_MOVEWORDRIGHT && IsEOP(ch)) // explicitly (spanning
  930. { // a class can go too
  931. AdjustCpCRLF(); // far). Go to end of
  932. AdvanceCpCRLF(); // EOP "word"
  933. goto done;
  934. }
  935. // Calc. max search
  936. if((DWORD)cpMost > (DWORD)cchText) // Bounds check: get < 0
  937. cpMost = cchText; // as well as too big
  938. cch = cpMost - _cp;
  939. while(cch > 0)
  940. { // The independent buffer
  941. cchBuffer = min(cch, breakBufSize - 1); // avoids gaps in BS
  942. cch -= bufferSize = cchBuffer;
  943. pBuf = pchBreakBuf; // Fill buffer forward
  944. // Grab the first character in reverse for fnWB that require 2
  945. // chars. Note, we play with _ich to get single char fnWB
  946. // to ignore this character.
  947. pch = GetPchReverse(cchChunk);
  948. *pBuf++ = (cchChunk ? *(pch - 1) : L' ');
  949. while ( cchBuffer ) // Finish filling
  950. {
  951. pch = GetPch(cchChunk);
  952. if (!cchChunk) { Assert(0); break; }
  953. cchChunk = min(cchBuffer, cchChunk);
  954. AdvanceCp(cchChunk);
  955. wcsncpy(pBuf, pch, cchChunk);
  956. pBuf += cchChunk;
  957. cchBuffer -= cchChunk;
  958. }
  959. ichBreak = _ped->TxWordBreakProc(pchBreakBuf, 1, // Find the break
  960. CbOfCch(bufferSize+1), action, GetCp()-bufferSize, GetCp()-bufferSize) - 1;
  961. // in 1.0 mode some apps will return 0 implying the current cp position is a valid break point
  962. if (ichBreak == -1 && b10ModeWordBreak)
  963. ichBreak = 0;
  964. // Apparently, some fnWBs return ambiguous results
  965. if(ichBreak >= 0 && ichBreak <= bufferSize)
  966. {
  967. // Ambiguous break pt?
  968. // Due to the imprecise nature of the word break proc spec,
  969. // we've reached an ambiguous condition where we don't know
  970. // if this is really a break, or just the end of the data.
  971. // By backing up or going forward by 2, we'll know for sure.
  972. // NOTE: we'll always be able to advance or go back by 2
  973. // because we guarantee that when !cch that we have
  974. // at least breakBufSize (16) characters in the data stream.
  975. if (ichBreak < bufferSize || !cch)
  976. {
  977. AdvanceCp( ichBreak - bufferSize );
  978. break;
  979. }
  980. // Need to recalc break pt to disambiguate
  981. t = AdvanceCp(ichBreak - bufferSize - 2); // abs() is a
  982. cch += abs(t); // macro
  983. }
  984. }
  985. }
  986. else // REVERSE - code dup based on EliK "streams" concept.
  987. {
  988. if(!_cp) // Can't go anywhere
  989. return 0;
  990. if(action == WB_MOVEWORDLEFT) // Easiest to handle EOPs
  991. { // here
  992. if(IsASCIIEOP(ch) && AdjustCpCRLF()) // In middle of a CRLF or
  993. goto done; // CRCRLF "word"
  994. ch = PrevChar(); // Check if previous char
  995. if(IsEOP(ch)) // is an EOP char
  996. {
  997. if(ch == LF) // Backspace to start of
  998. AdjustCpCRLF(); // CRLF and CRCRLF
  999. goto done;
  1000. }
  1001. AdvanceCp(1); // Move back to start char
  1002. }
  1003. // Calc. max search
  1004. if((DWORD)cpMost > (DWORD)_cp) // Bounds check (also
  1005. cpMost = _cp; // handles cpMost < 0)
  1006. cch = cpMost;
  1007. while(cch > 0)
  1008. { // The independent buffer
  1009. cchBuffer = min(cch, breakBufSize - 1); // avoids gaps in BS
  1010. cch -= bufferSize = cchBuffer;
  1011. pBuf = pchBreakBuf + cchBuffer; // Fill from the end.
  1012. // Grab the first character forward for fnWB that require 2 chars.
  1013. // Note: we play with _ich to get single char fnWB to ignore this
  1014. // character.
  1015. pch = GetPch(cchChunk);
  1016. if ( !cchChunk ) pch = L" "; // Any break char
  1017. *pBuf = *pch;
  1018. while ( cchBuffer > 0 ) // Fill rest of buffer
  1019. { // before going in reverse
  1020. pch = GetPchReverse(cchChunk );
  1021. if (!cchChunk) { Assert(0); break; }
  1022. cchChunk = min(cchBuffer, cchChunk);
  1023. AdvanceCp(-cchChunk);
  1024. pch -= cchChunk;
  1025. pBuf -= cchChunk;
  1026. wcsncpy(pBuf, pch, cchChunk);
  1027. cchBuffer -= cchChunk;
  1028. }
  1029. // Get break left.
  1030. ichBreak = _ped->TxWordBreakProc(pchBreakBuf, bufferSize,
  1031. CbOfCch(bufferSize+1), action, GetCp(), GetCp()+bufferSize);
  1032. // in 1.0 mode some apps will return 0 implying the current cp position is a valid break point
  1033. if (ichBreak == 0 && b10ModeWordBreak)
  1034. ichBreak = bufferSize;
  1035. // Apparently, some fnWBs return ambiguous results
  1036. if(ichBreak >= 0 && ichBreak <= bufferSize)
  1037. { // Ambiguous break pt?
  1038. // NOTE: when going in reverse, we have >= bufsize - 1
  1039. // because there is a break-after char (hyphen).
  1040. if ( ichBreak > 0 || !cch )
  1041. {
  1042. AdvanceCp(ichBreak); // Move _cp to break point.
  1043. break;
  1044. }
  1045. cch += AdvanceCp(2 + ichBreak); // Need to recalc break pt
  1046. } // to disambiguate.
  1047. }
  1048. }
  1049. done:
  1050. return _cp - cpSave; // Offset of where to break
  1051. }
  1052. /*
  1053. * CTxtPtr::TranslateRange(cch, CodePage, fSymbolCharSet, publdr)
  1054. *
  1055. * @mfunc
  1056. * Translate a range of text at this text pointer to...
  1057. *
  1058. * @rdesc
  1059. * Count of new characters added (should be same as count replaced)
  1060. *
  1061. * @devnote
  1062. * Moves this text pointer to end of replaced text.
  1063. * May move text block and formatting arrays.
  1064. */
  1065. LONG CTxtPtr::TranslateRange(
  1066. LONG cch, //@parm length of range to translate
  1067. UINT CodePage, //@parm CodePage for MBTWC or WCTMB
  1068. BOOL fSymbolCharSet, //@parm Target charset
  1069. IUndoBuilder *publdr) //@parm Undo bldr to receive antievents
  1070. {
  1071. CTempWcharBuf twcb;
  1072. CTempCharBuf tcb;
  1073. UINT ch;
  1074. BOOL fAllASCII = TRUE;
  1075. BOOL fNoCodePage;
  1076. BOOL fUsedDef; //@parm Out parm to receive whether default char used
  1077. LONG i;
  1078. char * pastr = tcb.GetBuf(cch);
  1079. WCHAR * pstr = twcb.GetBuf(cch);
  1080. WCHAR * pstrT = pstr;
  1081. i = GetText(cch, pstr);
  1082. Assert(i == cch);
  1083. if(fSymbolCharSet) // Target is SYMBOL_CHARSET
  1084. {
  1085. WCTMB(CodePage, 0, pstr, cch, pastr, cch, "\0", &fUsedDef,
  1086. &fNoCodePage, FALSE);
  1087. if(fNoCodePage)
  1088. return cch;
  1089. for(; i && *pastr; i--) // Break if conversion failed
  1090. { // (NULL default char used)
  1091. if(*pstr >= 128)
  1092. fAllASCII = FALSE;
  1093. *pstr++ = *(BYTE *)pastr++;
  1094. }
  1095. cch -= i;
  1096. if(fAllASCII)
  1097. return cch;
  1098. }
  1099. else // Target isn't SYMBOL_CHARSET
  1100. {
  1101. while(i--)
  1102. {
  1103. ch = *pstr++; // Source is SYMBOL_CHARSET, so
  1104. *pastr++ = (char)ch; // all chars should be < 256
  1105. if(ch >= 128) // In any event, truncate to BYTE
  1106. fAllASCII = FALSE;
  1107. }
  1108. if(fAllASCII) // All ASCII, so no conversion needed
  1109. return cch;
  1110. MBTWC(CodePage, 0, pastr - cch, cch, pstrT, cch, &fNoCodePage);
  1111. if(fNoCodePage)
  1112. return cch;
  1113. }
  1114. return ReplaceRange(cch, cch, pstrT, publdr, NULL, NULL);
  1115. }
  1116. /*
  1117. * CTxtPtr::ReplaceRange(cchOld, cchNew, *pch, publdr, paeCF, paePF)
  1118. *
  1119. * @mfunc
  1120. * replace a range of text at this text pointer.
  1121. *
  1122. * @rdesc
  1123. * count of new characters added
  1124. *
  1125. * @comm SideEffects: <nl>
  1126. * moves this text pointer to end of replaced text <nl>
  1127. * moves text block array <nl>
  1128. */
  1129. LONG CTxtPtr::ReplaceRange(
  1130. LONG cchOld, //@parm length of range to replace
  1131. // (<lt> 0 means to end of text)
  1132. LONG cchNew, //@parm length of replacement text
  1133. TCHAR const *pch, //@parm replacement text
  1134. IUndoBuilder *publdr, //@parm if non-NULL, where to put an
  1135. // anti-event for this action
  1136. IAntiEvent *paeCF, //@parm char format AE
  1137. IAntiEvent *paePF ) //@parm paragraph formatting AE
  1138. {
  1139. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::ReplaceRange");
  1140. _TEST_INVARIANT_
  1141. LONG cchAdded = 0;
  1142. LONG cchInBlock;
  1143. LONG cchNewInBlock;
  1144. if(cchOld < 0)
  1145. cchOld = GetTextLength() - _cp;
  1146. if(publdr)
  1147. HandleReplaceRangeUndo( cchOld, cchNew, publdr, paeCF, paePF);
  1148. // Blocks involving replacement
  1149. while(cchOld > 0 && cchNew > 0)
  1150. {
  1151. CTxtBlk *ptb = GetRun(0);
  1152. // cchOld should never be nonzero if the text run is empty
  1153. AssertSz(ptb,
  1154. "CTxtPtr::Replace() - Pointer to text block is NULL !");
  1155. ptb->MoveGap(_ich);
  1156. cchInBlock = min(cchOld, ptb->_cch - _ich);
  1157. if(cchInBlock > 0)
  1158. {
  1159. cchOld -= cchInBlock;
  1160. ptb->_cch -= cchInBlock;
  1161. ((CTxtArray *)_pRuns)->_cchText -= cchInBlock;
  1162. }
  1163. cchNewInBlock = CchOfCb(ptb->_cbBlock) - ptb->_cch;
  1164. // if there's room for a gap, leave one
  1165. if(cchNewInBlock > cchGapInitial)
  1166. cchNewInBlock -= cchGapInitial;
  1167. if(cchNewInBlock > cchNew)
  1168. cchNewInBlock = cchNew;
  1169. if(cchNewInBlock > 0)
  1170. {
  1171. CopyMemory(ptb->_pch + _ich, pch, CbOfCch(cchNewInBlock));
  1172. cchNew -= cchNewInBlock;
  1173. _cp += cchNewInBlock;
  1174. _ich += cchNewInBlock;
  1175. pch += cchNewInBlock;
  1176. cchAdded += cchNewInBlock;
  1177. ptb->_cch += cchNewInBlock;
  1178. ptb->_ibGap += CbOfCch(cchNewInBlock);
  1179. ((CTxtArray *)_pRuns)->_cchText += cchNewInBlock;
  1180. }
  1181. if(_iRun >= Count() - 1 || !cchOld )
  1182. break;
  1183. // Go to next block
  1184. _iRun++;
  1185. _ich = 0;
  1186. }
  1187. if(cchNew > 0)
  1188. cchAdded += InsertRange(cchNew, pch);
  1189. else if(cchOld > 0)
  1190. DeleteRange(cchOld);
  1191. return cchAdded;
  1192. }
  1193. /*
  1194. * CTxtPtr::HandleReplaceRangeUndo (cchOld, cchNew, pch, publdr)
  1195. *
  1196. * @mfunc
  1197. * worker function for ReplaceRange. Figures out what will happen in
  1198. * the replace range call and creates the appropriate anti-events
  1199. *
  1200. * @devnote
  1201. * We first check to see if our replace range data can be merged into
  1202. * an existing anti-event. If it can, then we just return.
  1203. * Otherwise, we copy the deleted characters into an allocated buffer
  1204. * and then create a ReplaceRange anti-event.
  1205. *
  1206. * In order to handle ordering problems between formatting and text
  1207. * anti-events (that is, text needs to exist before formatting can
  1208. * be applied), we have any formatting anti-events passed to us first.
  1209. */
  1210. void CTxtPtr::HandleReplaceRangeUndo(
  1211. LONG cchOld, //@parm Count of characters to delete
  1212. LONG cchNew, //@parm Count of new characters to add
  1213. IUndoBuilder * publdr, //@parm Undo builder to receive anti-event
  1214. IAntiEvent * paeCF, //@parm char formatting AE
  1215. IAntiEvent * paePF ) //@parm paragraph formatting AE
  1216. {
  1217. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::HandleReplaceRangeUndo");
  1218. _TEST_INVARIANT_
  1219. IAntiEvent *pae = publdr->GetTopAntiEvent();
  1220. TCHAR * pch = NULL;
  1221. if(pae)
  1222. {
  1223. SimpleReplaceRange sr;
  1224. sr.cpMin = _cp;
  1225. sr.cpMax = _cp + cchNew;
  1226. sr.cchDel = cchOld;
  1227. if(pae->MergeData(MD_SIMPLE_REPLACERANGE, &sr) == NOERROR)
  1228. {
  1229. // If the data was merged successfully, then we do
  1230. // not need these anti-events
  1231. if(paeCF)
  1232. DestroyAEList(paeCF);
  1233. if(paePF)
  1234. DestroyAEList(paePF);
  1235. // we've done everything we need to.
  1236. return;
  1237. }
  1238. }
  1239. // Allocate a buffer and grab the soon-to-be deleted
  1240. // text (if necessary)
  1241. if( cchOld > 0 )
  1242. {
  1243. pch = new TCHAR[cchOld];
  1244. if( pch )
  1245. GetText(cchOld, pch);
  1246. else
  1247. cchOld = 0;
  1248. }
  1249. // The new range will exist from our current position plus
  1250. // cchNew (because everything in cchOld gets deleted)
  1251. pae = gAEDispenser.CreateReplaceRangeAE(_ped, _cp, _cp + cchNew,
  1252. cchOld, pch, paeCF, paePF);
  1253. if( !pae )
  1254. delete pch;
  1255. if( pae )
  1256. publdr->AddAntiEvent(pae);
  1257. }
  1258. /*
  1259. * CTxtPtr::InsertRange(cch, pch)
  1260. *
  1261. * @mfunc
  1262. * Insert a range of characters at this text pointer
  1263. *
  1264. * @rdesc
  1265. * Count of characters successfully inserted
  1266. *
  1267. * @comm Side Effects: <nl>
  1268. * moves this text pointer to end of inserted text <nl>
  1269. * moves the text block array <nl>
  1270. */
  1271. LONG CTxtPtr::InsertRange (
  1272. LONG cch, //@parm length of text to insert
  1273. TCHAR const *pch) //@parm text to insert
  1274. {
  1275. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::InsertRange");
  1276. _TEST_INVARIANT_
  1277. LONG cchSave = cch;
  1278. LONG cchInBlock;
  1279. LONG cchFirst;
  1280. LONG cchLast = 0;
  1281. LONG ctbNew;
  1282. CTxtBlk *ptb;
  1283. // Ensure text array is allocated
  1284. if(!Count())
  1285. {
  1286. LONG cbSize = -1;
  1287. // If we don't have any blocks, allocate first block to be big enuf
  1288. // for the inserted text *only* if it's smaller than the normal block
  1289. // size. This allows us to be used efficiently as a display engine
  1290. // for small amounts of text.
  1291. if(cch < CchOfCb(cbBlockInitial))
  1292. cbSize = CbOfCch(cch);
  1293. if(!((CTxtArray *)_pRuns)->AddBlock(0, cbSize))
  1294. {
  1295. _ped->GetCallMgr()->SetOutOfMemory();
  1296. goto done;
  1297. }
  1298. }
  1299. ptb = GetRun(0);
  1300. cchInBlock = CchOfCb(ptb->_cbBlock) - ptb->_cch;
  1301. AssertSz(ptb->_cbBlock <= cbBlockMost, "block too big");
  1302. // try and resize without splitting...
  1303. if(cch > cchInBlock &&
  1304. cch <= cchInBlock + CchOfCb(cbBlockMost - ptb->_cbBlock))
  1305. {
  1306. if( !ptb->ResizeBlock(min(cbBlockMost,
  1307. CbOfCch(ptb->_cch + cch + cchGapInitial))) )
  1308. {
  1309. _ped->GetCallMgr()->SetOutOfMemory();
  1310. goto done;
  1311. }
  1312. cchInBlock = CchOfCb(ptb->_cbBlock) - ptb->_cch;
  1313. }
  1314. if(cch <= cchInBlock)
  1315. {
  1316. // all fits into block without any hassle
  1317. ptb->MoveGap(_ich);
  1318. CopyMemory(ptb->_pch + _ich, pch, CbOfCch(cch));
  1319. _cp += cch; // *this points at end of
  1320. _ich += cch; // insertion
  1321. ptb->_cch += cch;
  1322. ((CTxtArray *)_pRuns)->_cchText += cch;
  1323. ptb->_ibGap += CbOfCch(cch);
  1324. return cch;
  1325. }
  1326. // won't all fit in this block
  1327. // figure out best division into blocks
  1328. TxDivideInsertion(cch, _ich, ptb->_cch - _ich,&cchFirst, &cchLast);
  1329. // Subtract cchLast up front so return value isn't negative
  1330. // if SplitBlock() fails
  1331. cch -= cchLast; // don't include last block in count for middle blocks
  1332. // split block containing insertion point
  1333. // ***** moves _prgtb ***** //
  1334. if(!((CTxtArray *)_pRuns)->SplitBlock(_iRun, _ich, cchFirst, cchLast,
  1335. _ped->IsStreaming()))
  1336. {
  1337. _ped->GetCallMgr()->SetOutOfMemory();
  1338. goto done;
  1339. }
  1340. ptb = GetRun(0); // recompute ptb after (*_pRuns) moves
  1341. // copy into first block (first half of split)
  1342. if(cchFirst > 0)
  1343. {
  1344. AssertSz(ptb->_ibGap == CbOfCch(_ich), "split first gap in wrong place");
  1345. AssertSz(cchFirst <= CchOfCb(ptb->_cbBlock) - ptb->_cch, "split first not big enough");
  1346. CopyMemory(ptb->_pch + _ich, pch, CbOfCch(cchFirst));
  1347. cch -= cchFirst;
  1348. pch += cchFirst;
  1349. _ich += cchFirst;
  1350. ptb->_cch += cchFirst;
  1351. ((CTxtArray *)_pRuns)->_cchText += cchFirst;
  1352. ptb->_ibGap += CbOfCch(cchFirst);
  1353. }
  1354. // copy into middle blocks
  1355. // FUTURE: (jonmat) I increased the size for how large a split block
  1356. // could be and this seems to increase the performance, we should test
  1357. // the block size difference on a retail build, however. 5/15/1995
  1358. ctbNew = cch / cchBlkInsertmGapI /* cchBlkInitmGapI */;
  1359. if(ctbNew <= 0 && cch > 0)
  1360. ctbNew = 1;
  1361. for(; ctbNew > 0; ctbNew--)
  1362. {
  1363. cchInBlock = cch / ctbNew;
  1364. AssertSz(cchInBlock > 0, "nothing to put into block");
  1365. // ***** moves _prgtb ***** //
  1366. if(!((CTxtArray *)_pRuns)->AddBlock(++_iRun,
  1367. CbOfCch(cchInBlock + cchGapInitial)))
  1368. {
  1369. _ped->GetCallMgr()->SetOutOfMemory();
  1370. BindToCp(_cp); //force a rebind;
  1371. goto done;
  1372. }
  1373. // NOTE: next line intentionally advances ptb to next CTxtBlk
  1374. ptb = GetRun(0);
  1375. AssertSz(ptb->_ibGap == 0, "New block not added correctly");
  1376. CopyMemory(ptb->_pch, pch, CbOfCch(cchInBlock));
  1377. cch -= cchInBlock;
  1378. pch += cchInBlock;
  1379. _ich = cchInBlock;
  1380. ptb->_cch = cchInBlock;
  1381. ((CTxtArray *)_pRuns)->_cchText += cchInBlock;
  1382. ptb->_ibGap = CbOfCch(cchInBlock);
  1383. }
  1384. AssertSz(cch == 0, "Didn't use up all text");
  1385. // copy into last block (second half of split)
  1386. if(cchLast > 0)
  1387. {
  1388. AssertSz(_iRun < Count()-1, "no last block");
  1389. ptb = Elem(++_iRun);
  1390. AssertSz(ptb->_ibGap == 0, "split last gap in wrong place");
  1391. AssertSz(cchLast <= CchOfCb(ptb->_cbBlock) - ptb->_cch,
  1392. "split last not big enuf");
  1393. CopyMemory(ptb->_pch, pch, CbOfCch(cchLast));
  1394. // don't subtract cchLast from cch; it's already been done
  1395. _ich = cchLast;
  1396. ptb->_cch += cchLast;
  1397. ((CTxtArray *)_pRuns)->_cchText += cchLast;
  1398. ptb->_ibGap = CbOfCch(cchLast);
  1399. cchLast = 0; // Inserted all requested chars
  1400. }
  1401. done:
  1402. AssertSz(cch + cchLast >= 0, "we should have inserted some characters");
  1403. AssertSz(cch + cchLast <= cchSave, "don't insert more than was asked for");
  1404. cch = cchSave - cch - cchLast; // # chars successfully inserted
  1405. _cp += cch;
  1406. AssertSz (GetTextLength() ==
  1407. ((CTxtArray *)_pRuns)->CalcTextLength(),
  1408. "CTxtPtr::InsertRange(): _pRuns->_cchText messed up !");
  1409. return cch;
  1410. }
  1411. /*
  1412. * TxDivideInsertion(cch, ichBlock, cchAfter, pcchFirst, pcchLast)
  1413. *
  1414. * @func
  1415. * Find best way to distribute an insertion
  1416. *
  1417. * @rdesc
  1418. * nothing
  1419. */
  1420. static void TxDivideInsertion(
  1421. LONG cch, //@parm length of text to insert
  1422. LONG ichBlock, //@parm offset within block to insert text
  1423. LONG cchAfter, //@parm length of text after insertion in block
  1424. LONG *pcchFirst, //@parm exit: length of text to put in first block
  1425. LONG *pcchLast) //@parm exit: length of text to put in last block
  1426. {
  1427. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "TxDivideInsertion");
  1428. LONG cchFirst = max(0, cchBlkCombmGapI - ichBlock);
  1429. LONG cchLast = max(0, cchBlkCombmGapI - cchAfter);
  1430. LONG cchPartial;
  1431. LONG cchT;
  1432. // Fill first and last blocks to min block size if possible
  1433. cchFirst = min(cch, cchFirst);
  1434. cch -= cchFirst;
  1435. cchLast = min(cch, cchLast);
  1436. cch -= cchLast;
  1437. // How much is left over when we divide up the rest?
  1438. cchPartial = cch % cchBlkInsertmGapI;
  1439. if(cchPartial > 0)
  1440. {
  1441. // Fit as much as the leftover as possible in the first and last
  1442. // w/o growing the first and last over cbBlockInitial
  1443. cchT = max(0, cchBlkInsertmGapI - ichBlock - cchFirst);
  1444. cchT = min(cchT, cchPartial);
  1445. cchFirst += cchT;
  1446. cch -= cchT;
  1447. cchPartial -= cchT;
  1448. if(cchPartial > 0)
  1449. {
  1450. cchT = max(0, cchBlkInsertmGapI - cchAfter - cchLast);
  1451. cchT = min(cchT, cchPartial);
  1452. cchLast += cchT;
  1453. }
  1454. }
  1455. *pcchFirst = cchFirst;
  1456. *pcchLast = cchLast;
  1457. }
  1458. /*
  1459. * CTxtPtr::DeleteRange(cch)
  1460. *
  1461. * @mfunc
  1462. * Delete cch characters starting at this text pointer
  1463. *
  1464. * @rdesc
  1465. * nothing
  1466. *
  1467. * @comm Side Effects: <nl>
  1468. * moves text block array
  1469. */
  1470. void CTxtPtr::DeleteRange(
  1471. LONG cch) //@parm length of text to delete
  1472. {
  1473. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::DeleteRange");
  1474. _TEST_INVARIANT_
  1475. LONG cchInBlock;
  1476. LONG ctbDel = 0; // Default no blocks to delete
  1477. LONG itb;
  1478. CTxtBlk * ptb = GetRun(0);
  1479. AssertSz(ptb,
  1480. "CTxtPtr::DeleteRange: want to delete, but no text blocks");
  1481. if (cch > GetTextLength() - _cp) // Don't delete beyond end of story
  1482. cch = GetTextLength() - _cp;
  1483. ((CTxtArray *)_pRuns)->_cchText -= cch;
  1484. // remove from first block
  1485. ptb->MoveGap(_ich);
  1486. cchInBlock = min(cch, ptb->_cch - _ich);
  1487. cch -= cchInBlock;
  1488. ptb->_cch -= cchInBlock;
  1489. #ifdef DEBUG
  1490. ((CTxtArray *)_pRuns)->Invariant();
  1491. #endif // DEBUG
  1492. for(itb = ptb->_cch ? _iRun + 1 : _iRun;
  1493. cch && cch >= Elem(itb)->_cch; ctbDel++, itb++)
  1494. {
  1495. // More to go: scan for complete blocks to remove
  1496. cch -= Elem(itb)->_cch;
  1497. }
  1498. if(ctbDel)
  1499. {
  1500. // ***** moves (*_pRuns) ***** //
  1501. itb -= ctbDel;
  1502. ((CTxtArray *)_pRuns)->RemoveBlocks(itb, ctbDel);
  1503. }
  1504. // Remove from last block
  1505. if(cch > 0)
  1506. {
  1507. ptb = Elem(itb);
  1508. AssertSz(cch < ptb->_cch, "last block too small");
  1509. ptb->MoveGap(0);
  1510. ptb->_cch -= cch;
  1511. #ifdef DEBUG
  1512. ((CTxtArray *)_pRuns)->Invariant();
  1513. #endif // DEBUG
  1514. }
  1515. ((CTxtArray *)_pRuns)->CombineBlocks(_iRun);
  1516. if(_iRun >= Count() || !Elem(_iRun)->_cch)
  1517. BindToCp(_cp); // Empty block: force tp rebind
  1518. AssertSz (GetTextLength() ==
  1519. ((CTxtArray *)_pRuns)->CalcTextLength(),
  1520. "CTxtPtr::DeleteRange(): _pRuns->_cchText messed up !");
  1521. }
  1522. /*
  1523. * CTxtPtr::FindText (cpMost, dwFlags, pch, cch)
  1524. *
  1525. * @mfunc
  1526. * Find the text string <p pch> of length <p cch> starting at this
  1527. * text pointer. If found, move this text pointer to the end of the
  1528. * matched string and return the cp of the first character of the matched
  1529. * string. If not found, return -1 and don't change this text ptr.
  1530. *
  1531. * @rdesc
  1532. * character position of first match
  1533. * <lt> 0 if no match
  1534. */
  1535. LONG CTxtPtr::FindText (
  1536. LONG cpLimit, //@parm Limit of search or <lt> 0 for end of text
  1537. DWORD dwFlags, //@parm FR_MATCHCASE case must match <nl>
  1538. // FR_WHOLEWORD match must be a whole word
  1539. const TCHAR *pch, //@parm Text to find
  1540. LONG cch) //@parm Length of text to find
  1541. {
  1542. LONG cpFirst, cpLast;
  1543. CTxtFinder tf;
  1544. if(tf.FindText(*this, cpLimit, dwFlags, pch, cch, cpFirst, cpLast))
  1545. {
  1546. // Set text ptr to char just after last char in found string
  1547. SetCp(cpLast + 1);
  1548. // Return cp of first char in found string
  1549. return cpFirst;
  1550. }
  1551. return -1;
  1552. }
  1553. /*
  1554. * CTxtPtr::FindOrSkipWhiteSpaces (cchMax, pdwResult, fAdvance, fSkip)
  1555. *
  1556. * @mfunc
  1557. * Find a whitespace or a non-whitespace character (skip all whitespaces).
  1558. *
  1559. * @rdesc
  1560. * Signed number of character this ptr was moved by the operation.
  1561. * In case of moving backward, the return position was already adjusted forward
  1562. * so the caller doesnt need to.
  1563. */
  1564. LONG CTxtPtr::FindOrSkipWhiteSpaces (
  1565. LONG cchMax, //@parm Max signed count of char to search
  1566. DWORD dwFlags, //@parm Input flags
  1567. DWORD* pdwResult) //@parm Flag set if found
  1568. {
  1569. const TCHAR* pch;
  1570. CTxtPtr tp(*this);
  1571. LONG iDir = cchMax < 0 ? -1 : 1;
  1572. LONG cpSave = _cp;
  1573. LONG cchChunk, cch = 0;
  1574. DWORD dwResult = 0;
  1575. BOOL (*pfnIsWhite)(unsigned) = IsWhiteSpace;
  1576. if (dwFlags & FWS_BOUNDTOPARA)
  1577. pfnIsWhite = IsEOP;
  1578. if (cchMax < 0)
  1579. cchMax = -cchMax;
  1580. while (cchMax > 0 && !dwResult)
  1581. {
  1582. pch = iDir > 0 ? tp.GetPch(cch) : tp.GetPchReverse(cch);
  1583. if (!pch)
  1584. break; // No text available
  1585. if (iDir < 0)
  1586. pch--; // Going backward, point at previous char
  1587. cch = min(cch, cchMax);
  1588. for(cchChunk = cch; cch > 0; cch--, pch += iDir)
  1589. {
  1590. if ((dwFlags & FWS_SKIP) ^ pfnIsWhite(*pch))
  1591. {
  1592. dwResult++;
  1593. break;
  1594. }
  1595. }
  1596. cchChunk -= cch;
  1597. cchMax -= cchChunk;
  1598. tp.AdvanceCp(iDir * cchChunk); // advance to next chunk
  1599. }
  1600. if (pdwResult)
  1601. *pdwResult = dwResult;
  1602. cch = tp.GetCp() - cpSave;
  1603. if (dwFlags & FWS_ADVANCECP)
  1604. AdvanceCp(cch); // Auto advance if requested
  1605. return cch;
  1606. }
  1607. /*
  1608. * CTxtPtr::FindWhiteSpaceBound (cchMin, cpStart, cpEnd)
  1609. *
  1610. * @mfunc
  1611. * Figure the smallest boundary that covers cchMin and limited by
  1612. * whitespaces (included CR/LF). This is how it works.
  1613. *
  1614. * Text: xxx xxx xxx xxx xxx
  1615. * cp + cchMin: xxxxx
  1616. * Boundary: xxxxxxxxxxxxx
  1617. *
  1618. */
  1619. LONG CTxtPtr::FindWhiteSpaceBound (
  1620. LONG cchMin, // @parm Minimum char count to be covered
  1621. LONG& cpStart, // @parm Boundary start
  1622. LONG& cpEnd, // @parm Boundary end
  1623. DWORD dwFlags) // @parm Input flags
  1624. {
  1625. CTxtPtr tp(*this);
  1626. LONG cch = tp.GetTextLength();
  1627. LONG cp = _cp;
  1628. Assert (cp + cchMin <= cch);
  1629. cpStart = cpEnd = cp;
  1630. cpEnd += max(2, cchMin); // make sure it covers minimum requirement.
  1631. cpEnd = min(cpEnd, cch); // but not too many
  1632. dwFlags &= FWS_BOUNDTOPARA;
  1633. // Figure nearest upper bound
  1634. //
  1635. tp.SetCp(cpEnd);
  1636. cpEnd += tp.FindOrSkipWhiteSpaces(cch - cpEnd, dwFlags | FWS_ADVANCECP); // find a whitespaces
  1637. cpEnd += tp.FindOrSkipWhiteSpaces(cch - cpEnd, dwFlags | FWS_ADVANCECP | FWS_SKIP); // skip whitespaces
  1638. if (!(dwFlags & FWS_BOUNDTOPARA))
  1639. cpEnd += tp.FindOrSkipWhiteSpaces(cch - cpEnd, dwFlags | FWS_ADVANCECP); // find a whitespace
  1640. // Figure nearest lower bound
  1641. //
  1642. tp.SetCp(cpStart);
  1643. cpStart += tp.FindOrSkipWhiteSpaces(-cpStart, dwFlags | FWS_ADVANCECP); // find a whitespace
  1644. cpStart += tp.FindOrSkipWhiteSpaces(-cpStart, dwFlags | FWS_ADVANCECP | FWS_SKIP); // skip whitespaces
  1645. if (!(dwFlags & FWS_BOUNDTOPARA))
  1646. cpStart += tp.FindOrSkipWhiteSpaces(-cpStart, dwFlags | FWS_ADVANCECP); // find a whitespace
  1647. Assert (cpStart <= cpEnd && cpEnd - cpStart >= cchMin);
  1648. return cpEnd - cpStart;
  1649. }
  1650. /*
  1651. * CTxtPtr::FindEOP(cchMax, pResults)
  1652. *
  1653. * @mfunc
  1654. * Find EOP mark in a range within cchMax chars from this text pointer
  1655. * and position *this after it. If no EOP is found and cchMax is not
  1656. * enough to reach the start or end of the story, leave this text ptr
  1657. * alone and return 0. If no EOP is found and cchMax is sufficient to
  1658. * reach the start or end of the story, position this text ptr at the
  1659. * beginning/end of document (BOD/EOD) for cchMax <lt>/<gt> 0,
  1660. * respectively, that is, BOD and EOD are treated as a BOP and an EOP,
  1661. * respectively.
  1662. *
  1663. * @rdesc
  1664. * Return cch this text ptr is moved. Return in *pResults whether a CELL
  1665. * or EOP was found. The low byte gives the cch of the EOP if moving
  1666. * forward (else it's just 1).
  1667. *
  1668. * @devnote
  1669. * This function assumes that this text ptr isn't in middle of a CRLF
  1670. * or CRCRLF (found only in RichEdit 1.0 compatibility mode). Changing
  1671. * the for loop could speed up ITextRange MoveUntil/While substantially.
  1672. */
  1673. LONG CTxtPtr::FindEOP (
  1674. LONG cchMax, //@parm Max signed count of chars to search
  1675. LONG *pResults) //@parm Flags saying if EOP and CELL are found
  1676. {
  1677. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindEOP");
  1678. LONG cch = 0, cchStart; // cch's for scans
  1679. unsigned ch; // Current char
  1680. LONG cpSave = _cp; // Save _cp for returning delta
  1681. LONG iDir = 1; // Default forward motion
  1682. const TCHAR*pch; // Used to walk text chunks
  1683. LONG Results = 0; // Nothing found yet
  1684. CTxtPtr tp(*this); // tp to search text with
  1685. if(cchMax < 0) // Backward search
  1686. {
  1687. iDir = -1; // Backward motion
  1688. cchMax = -cchMax; // Make max count positive
  1689. cch = tp.AdjustCpCRLF(); // If in middle of CRLF or
  1690. if(!cch && IsAfterEOP()) // CRCRLF, or follow any EOP,
  1691. cch = tp.BackupCpCRLF(); // backup before EOP
  1692. cchMax += cch;
  1693. }
  1694. while(cchMax > 0) // Scan until get out of search
  1695. { // range or match an EOP
  1696. pch = iDir > 0 // Point pch at contiguous text
  1697. ? tp.GetPch(cch) // chunk going forward or
  1698. : tp.GetPchReverse(cch); // going backward
  1699. if(!pch) // No more text to search
  1700. break;
  1701. if(iDir < 0) // Going backward, point at
  1702. pch--; // previous char
  1703. cch = min(cch, cchMax); // Limit scan to cchMax chars
  1704. for(cchStart = cch; cch; cch--) // Scan chunk for EOP
  1705. {
  1706. ch = *pch;
  1707. if(ch == CELL)
  1708. Results |= FEOP_CELL; // Note that CELL was found
  1709. if(IsEOP(ch)) // Note that EOP was found
  1710. { // Going forward, cch may = 0
  1711. Results |= FEOP_EOP;
  1712. break;
  1713. }
  1714. pch += iDir;
  1715. }
  1716. cchStart -= cch; // Get cch of chars passed by
  1717. cchMax -= cchStart; // Update cchMax
  1718. AssertSz(iDir > 0 && GetCp() + cchStart <= GetTextLength() ||
  1719. iDir < 0 && GetCp() - cchStart >= 0,
  1720. "CTxtPtr::FindEOP: illegal advance");
  1721. tp.AdvanceCp(iDir*cchStart); // Update tp
  1722. if(Results & FEOP_EOP) // Found an EOP
  1723. break;
  1724. } // Continue with next chunk
  1725. LONG cp = tp.GetCp();
  1726. if ((Results & FEOP_EOP) || !cp || // Found EOP or cp is at story
  1727. cp == GetTextLength()) // beginning or end
  1728. {
  1729. SetCp(cp); // Set _cp = tp._cp
  1730. if(iDir > 0) // Going forward, put ptr just
  1731. Results = (Results & ~255) | AdvanceCpCRLF();// after EOP (going
  1732. } // back, is already after EOP)
  1733. if(pResults) // Report whether EOP and CELL
  1734. *pResults = Results; // were found
  1735. return _cp - cpSave; // Return cch this tp moved
  1736. }
  1737. /*
  1738. * CTxtPtr::FindBOSentence(cch)
  1739. *
  1740. * @mfunc
  1741. * Find beginning of sentence in a range within cch chars from this text
  1742. * pointer and position *this at it. If no sentence beginning is found,
  1743. * position *this at beginning of document (BOD) for cch <lt> 0 and
  1744. * leave *this unchanged for cch >= 0.
  1745. *
  1746. * @rdesc
  1747. * Count of chars moved *this moves
  1748. *
  1749. * @comm
  1750. * This routine defines a sentence as a character string that ends with
  1751. * period followed by at least one whitespace character or the EOD. This
  1752. * should be replacable so that other kinds of sentence endings can be
  1753. * used. This routine also matches initials like "M. " as sentences.
  1754. * We could eliminate those by requiring that sentences don't end with
  1755. * a word consisting of a single capital character. Similarly, common
  1756. * abbreviations like "Mr." could be bypassed. To allow a sentence to
  1757. * end with these "words", two blanks following a period could be used
  1758. * to mean an unconditional end of sentence.
  1759. */
  1760. LONG CTxtPtr::FindBOSentence (
  1761. LONG cch) //@parm max signed count of chars to search
  1762. {
  1763. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindBOSentence");
  1764. _TEST_INVARIANT_
  1765. LONG cchWhite = 0; // No whitespace chars yet
  1766. LONG cp;
  1767. LONG cpSave = _cp; // Save value for return
  1768. BOOL fST; // TRUE if sent terminator
  1769. LONG iDir = cch > 0 ? 1 : -1; // AdvanceCp() increment
  1770. CTxtPtr tp(*this); // tp to search with
  1771. if(iDir > 0) // If going forward in white
  1772. while(IsWhiteSpace(tp.GetChar()) && // space, backup to 1st non
  1773. tp.AdvanceCp(-1)) ; // whitespace char (in case
  1774. // inside sentence ending)
  1775. while(iDir > 0 || tp.AdvanceCp(-1)) // Need to back up if finding
  1776. { // backward
  1777. for(fST = FALSE; cch; cch -= iDir) // Find sentence terminator
  1778. {
  1779. fST = IsSentenceTerminator(tp.GetChar());
  1780. if(fST || !tp.AdvanceCp(iDir))
  1781. break;
  1782. }
  1783. if(!fST) // If FALSE, we ran out of
  1784. break; // chars
  1785. while(IsWhiteSpace(tp.NextChar()) && cch)
  1786. { // Bypass a span of blank
  1787. cchWhite++; // chars
  1788. cch--;
  1789. }
  1790. if(cchWhite && (cch >= 0 || tp._cp < cpSave))// Matched new sentence
  1791. break; // break
  1792. if(cch < 0) // Searching backward
  1793. {
  1794. tp.AdvanceCp(-cchWhite - 1); // Back up to terminator
  1795. cch += cchWhite + 1; // Fewer chars to search
  1796. }
  1797. cchWhite = 0; // No whitespace yet for next
  1798. } // iteration
  1799. cp = tp._cp;
  1800. if(cchWhite || !cp || cp == GetTextLength())// If sentence found or got
  1801. SetCp(cp); // start/end of story, set
  1802. // _cp to tp's
  1803. return _cp - cpSave; // Tell caller cch moved
  1804. }
  1805. /*
  1806. * CTxtPtr::IsAtBOSentence()
  1807. *
  1808. * @mfunc
  1809. * Return TRUE iff *this is at the beginning of a sentence (BOS) as
  1810. * defined in the description of the FindBOSentence(cch) routine
  1811. *
  1812. * @rdesc
  1813. * TRUE iff this text ptr is at the beginning of a sentence
  1814. */
  1815. BOOL CTxtPtr::IsAtBOSentence()
  1816. {
  1817. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAtBOSentence");
  1818. if(!_cp) // Beginning of story is an
  1819. return TRUE; // unconditional beginning
  1820. // of sentence
  1821. unsigned ch = GetChar();
  1822. if (IsWhiteSpace(ch) || // Proper sentences don't
  1823. IsSentenceTerminator(ch)) // start with whitespace or
  1824. { // sentence terminators
  1825. return FALSE;
  1826. }
  1827. LONG cchWhite;
  1828. CTxtPtr tp(*this); // tp to walk preceding chars
  1829. for(cchWhite = 0; // Backspace over possible
  1830. IsWhiteSpace(ch = tp.PrevChar()); // span of whitespace chars
  1831. cchWhite++) ;
  1832. return cchWhite && IsSentenceTerminator(ch);
  1833. }
  1834. /*
  1835. * CTxtPtr::IsAtBOWord()
  1836. *
  1837. * @mfunc
  1838. * Return TRUE iff *this is at the beginning of a word, that is,
  1839. * _cp = 0 or the char at _cp is an EOP, or
  1840. * FindWordBreak(WB_MOVEWORDRIGHT) would break at _cp.
  1841. *
  1842. * @rdesc
  1843. * TRUE iff this text ptr is at the beginning of a Word
  1844. */
  1845. BOOL CTxtPtr::IsAtBOWord()
  1846. {
  1847. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::IsAtBOWord");
  1848. if(!_cp || IsAtEOP()) // Story beginning is also
  1849. return TRUE; // a word beginning
  1850. CTxtPtr tp(*this);
  1851. tp.AdvanceCp(-1);
  1852. tp.FindWordBreak(WB_MOVEWORDRIGHT);
  1853. return _cp == tp._cp;
  1854. }
  1855. /*
  1856. * CTxtPtr::FindExact(cchMax, pch)
  1857. *
  1858. * @mfunc
  1859. * Find exact text match for null-terminated string pch in a range
  1860. * starting at this text pointer. Position this just after matched
  1861. * string and return cp at start of string, i.e., same as FindText().
  1862. *
  1863. * @rdesc
  1864. * Return cp of first char in matched string and *this pointing at cp
  1865. * just following matched string. Return -1 if no match
  1866. *
  1867. * @comm
  1868. * Much faster than FindText, but still a simple search, i.e., could
  1869. * be improved.
  1870. *
  1871. * FindText can delegate to this search for search strings in which
  1872. * each char can only match itself.
  1873. */
  1874. LONG CTxtPtr::FindExact (
  1875. LONG cchMax, //@parm signed max # of chars to search
  1876. TCHAR * pch) //@parm ptr to null-terminated string to find exactly
  1877. {
  1878. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::FindExact");
  1879. _TEST_INVARIANT_
  1880. LONG cch, cchStart;
  1881. LONG cchValid;
  1882. LONG cchText = GetTextLength();
  1883. LONG cpMatch;
  1884. LONG iDir = 1; // Default for forward search
  1885. const TCHAR *pc;
  1886. CTxtPtr tp(*this); // tp to search text with
  1887. if(!*pch)
  1888. return -1; // Signal null string not found
  1889. if(cchMax < 0) // Backward search
  1890. {
  1891. iDir = -1;
  1892. cchMax = -cchMax; // Make count positive
  1893. }
  1894. while(cchMax > 0)
  1895. {
  1896. if(iDir > 0)
  1897. {
  1898. if(tp.GetCp() >= cchText) // Can't go further
  1899. break;
  1900. pc = tp.GetPch(cchValid); // Characters we can search w/o
  1901. cch = cchValid; // encountering block end/gap,
  1902. } // i.e., stay within text chunk
  1903. else
  1904. {
  1905. if(!tp.GetCp()) // Can't back up any more
  1906. break;
  1907. tp.AdvanceCp(-1);
  1908. pc = tp.GetPchReverse(cchValid);
  1909. cch = cchValid + 1;
  1910. }
  1911. cch = min(cch, cchMax);
  1912. if(!cch || !pc)
  1913. break; // No more text to search
  1914. for(cchStart = cch; // Find first char
  1915. cch && *pch != *pc; cch--) // Most execution time is spent
  1916. { // in this loop going forward or
  1917. pc += iDir; // backward. x86 rep scasb/scasw
  1918. } // are faster
  1919. cchStart -= cch;
  1920. cchMax -= cchStart; // Update cchMax
  1921. tp.AdvanceCp( iDir*(cchStart)); // Update tp
  1922. if(cch && *pch == *pc) // Matched first char
  1923. { // See if matches up to null
  1924. cpMatch = tp.GetCp(); // Save cp of matched first char
  1925. cch = cchMax;
  1926. for(pc = pch; // Try to match rest of string
  1927. cch && *++pc==tp.NextChar();// Note: this match goes forward
  1928. cch--) ; // for both values of iDir
  1929. if(!cch)
  1930. break; // Not enuf chars for string
  1931. if(!*pc) // Matched null-terminated string
  1932. { // *pch. Set this tp just after
  1933. SetCp(tp.GetCp()); // matched string and return cp
  1934. return cpMatch; // at start
  1935. }
  1936. tp.SetCp(cpMatch + iDir); // Move to char just following or
  1937. } // preceding matched first char
  1938. } // Up-to-date tp: continue search
  1939. return -1; // Signal string not found
  1940. }
  1941. /*
  1942. * CTxtPtr::NextCharCount(&cch)
  1943. *
  1944. * @mfunc
  1945. * Helper function for getting next char and decrementing abs(*pcch)
  1946. *
  1947. * @rdesc
  1948. * Next char
  1949. */
  1950. TCHAR CTxtPtr::NextCharCount (
  1951. LONG& cch) //@parm count to use and decrement
  1952. {
  1953. TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtPtr::NextCharCount");
  1954. LONG iDelta = (cch > 0) ? 1 : -1;
  1955. if(!cch || !AdvanceCp(iDelta))
  1956. return 0;
  1957. cch -= iDelta; // Count down or up
  1958. return GetChar(); // Return char at _cp
  1959. }
  1960. /*
  1961. * CTxtPtr::Zombie ()
  1962. *
  1963. * @mfunc
  1964. * Turn this object into a zombie by NULLing out its _ped member
  1965. */
  1966. void CTxtPtr::Zombie ()
  1967. {
  1968. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtPtr::Zombie");
  1969. _ped = NULL;
  1970. _cp = 0;
  1971. SetToNull();
  1972. }
  1973. /*
  1974. * CTxtIStream::CTxtIStream(tp, iDir)
  1975. *
  1976. * @mfunc
  1977. * Creates from the textptr, <p tp>, a character input stream with which
  1978. * to retrieve characters starting from the cp of the <p tp> and proceeding
  1979. * in the direction indicated by <p iDir>.
  1980. */
  1981. CTxtIStream::CTxtIStream(
  1982. const CTxtPtr &tp,
  1983. int iDir
  1984. ) : CTxtPtr(tp)
  1985. {
  1986. _pfnGetChar = (iDir == DIR_FWD ?
  1987. &CTxtIStream::GetNextChar : &CTxtIStream::GetPrevChar);
  1988. _cch = 0;
  1989. _pch = NULL;
  1990. }
  1991. /*
  1992. * CTxtIStream::GetNextChar()
  1993. *
  1994. * @mfunc
  1995. * Returns the next character in the text stream.
  1996. * Ensures that at least one valid character exists in _pch and then returns
  1997. * the next character in _pch.
  1998. *
  1999. * @rdesc
  2000. * TCHAR the next character in the character input stream
  2001. * 0, if end of text stream
  2002. */
  2003. TCHAR CTxtIStream::GetNextChar()
  2004. {
  2005. if(!_cch)
  2006. FillPchFwd();
  2007. if(_cch)
  2008. {
  2009. _cch--;
  2010. return *_pch++;
  2011. }
  2012. return 0;
  2013. }
  2014. /*
  2015. * CTxtIStream::GetPrevChar()
  2016. *
  2017. * @mfunc
  2018. * Returns the next character in the text stream, where the direction of the
  2019. * stream is reverse.
  2020. * Ensures that at least one valid character exists in _pch and then returns
  2021. * the next character in _pch. Here, _pch points to the end of a string
  2022. * containing _cch valid characters.
  2023. *
  2024. * @rdesc
  2025. * TCHAR the next character in the character input stream (travelling backwards
  2026. * along the string pointed to by _pch)
  2027. * 0, if end of text stream
  2028. */
  2029. TCHAR CTxtIStream::GetPrevChar()
  2030. {
  2031. if(!_cch)
  2032. FillPchRev();
  2033. if(_cch)
  2034. {
  2035. _cch--;
  2036. return *(--_pch);
  2037. }
  2038. return 0;
  2039. }
  2040. /*
  2041. * CTxtIStream::FillPchFwd()
  2042. *
  2043. * @mfunc
  2044. * Gets the next run of characters and advances the cp of this CTxtPtr (base
  2045. * class) just past the run.
  2046. * This ensures enough chars in _pch to facilitate the next _cch calls to
  2047. * GetNextChar().
  2048. */
  2049. void CTxtIStream::FillPchFwd()
  2050. {
  2051. _pch = GetPch(_cch);
  2052. AdvanceCp(_cch);
  2053. }
  2054. /*
  2055. * CTxtIStream::FillPchRev()
  2056. *
  2057. * @mfunc
  2058. * Gets the run of characters preceding the one previously pointed to by _pch
  2059. * and advances the cp of this CTxtPtr (base class) to the beginning of the run.
  2060. * This ensures enough chars in _pch to facilitate the next _cch calls to
  2061. * GetPrevChar().
  2062. */
  2063. void CTxtIStream::FillPchRev()
  2064. {
  2065. _pch = GetPchReverse(_cch);
  2066. AdvanceCp(-_cch);
  2067. }
  2068. /*
  2069. * CTxtFinder::FindText (cpMost, dwFlags, pch, cchToFind)
  2070. *
  2071. * @mfunc
  2072. * Find the text string <p pch> of length <p cchToFind> starting at this
  2073. * text pointer. If found, <p cpFirst> and <p cpLast> are set to
  2074. * the cp's of the first and last characters in the matched string (wrt tp).
  2075. * If not found, return FALSE.
  2076. *
  2077. * @rdesc
  2078. * TRUE string matched. First char at tp.GetCp() + cchOffFirst.
  2079. * Last char at tp.GetCp() + cchOffLast.
  2080. * FALSE string not found.
  2081. */
  2082. BOOL CTxtPtr::CTxtFinder::FindText (
  2083. const CTxtPtr &tp,
  2084. LONG cpLimit, //@parm Limit of search or <lt> 0 for end of text
  2085. DWORD dwFlags, //@parm FR_MATCHCASE case must match <nl>
  2086. // FR_WHOLEWORD match must be a whole word
  2087. const TCHAR *pchToFind, //@parm Text to search for
  2088. LONG cchToFind, //@parm Count of chars to search for
  2089. LONG &cpFirst, //@parm If string found, returns cp (wrt tp) of first char
  2090. LONG &cpLast) //@parm If string found, returns cp (wrt tp) of last char
  2091. {
  2092. if(!cchToFind)
  2093. return FALSE;
  2094. _fSearchForward = dwFlags & FR_DOWN;
  2095. // Calculate max number of chars we must search for pchToFind
  2096. if(_fSearchForward)
  2097. {
  2098. const LONG cchText = tp.GetTextLength();
  2099. if((DWORD)cpLimit > (DWORD)cchText) // NB: catches cpLimit < 0 too
  2100. cpLimit = cchText;
  2101. _cchToSearch = cpLimit - tp.GetCp();
  2102. }
  2103. else
  2104. {
  2105. if((DWORD)cpLimit > (DWORD)tp.GetCp()) // NB: catches cpLimit < 0 too
  2106. cpLimit = 0;
  2107. _cchToSearch = tp.GetCp() - cpLimit;
  2108. }
  2109. if(cchToFind > _cchToSearch)
  2110. {
  2111. // Not enough chars in requested direction within which
  2112. // to find string
  2113. return FALSE;
  2114. }
  2115. const BOOL fWholeWord = dwFlags & FR_WHOLEWORD;
  2116. _fIgnoreCase = !(dwFlags & FR_MATCHCASE);
  2117. _fMatchAlefhamza = dwFlags & FR_MATCHALEFHAMZA;
  2118. _fMatchKashida = dwFlags & FR_MATCHKASHIDA;
  2119. _fMatchDiac = dwFlags & FR_MATCHDIAC;
  2120. typedef LONG (CTxtPtr::CTxtFinder::*PFNMATCHSTRING)(TCHAR const *pchToFind,
  2121. LONG cchToFind,
  2122. CTxtIStream &tistr);
  2123. // Setup function pointer appropriate for this type of search
  2124. CTxtEdit* ped = tp._ped;
  2125. PFNMATCHSTRING pfnMatchString;
  2126. #define MATCHARABICSPECIALS (FR_MATCHALEFHAMZA | FR_MATCHKASHIDA | FR_MATCHDIAC)
  2127. // If match all Arabic special characters exactly, then use simpler
  2128. // MatchString routine. If ignore any and BiDi text exists, use
  2129. // MatchStringBiDi.
  2130. pfnMatchString = (ped->IsBiDi() &&
  2131. (dwFlags & MATCHARABICSPECIALS) != MATCHARABICSPECIALS)
  2132. ? &CTxtFinder::MatchStringBiDi
  2133. : &CTxtFinder::MatchString;
  2134. _iDirection = _fSearchForward ? 1 : -1;
  2135. BOOL fFound = FALSE;
  2136. TCHAR chFirst = _fSearchForward ? *pchToFind : pchToFind[cchToFind - 1];
  2137. const TCHAR *pchRemaining = _fSearchForward ?
  2138. &pchToFind[1] : &pchToFind[cchToFind - 2];
  2139. LONG cchRead;
  2140. LONG cchReadToFirst = 0;
  2141. LONG cchReadToLast;
  2142. CTxtIStream tistr(tp,
  2143. _fSearchForward ? CTxtIStream::DIR_FWD : CTxtIStream::DIR_REV);
  2144. while((cchRead = FindChar(chFirst, tistr)) != -1)
  2145. {
  2146. cchReadToFirst += cchRead;
  2147. if(cchToFind == 1) // Only one char in string - we've matched it!
  2148. {
  2149. if (_iDirection > 0) // Searching forward
  2150. {
  2151. Assert(tp.GetCp() + cchReadToFirst - 1 >= 0);
  2152. cpLast = cpFirst = tp.GetCp() + cchReadToFirst - 1;
  2153. }
  2154. else // Searching backward
  2155. {
  2156. Assert(tp.GetCp() - cchReadToFirst >= 0);
  2157. cpLast = cpFirst = tp.GetCp() - cchReadToFirst;
  2158. }
  2159. fFound = TRUE;
  2160. }
  2161. else
  2162. {
  2163. // Check if this first char begins a match of string
  2164. CTxtIStream tistrT(tistr);
  2165. cchRead = (this->*pfnMatchString)(pchRemaining, cchToFind - 1, tistrT);
  2166. if(cchRead != -1)
  2167. {
  2168. cchReadToLast = cchReadToFirst + cchRead;
  2169. if (_iDirection > 0) // Searching forward
  2170. {
  2171. Assert(tp.GetCp() + cchReadToFirst - 1 >= 0);
  2172. Assert(tp.GetCp() + cchReadToLast - 1 >= 0);
  2173. cpFirst = tp.GetCp() + cchReadToFirst - 1;
  2174. cpLast = tp.GetCp() + cchReadToLast - 1;
  2175. }
  2176. else // Searching backward
  2177. {
  2178. Assert(tp.GetCp() - cchReadToFirst >= 0);
  2179. Assert(tp.GetCp() - cchReadToLast >= 0);
  2180. cpFirst = tp.GetCp() - cchReadToFirst;
  2181. cpLast = tp.GetCp() - cchReadToLast;
  2182. }
  2183. fFound = TRUE;
  2184. }
  2185. }
  2186. if(fFound)
  2187. {
  2188. Assert(cpLast < tp.GetTextLength());
  2189. if(!fWholeWord)
  2190. break;
  2191. // Check if matched string is whole word
  2192. LONG cchT;
  2193. LONG cpBefore = (_fSearchForward ? cpFirst : cpLast) - 1;
  2194. LONG cpAfter = (_fSearchForward ? cpLast : cpFirst) + 1;
  2195. if((cpBefore < 0 ||
  2196. (ped->TxWordBreakProc(const_cast<LPTSTR>(CTxtPtr(tp._ped, cpBefore).GetPch(cchT)),
  2197. 0,
  2198. sizeof(TCHAR),
  2199. WB_CLASSIFY, cpBefore) & WBF_CLASS) ||
  2200. ped->_pbrk && ped->_pbrk->CanBreakCp(BRK_WORD, cpBefore + 1))
  2201. &&
  2202. (cpAfter >= tp.GetTextLength() ||
  2203. (ped->TxWordBreakProc(const_cast<LPTSTR>(CTxtPtr(tp._ped, cpAfter).GetPch(cchT)),
  2204. 0,
  2205. sizeof(TCHAR),
  2206. WB_CLASSIFY, cpAfter) & WBF_CLASS) ||
  2207. ped->_pbrk && ped->_pbrk->CanBreakCp(BRK_WORD, cpAfter)))
  2208. {
  2209. break;
  2210. }
  2211. else
  2212. fFound = FALSE;
  2213. }
  2214. }
  2215. if(fFound && !_fSearchForward)
  2216. {
  2217. // For search backwards, first and last are juxtaposed
  2218. LONG cpTemp = cpFirst;
  2219. cpFirst = cpLast;
  2220. cpLast = cpTemp;
  2221. }
  2222. return fFound;
  2223. }
  2224. /*
  2225. * CTxtPtr::CTxtFinder::CharCompMatchCase(ch1, ch2)
  2226. *
  2227. * @func Character comparison function sensitive to case according to parms
  2228. * of current search.
  2229. *
  2230. * @rdesc TRUE iff characters are equal
  2231. */
  2232. inline BOOL CTxtPtr::CTxtFinder::CharComp(
  2233. TCHAR ch1,
  2234. TCHAR ch2) const
  2235. {
  2236. // We compare the characters ourselves if ignore case AND the character isn't a surrogate
  2237. //
  2238. return (_fIgnoreCase && !IN_RANGE(0xD800, ch1, 0xDFFF)) ? CharCompIgnoreCase(ch1, ch2) : (ch1 == ch2);
  2239. }
  2240. /*
  2241. * CTxtPtr::CTxtFinder::CharCompIgnoreCase(ch1, ch2)
  2242. *
  2243. * @func Character comparison function
  2244. *
  2245. * @rdesc TRUE iff characters are equal, ignoring case
  2246. */
  2247. inline BOOL CTxtPtr::CTxtFinder::CharCompIgnoreCase(
  2248. TCHAR ch1,
  2249. TCHAR ch2) const
  2250. {
  2251. return CompareString(LOCALE_USER_DEFAULT,
  2252. NORM_IGNORECASE | NORM_IGNOREWIDTH,
  2253. &ch1, 1, &ch2, 1) == 2;
  2254. }
  2255. /*
  2256. * CTxtPtr::CTxtFinder::FindChar(ch, tistr)
  2257. *
  2258. * @mfunc
  2259. * Steps through the characters returned from <p tistr> until a character is
  2260. * found which matches ch or until _cchToSearch characters have been examined.
  2261. * If found, the return value indicates the number of chars read from <p tistr>.
  2262. * If not found, -1 is returned.
  2263. *
  2264. * @rdesc
  2265. * -1, if char not found
  2266. * n, if char found. n indicates number of chars read from <p tistr>
  2267. * to find the char
  2268. */
  2269. LONG CTxtPtr::CTxtFinder::FindChar(
  2270. TCHAR ch,
  2271. CTxtIStream &tistr)
  2272. {
  2273. LONG cchSave = _cchToSearch;
  2274. while(_cchToSearch)
  2275. {
  2276. _cchToSearch--;
  2277. TCHAR chComp = tistr.GetChar();
  2278. if(CharComp(ch, chComp) ||
  2279. (!_fMatchAlefhamza && IsAlef(ch) && IsAlef(chComp)))
  2280. {
  2281. return cchSave - _cchToSearch;
  2282. }
  2283. }
  2284. return -1;
  2285. }
  2286. /*
  2287. * CTxtPtr::CTxtFinder::MatchString(pchToFind, cchToFind, tistr)
  2288. *
  2289. * @mfunc
  2290. * This method compares the characters returned from <p tistr> against those
  2291. * found in pchToFind. If the string is found, the return value indicates
  2292. * how many characters were read from <p tistr> to match the string.
  2293. * If the string is not found, -1 is returned.
  2294. *
  2295. * @rdesc
  2296. * -1, if string not found
  2297. * n, if string found. n indicates number of chars read from <p tistr>
  2298. * to find string
  2299. */
  2300. LONG CTxtPtr::CTxtFinder::MatchString(
  2301. const TCHAR *pchToFind,
  2302. LONG cchToFind,
  2303. CTxtIStream &tistr)
  2304. {
  2305. if((DWORD)_cchToSearch < (DWORD)cchToFind)
  2306. return -1;
  2307. LONG cchT = cchToFind;
  2308. while(cchT--)
  2309. {
  2310. if(!CharComp(*pchToFind, tistr.GetChar()))
  2311. return -1;
  2312. pchToFind += _iDirection;
  2313. }
  2314. return cchToFind;
  2315. }
  2316. /*
  2317. * CTxtPtr::CTxtFinder::MatchStringBiDi(pchToFind, cchToFind, tistr)
  2318. *
  2319. * @mfunc
  2320. * This method compares the characters returned from <p tistr> against those
  2321. * found in pchToFind. If the string is found, the return value indicates
  2322. * how many characters were read from <p tistr> to match the string.
  2323. * If the string is not found, -1 is returned.
  2324. * Kashida, diacritics and Alefs are matched/not matched according
  2325. * to the type of search requested.
  2326. *
  2327. * @rdesc
  2328. * -1, if string not found
  2329. * n, if string found. n indicates number of chars read from <p tistr>
  2330. * to find string
  2331. */
  2332. LONG CTxtPtr::CTxtFinder::MatchStringBiDi(
  2333. const TCHAR *pchToFind,
  2334. LONG cchToFind,
  2335. CTxtIStream &tistr)
  2336. {
  2337. if((DWORD)_cchToSearch < (DWORD)cchToFind)
  2338. return -1;
  2339. LONG cchRead = 0;
  2340. while(cchToFind)
  2341. {
  2342. TCHAR chComp = tistr.GetChar();
  2343. cchRead++;
  2344. if(!CharComp(*pchToFind, chComp))
  2345. {
  2346. if (!_fMatchKashida && chComp == KASHIDA ||
  2347. !_fMatchDiac && IsBiDiDiacritic(chComp))
  2348. {
  2349. continue;
  2350. }
  2351. if (!_fMatchAlefhamza &&
  2352. IsAlef(*pchToFind) && IsAlef(chComp))
  2353. {
  2354. // Skip *pchToFind
  2355. }
  2356. else
  2357. return -1;
  2358. }
  2359. pchToFind += _iDirection;
  2360. cchToFind--;
  2361. }
  2362. return cchRead;
  2363. }