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.

706 lines
16 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module DOC.C CTxtStory and CTxtArray implementation |
  5. *
  6. * Original 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
  13. *
  14. * Copyright (c) 1995-1997, Microsoft Corporation. All rights reserved.
  15. */
  16. #include "_common.h"
  17. #include "_doc.h"
  18. #include "_format.h"
  19. ASSERTDATA
  20. // =========================== Invariant stuff ======================
  21. #define DEBUG_CLASSNAME CTxtArray
  22. #include "_invar.h"
  23. // ======================== CTxtArray class =========================
  24. #ifdef DEBUG
  25. /*
  26. * CTxtArray::Invariant
  27. *
  28. * @mfunc Tests CTxtArray's state
  29. *
  30. * @rdesc Returns TRUE always; failures are indicated by Asserts
  31. * Actually in this routine, we return count of chars in blocks
  32. * since we need this value for one check.
  33. */
  34. BOOL CTxtArray::Invariant() const
  35. {
  36. static LONG numTests = 0;
  37. numTests++; // How many times we've been called.
  38. LONG cch = 0;
  39. LONG iMax = Count();
  40. if(iMax > 0)
  41. {
  42. CTxtBlk *ptb = Elem(0);
  43. // ptb shouldn't be NULL since we're within Count elements
  44. Assert(ptb);
  45. for(LONG i = 0; i < iMax; i++, ptb++)
  46. {
  47. LONG cchCurr = ptb->_cch;
  48. cch += cchCurr;
  49. Assert ( cchCurr >= 0 );
  50. Assert ( cchCurr <= CchOfCb(ptb->_cbBlock) );
  51. // While we're here, check range of interblock gaps
  52. Assert (ptb->_ibGap >= 0);
  53. Assert (ptb->_ibGap <= ptb->_cbBlock);
  54. LONG cchGap = CchOfCb(ptb->_ibGap);
  55. Assert ( cchGap >= 0 );
  56. Assert ( cchGap <= cchCurr );
  57. }
  58. }
  59. return cch;
  60. }
  61. #endif // DEBUG
  62. /*
  63. * CTxtArray::CTxtArray()
  64. *
  65. * @mfunc Text array constructor
  66. *
  67. */
  68. CTxtArray::CTxtArray() : CArray<CTxtBlk> ()
  69. {
  70. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::CTxtArray()");
  71. AssertSz(CchOfCb(cbBlockMost) - cchGapInitial >= cchBlkInitmGapI * 2,
  72. "cchBlockMax - cchGapInitial must be at least (cchBlockInitial - cchGapInitial) * 2");
  73. Assert(!_cchText && !_iCF && !_iPF);
  74. // Make sure we have no data to initialize
  75. Assert(sizeof(CTxtArray) == sizeof(CArray<CTxtBlk>) + sizeof(_cchText) + 2*sizeof(_iCF));
  76. }
  77. /*
  78. * CTxtArray::~CTxtArray
  79. *
  80. * @mfunc Text array destructor
  81. */
  82. CTxtArray::~CTxtArray()
  83. {
  84. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::~CTxtArray");
  85. LONG itb = Count();
  86. while(itb--)
  87. {
  88. Assert(Elem(itb) != NULL);
  89. Elem(itb)->FreeBlock();
  90. }
  91. }
  92. /*
  93. * CTxtArray::CalcTextLength()
  94. *
  95. * @mfunc Computes and return length of text in this text array
  96. *
  97. * @rdesc Count of character in this text array
  98. *
  99. * @devnote This call may be computationally expensive; we have to
  100. * sum up the character sizes of all of the text blocks in
  101. * the array.
  102. */
  103. LONG CTxtArray::CalcTextLength() const
  104. {
  105. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::GetCch");
  106. _TEST_INVARIANT_
  107. LONG itb = Count();
  108. if(!itb)
  109. return 0;
  110. LONG cch;
  111. CTxtBlk *ptb = Elem(0);
  112. for(cch = 0; itb--; ptb++)
  113. cch += ptb->_cch;
  114. return cch;
  115. }
  116. /*
  117. * CTxtArray::AddBlock(itbNew, cb)
  118. *
  119. * @mfunc create new text block
  120. *
  121. * @rdesc
  122. * FALSE if block could not be added
  123. * non-FALSE otherwise
  124. *
  125. * @comm
  126. * Side Effects:
  127. * moves text block array
  128. */
  129. BOOL CTxtArray::AddBlock(
  130. LONG itbNew, //@parm index of the new block
  131. LONG cb) //@parm size of new block; if <lt>= 0, default is used
  132. {
  133. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::AddBlock");
  134. _TEST_INVARIANT_
  135. CTxtBlk *ptb;
  136. if(cb <= 0)
  137. cb = cbBlockInitial;
  138. AssertSz(cb > 0, "CTxtArray::AddBlock() - adding block of size zero");
  139. AssertSz(cb <= cbBlockMost, "CTxtArray::AddBlock() - block too big");
  140. ptb = Insert(itbNew, 1);
  141. if(!ptb || !ptb->InitBlock(cb))
  142. {
  143. TRACEERRSZSC("TXTARRAT::AddBlock() - unable to allocate new block", E_OUTOFMEMORY);
  144. return FALSE;
  145. }
  146. return TRUE;
  147. }
  148. /*
  149. * CTxtArray::SplitBlock(itb, ichSplit, cchFirst, cchLast, fStreaming)
  150. *
  151. * @mfunc split a text block into two
  152. *
  153. * @rdesc
  154. * FALSE if the block could not be split <nl>
  155. * non-FALSE otherwise
  156. *
  157. * @comm
  158. * Side Effects: <nl>
  159. * moves text block array
  160. */
  161. BOOL CTxtArray::SplitBlock(
  162. LONG itb, //@parm index of the block to split
  163. LONG ichSplit, //@parm character index within block at which to split
  164. LONG cchFirst, //@parm desired extra space in first block
  165. LONG cchLast, //@parm desired extra space in new block
  166. BOOL fStreaming) //@parm TRUE if streaming in new text
  167. {
  168. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::SplitBlock");
  169. _TEST_INVARIANT_
  170. LPBYTE pbSrc;
  171. LPBYTE pbDst;
  172. CTxtBlk *ptb, *ptb1;
  173. AssertSz(ichSplit > 0 || cchFirst > 0, "CTxtArray::SplitBlock(): splitting at beginning, but not adding anything");
  174. AssertSz(itb >= 0, "CTxtArray::SplitBlock(): negative itb");
  175. ptb = Elem(itb);
  176. // compute size for first half
  177. AssertSz(cchFirst + ichSplit <= CchOfCb(cbBlockMost),
  178. "CTxtArray::SplitBlock(): first size too large");
  179. cchFirst += ichSplit + cchGapInitial;
  180. cchFirst = min(cchFirst, CchOfCb(cbBlockMost));
  181. // compute size for second half
  182. AssertSz(cchLast + ptb->_cch - ichSplit <= CchOfCb(cbBlockMost),
  183. "CTxtArray::SplitBlock(): second size too large");
  184. cchLast += ptb->_cch - ichSplit + cchGapInitial;
  185. cchLast = min(cchLast, CchOfCb(cbBlockMost));
  186. // Allocate second block and move text to it
  187. // If streaming in, allocate a block that's as big as possible so that
  188. // subsequent additions of text are faster. We always fall back to
  189. // smaller allocations so this won't cause unnecessary errors. When
  190. // we're done streaming we compress blocks, so this won't leave a
  191. // big empty gap. NOTE: ***** moves rgtb *****
  192. if(fStreaming)
  193. {
  194. LONG cb = cbBlockMost;
  195. const LONG cbMin = CbOfCch(cchLast);
  196. while(cb >= cbMin && !AddBlock(itb + 1, cb))
  197. cb -= cbBlockCombine;
  198. if(cb >= cbMin)
  199. goto got_block;
  200. }
  201. if(!AddBlock(itb + 1, CbOfCch(cchLast)))
  202. {
  203. TRACEERRSZSC("CTxtArray::SplitBlock(): unabled to add new block", E_FAIL);
  204. return FALSE;
  205. }
  206. got_block:
  207. ptb1 = Elem(itb+1); // recompute ptb after rgtb moves
  208. ptb = Elem(itb); // recompute ptb after rgtb moves
  209. ptb1->_cch = ptb->_cch - ichSplit;
  210. ptb1->_ibGap = 0;
  211. pbDst = (LPBYTE) (ptb1->_pch - ptb1->_cch) + ptb1->_cbBlock;
  212. ptb->MoveGap(ptb->_cch); // make sure pch points to a continuous block of all text in ptb.
  213. pbSrc = (LPBYTE) (ptb->_pch + ichSplit);
  214. CopyMemory(pbDst, pbSrc, CbOfCch(ptb1->_cch));
  215. ptb->_cch = ichSplit;
  216. ptb->_ibGap = CbOfCch(ichSplit);
  217. // Resize first block
  218. if(CbOfCch(cchFirst) != ptb->_cbBlock)
  219. {
  220. //$ FUTURE: don't resize unless growing or shrinking considerably
  221. if(!ptb->ResizeBlock(CbOfCch(cchFirst)))
  222. {
  223. TRACEERRSZSC("TXTARRA::SplitBlock(): unabled to resize block", E_OUTOFMEMORY);
  224. return FALSE;
  225. }
  226. }
  227. return TRUE;
  228. }
  229. /*
  230. * CTxtArray::ShrinkBlocks()
  231. *
  232. * @mfunc Shrink all blocks to their minimal size
  233. */
  234. void CTxtArray::ShrinkBlocks()
  235. {
  236. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::ShrinkBlocks");
  237. _TEST_INVARIANT_
  238. LONG itb = Count();
  239. CTxtBlk *ptb;
  240. while(itb--)
  241. {
  242. ptb = Elem(itb);
  243. Assert(ptb);
  244. ptb->ResizeBlock(CbOfCch(ptb->_cch));
  245. }
  246. }
  247. /*
  248. * CTxtArray::RemoveBlocks(itbFirst, ctbDel)
  249. *
  250. * @mfunc remove a range of text blocks
  251. *
  252. * @rdesc
  253. * nothing
  254. *
  255. * @comm Side Effects: <nl>
  256. * moves text block array
  257. */
  258. void CTxtArray::RemoveBlocks(
  259. LONG itbFirst, //@parm index of first block to remove
  260. LONG ctbDel) //@parm number of blocks to remove
  261. {
  262. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::RemoveBlocks");
  263. _TEST_INVARIANT_
  264. LONG itb = itbFirst;
  265. LONG ctb = ctbDel;
  266. AssertSz(itb + ctb <= Count(), "CTxtArray::RemoveBlocks(): not enough blocks");
  267. while(ctb--)
  268. {
  269. Assert(Elem(itb) != NULL);
  270. Elem(itb++)->FreeBlock();
  271. }
  272. Remove(itbFirst, ctbDel);
  273. }
  274. /*
  275. * CTxtArray::CombineBlocks(itb)
  276. *
  277. * @mfunc combine adjacent text blocks
  278. *
  279. * @rdesc
  280. * nothing
  281. *
  282. * @comm
  283. * Side Effects: <nl>
  284. * moves text block array
  285. *
  286. * @devnote
  287. * scans blocks from itb - 1 through itb + 1 trying to combine
  288. * adjacent blocks
  289. */
  290. void CTxtArray::CombineBlocks(
  291. LONG itb) //@parm index of the first block modified
  292. {
  293. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::CombineBlocks");
  294. _TEST_INVARIANT_
  295. LONG ctb;
  296. LONG cbT;
  297. CTxtBlk *ptb, *ptb1;
  298. if(itb > 0)
  299. itb--;
  300. ctb = min(3, Count() - itb);
  301. if(ctb <= 1)
  302. return;
  303. for(; ctb > 1; ctb--)
  304. {
  305. ptb = Elem(itb); // Can we combine current
  306. ptb1 = Elem(itb+1); // and next blocks ?
  307. cbT = CbOfCch(ptb->_cch + ptb1->_cch + cchGapInitial);
  308. if(cbT <= cbBlockInitial)
  309. { // Yes
  310. if(cbT != ptb->_cbBlock && !ptb->ResizeBlock(cbT))
  311. continue;
  312. ptb ->MoveGap(ptb->_cch); // Move gaps at ends of
  313. ptb1->MoveGap(ptb1->_cch); // both blocks
  314. CopyMemory(ptb->_pch + ptb->_cch, // Copy next block text
  315. ptb1->_pch, CbOfCch(ptb1->_cch)); // into current block
  316. ptb->_cch += ptb1->_cch;
  317. ptb->_ibGap += CbOfCch(ptb1->_cch);
  318. RemoveBlocks(itb+1, 1); // Remove next block
  319. }
  320. else
  321. itb++;
  322. }
  323. }
  324. /*
  325. * CTxtArray::GetChunk(ppch, cch, pchChunk, cchCopy)
  326. *
  327. * @mfunc
  328. * Get content of text chunk in this text array into a string
  329. *
  330. * @rdesc
  331. * remaining count of characters to get
  332. */
  333. LONG CTxtArray::GetChunk(
  334. TCHAR **ppch, //@parm ptr to ptr to buffer to copy text chunk into
  335. LONG cch, //@parm length of pch buffer
  336. TCHAR *pchChunk, //@parm ptr to text chunk
  337. LONG cchCopy) const //@parm count of characters in chunk
  338. {
  339. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtArray::GetChunk");
  340. _TEST_INVARIANT_
  341. if(cch > 0 && cchCopy > 0)
  342. {
  343. if(cch < cchCopy)
  344. cchCopy = cch; // Copy less than full chunk
  345. CopyMemory(*ppch, pchChunk, cchCopy*sizeof(TCHAR));
  346. *ppch += cchCopy; // Adjust target buffer ptr
  347. cch -= cchCopy; // Fewer chars to copy
  348. }
  349. return cch; // Remaining count to copy
  350. }
  351. const CCharFormat* CTxtArray::GetCharFormat(LONG iCF)
  352. {
  353. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtArray::GetCharFormat");
  354. const CCharFormat * pCF;
  355. if(iCF < 0)
  356. iCF = _iCF;
  357. Assert(iCF >= 0);
  358. if(FAILED(GetCharFormatCache()->Deref(iCF, &pCF)))
  359. {
  360. AssertSz(FALSE, "CTxtArray::GetCharFormat: couldn't deref iCF");
  361. pCF = NULL;
  362. }
  363. return pCF;
  364. }
  365. const CParaFormat* CTxtArray::GetParaFormat(LONG iPF)
  366. {
  367. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtArray::GetParaFormat");
  368. const CParaFormat * pPF;
  369. if(iPF < 0)
  370. iPF = _iPF;
  371. Assert(iPF >= 0);
  372. if(FAILED(GetParaFormatCache()->Deref(iPF, &pPF)))
  373. {
  374. AssertSz(FALSE, "CTxtArray::GetParaFormat: couldn't deref iPF");
  375. pPF = NULL;
  376. }
  377. return pPF;
  378. }
  379. // ======================== CTxtBlk class =================================
  380. /*
  381. * CTxtBlk::InitBlock(cb)
  382. *
  383. * @mfunc
  384. * Initialize this text block
  385. *
  386. * @rdesc
  387. * TRUE if success, FALSE if allocation failed
  388. */
  389. BOOL CTxtBlk::InitBlock(
  390. LONG cb) //@parm initial size of the text block
  391. {
  392. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtBlk::InitBlock");
  393. _pch = NULL;
  394. _cch = 0;
  395. _ibGap = 0;
  396. _cbBlock= cb;
  397. if(cb)
  398. _pch = (TCHAR*)PvAlloc(cb, GMEM_ZEROINIT);
  399. return _pch != 0;
  400. }
  401. /*
  402. * CTxtBlk::FreeBlock()
  403. *
  404. * @mfunc
  405. * Free this text block
  406. *
  407. * @rdesc
  408. * nothing
  409. */
  410. VOID CTxtBlk::FreeBlock()
  411. {
  412. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtBlk::FreeBlock");
  413. FreePv(_pch);
  414. _pch = NULL;
  415. _cch = 0;
  416. _ibGap = 0;
  417. _cbBlock= 0;
  418. }
  419. /*
  420. * CTxtBlk::MoveGap(ichGap)
  421. *
  422. * @mfunc
  423. * move gap in this text block
  424. *
  425. * @rdesc
  426. * nothing
  427. */
  428. void CTxtBlk::MoveGap(
  429. LONG ichGap) //@parm new position for the gap
  430. {
  431. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtBlk::MoveGap");
  432. LONG cbMove;
  433. LONG ibGapNew = CbOfCch(ichGap);
  434. LPBYTE pbFrom = (LPBYTE) _pch;
  435. LPBYTE pbTo;
  436. if(ibGapNew == _ibGap)
  437. return;
  438. if(ibGapNew < _ibGap)
  439. {
  440. cbMove = _ibGap - ibGapNew;
  441. pbFrom += ibGapNew;
  442. pbTo = pbFrom + _cbBlock - CbOfCch(_cch);
  443. }
  444. else
  445. {
  446. cbMove = ibGapNew - _ibGap;
  447. pbTo = pbFrom + _ibGap;
  448. pbFrom = pbTo + _cbBlock - CbOfCch(_cch);
  449. }
  450. MoveMemory(pbTo, pbFrom, cbMove);
  451. _ibGap = ibGapNew;
  452. }
  453. /*
  454. * CTxtBlk::ResizeBlock(cbNew)
  455. *
  456. * @mfunc
  457. * resize this text block
  458. *
  459. * @rdesc
  460. * FALSE if block could not be resized <nl>
  461. * non-FALSE otherwise
  462. *
  463. * @comm
  464. * Side Effects: <nl>
  465. * moves text block
  466. */
  467. BOOL CTxtBlk::ResizeBlock(
  468. LONG cbNew) //@parm the new size
  469. {
  470. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtBlk::ResizeBlock");
  471. TCHAR *pch;
  472. LONG cbMove;
  473. AssertSz(cbNew > 0, "resizing block to size <= 0");
  474. AssertSz(cbNew <= cbBlockMost, "CTxtBlk::ResizeBlock() - block too big");
  475. if(cbNew < _cbBlock)
  476. {
  477. if(_ibGap != CbOfCch(_cch))
  478. {
  479. // move text after gap down so that it doesn't get dropped
  480. cbMove = CbOfCch(_cch) - _ibGap;
  481. pch = _pch + CchOfCb(_cbBlock - cbMove);
  482. MoveMemory(pch - CchOfCb(_cbBlock - cbNew), pch, cbMove);
  483. }
  484. _cbBlock = cbNew;
  485. }
  486. pch = (TCHAR*)PvReAlloc(_pch, cbNew);
  487. if(!pch)
  488. return _cbBlock == cbNew; // FALSE if grow, TRUE if shrink
  489. _pch = pch;
  490. if(cbNew > _cbBlock)
  491. {
  492. if(_ibGap != CbOfCch(_cch)) // Move text after gap to end so that
  493. { // we don't end up with two gaps
  494. cbMove = CbOfCch(_cch) - _ibGap;
  495. pch += CchOfCb(_cbBlock - cbMove);
  496. MoveMemory(pch + CchOfCb(cbNew - _cbBlock), pch, cbMove);
  497. }
  498. _cbBlock = cbNew;
  499. }
  500. return TRUE;
  501. }
  502. // ======================== CTxtStory class ============================
  503. /*
  504. * CTxtStory::CTxtStory
  505. *
  506. * @mfunc Constructor
  507. *
  508. * @devnote Automatically allocates a text array. If we want to have a
  509. * completely empty edit control, then don't allocate a story. NB!
  510. *
  511. */
  512. CTxtStory::CTxtStory()
  513. {
  514. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtStory::CTxtStory");
  515. _pCFRuns = NULL;
  516. _pPFRuns = NULL;
  517. }
  518. /*
  519. * CTxtStory::~CTxtStory
  520. *
  521. * @mfunc Destructor
  522. */
  523. CTxtStory::~CTxtStory()
  524. {
  525. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtStory::~CTxtStory");
  526. // Remove formatting.
  527. DeleteFormatRuns();
  528. }
  529. /*
  530. * DeleteRuns ()
  531. *
  532. * @mfunc
  533. * Helper function for DeleteFormatRuns() below. Releases
  534. * formats used by format run collection before deleting the
  535. * collection
  536. */
  537. void DeleteRuns(CFormatRuns *pRuns, IFormatCache *pf)
  538. {
  539. if(pRuns) // Format runs may exist
  540. {
  541. LONG n = pRuns->Count();
  542. if(n)
  543. {
  544. CFormatRun *pRun = pRuns->Elem(0);
  545. for( ; n--; pRun++)
  546. pf->Release(pRun->_iFormat); // Free run's format
  547. }
  548. delete pRuns;
  549. }
  550. }
  551. /*
  552. * CTxtStory::DeleteFormatRuns ()
  553. *
  554. * @mfunc Convert to plain - remove format runs
  555. */
  556. void CTxtStory::DeleteFormatRuns()
  557. {
  558. TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CTxtStory::ConvertToPlain");
  559. DeleteRuns(_pCFRuns, GetCharFormatCache());
  560. DeleteRuns(_pPFRuns, GetParaFormatCache());
  561. _pCFRuns = NULL;
  562. _pPFRuns = NULL;
  563. }
  564. #ifdef DEBUG
  565. //This dumps the contents of the CTxtStory
  566. //TxtBlk & FormatRun arrays to the debug output.
  567. void CTxtStory::DbgDumpStory(void)
  568. {
  569. CTxtBlk * pblk;
  570. CFormatRun * pcfr;
  571. CFormatRun * ppfr;
  572. LONG ctxtr = 0;
  573. LONG ccfr = 0;
  574. LONG cpfr = 0;
  575. LONG i;
  576. ctxtr = _TxtArray.Count();
  577. if (_pCFRuns)
  578. ccfr = _pCFRuns->Count();
  579. if (_pPFRuns)
  580. cpfr = _pPFRuns->Count();
  581. for(i = 0; i < ctxtr; i++)
  582. {
  583. pblk = (CTxtBlk*)_TxtArray.Elem(i);
  584. Tracef(TRCSEVNONE, "TxtBlk #%d: cch = %d.", (i + 1), pblk->_cch);
  585. }
  586. for(i = 0; i < ccfr; i++)
  587. {
  588. pcfr = (CFormatRun*)_pCFRuns->Elem(i);
  589. Tracef(TRCSEVNONE, "CFR #%d: cch = %d, iFormat = %d.",(i + 1), pcfr->_cch, pcfr->_iFormat);
  590. }
  591. for(i = 0; i < cpfr; i++)
  592. {
  593. ppfr = (CFormatRun*)_pPFRuns->Elem(i);
  594. Tracef(TRCSEVNONE, "PFR #%d: cch = %d, iFormat = %d.",(i + 1), ppfr->_cch, ppfr->_iFormat);
  595. }
  596. }
  597. #endif