/* * @doc INTERNAL * * @module FRUNPTR.C -- FormatRunPtr methods | * * common code to handle character and paragraph format runs * * Original Authors: * Original RichEdit 1.0 code: David R. Fulmer * Christian Fortini * Murray Sargent * * History: * 6/25/95 alexgo convert to use Auto-Doc and simplified backing * store model * * @devnote * BOR and EOR mean Beginning Of Run and End Of Run, respectively * * Copyright (c) 1995-1999, Microsoft Corporation. All rights reserved. */ #include "_common.h" #include "_edit.h" #include "_frunptr.h" #include "_rtext.h" #include "_font.h" ASSERTDATA // // Invariant stuff // #define DEBUG_CLASSNAME CFormatRunPtr #include "_invar.h" #ifdef DEBUG /* * CFormatRunPtr::Invariant * * @mfunc Invariant for format run pointers * * @rdesc BOOL */ BOOL CFormatRunPtr::Invariant() const { if(IsValid()) { CFormatRun *prun = GetRun(0); if(prun && _iRun) { Assert(prun->_cch > 0); } } else { Assert(_ich == 0); } return CRunPtrBase::Invariant(); } #endif /* * CFormatRunPtr::InitRuns(ich, cch, iFormat, ppfrs) * * @mfunc * Setup this format run ptr for rich-text operation, namely, * allocate CArrayCFormatRun if not allocated, assign it to this * run ptr's _pRuns, add initial run if no runs are present, and store * initial cch and ich * * @rdesc * TRUE if succeeds */ BOOL CFormatRunPtr::InitRuns( LONG ich, //@parm # chars in initial run LONG cch, //@parm char offset in initial run CFormatRuns **ppfrs) //@parm ptr to CFormatRuns ptr { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CFormatRunPtr::InitRuns"); _TEST_INVARIANT_ AssertSz( ppfrs, "FRP::InitRuns: illegal ptr to runs"); AssertSz( !IsValid(), "FRP::InitRuns: ptr already valid"); if(!*ppfrs) // Allocate format runs { _pRuns = (CRunArray *) new CFormatRuns(); if(!_pRuns) goto NoRAM; *ppfrs = (CFormatRuns *)_pRuns; } else // Format runs already alloc'd _pRuns = (CRunArray *)*ppfrs; // Cache ptr to runs if(!Count()) // No runs yet, so add one { CFormatRun *pRun= Add(1, NULL); if(!pRun) goto NoRAM; #ifdef DEBUG PvSet(*(void**)_pRuns); #endif _ich = ich; ZeroMemory(pRun, sizeof(*pRun)); pRun->_cch = cch; // Define its _cch pRun->_iFormat = -1; // and _iFormat } else BindToCp(ich); // Format runs are in place return TRUE; NoRAM: TRACEERRSZSC("CFormatRunPtr::InitRuns: Out Of RAM", E_OUTOFMEMORY); return FALSE; } /* * CFormatRunPtr::Delete(cch, pf, cchMove) * * @mfunc * Delete/modify runs starting at this run ptr up to cch chars. * There are 7 possibilities: * 1. cch comes out of this run with count left over, i.e., * cch = (*this)->_cch - _ich && (*this)->_cch > cch * (simple: no runs deleted/merged, just subtract cch) * 2. cch comes out of this run and empties run and doc * (simple: no runs left to delete/merge) * 3. cch comes out of this run and empties run, which is last * (need to delete run, no merge possibility) * 4. cch comes out of this run and empties run, which is first * (need to delete run, no merge possibility) * 5. cch exceeds count available in this run and this run is last * (simple: treat as 3.) * 6. cch comes out of this run and empties run with runs before * and after (need to delete run; merge possibility) * 7. cch comes partly out of this run and partly out of later run(s) * (may need to delete and merge) * * @comm * PARAFORMATs have two special cases that use the cchMove argument set * up in CRchTxtPtr::ReplaceRange(). */ void CFormatRunPtr::Delete( LONG cch, //@parm # chars to modify format runs for IFormatCache *pf, //@parm IFormatCache ptr for ReleaseFormat LONG cchMove) //@parm cch to move between runs (always 0 for CF) { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CFormatRunPtr::Delete"); _TEST_INVARIANT_ // We should not have any boundary cases for empty or NULL pointers. // (i.e. if there's no text, then nobody should be calling delete). Assert(IsValid()); LONG cchEnd = 0; // Probably unnecessary: see below LONG cRun = 1; BOOL fLast = (_iRun == Count() - 1); LONG ifmtEnd, ifmtStart; CFormatRun * pRun = Elem(_iRun); CFormatRun * pRunRp; LONG cchChunk = pRun->_cch - _ich; CFormatRunPtr rp(*this); // Clone this run ptr CBiDiLevel levelStart = {0,0}; CBiDiLevel levelEnd = {0,0}; rp.AdjustBackward(); // If at BOR, move to prev EOR ifmtStart = rp.GetRun(0)->_iFormat; // to get start format levelStart = rp.GetRun(0)->_level; // and level rp = *this; // In case RpAdjustCp() backed up // Process deletes confined to this run first, since their logic tends to // clutter up other cases AssertSz(cch >= 0, "FRP::Delete: cch < 0"); if(fLast) // Handle oversized cch on last cch = min(cch, cchChunk); // run here if(cch <= cchChunk) // cch comes out of this run { pRun->_cch -= cch; Assert(pRun->_cch >= 0); if(cchMove) // If nonzero here, we are { // deleting EOP at end of run rp.AdjustForward(); // Adjust rp to beginning of goto move; // next run and go move cchMove } // chars back into this run if(pRun->_cch) // Something left in run: done return; // Note: _ich = 0 if(!_iRun || fLast) // This run is either first { // or last AdjustBackward(); // If last, go to prev EOR if(_ich) // This run is empty so delete cRun++; // Compensate for cRun-- coming up ifmtStart = -2; // No runs eligible for merging } // so use unmatchable ifmtStart rp.NextRun(); // Set up to get next _iFormat } else { rp.Move(cch); // Move clone to end of delete pRunRp = rp.GetRun(0); cRun = rp._iRun - _iRun // If at EOR, then need to add + (rp._ich == pRunRp->_cch); // one more run to delete pRun->_cch = _ich; // Shorten this run to _ich chars pRunRp->_cch -= rp._ich; // Shorten last run by rp._ich rp._ich = 0; Assert(pRunRp->_cch >= 0); AssertSz(cRun > 0, "FRP: bogus runptr"); if(!_iRun) // First run? ifmtStart = -2; // Then we cannot merge runs so } // set to unmergable format ifmtEnd = -3; // Default invalid format at end if(rp.IsValid()) { // FUTURE (murrays): probably rp is always valid here now and // pRun->_cch is nonzero pRun = rp.GetRun(0); if (pRun->_cch) // run not empty { ifmtEnd = pRun->_iFormat; // Remember end format and count levelEnd = pRun->_level; cchEnd = pRun->_cch; // in case of merge } else if(rp._iRun != rp.Count() - 1) // run not last { pRun = rp.GetRun(1); ifmtEnd = pRun->_iFormat; // Remember end format and count levelEnd = pRun->_level; cchEnd = pRun->_cch; // in case of merge } } rp = *this; // Default to delete this run if(_ich) // There are chars in this run { if(cchMove + _ich == 0) // Need to combine all chars of { // this run with run after del, pf->AddRef(ifmtEnd); // so setup merge below using ifmtStart = ifmtEnd; // ifmtEnd. This run then takes pf->Release(GetRun(0)->_iFormat); GetRun(0)->_iFormat = ifmtEnd; // place of run after del. GetRun(0)->_level = levelEnd; cchMove = 0; // cchMove all accounted for } rp.NextRun(); // Don't delete this run; start cRun--; // with next one } AdjustBackward(); // If !_ich, go to prev EOR if(ifmtEnd >=0 && // Same formats: merge runs ifmtEnd == ifmtStart && levelStart == levelEnd) { GetRun(0)->_cch += cchEnd; // Add last-run cch to this one's Assert(GetRun(0)->_cch >= 0); cRun++; // Setup to eat last run } if(cRun > 0) // There are run(s) to delete { rp.Remove(cRun, pf); if(!Count()) // If no more runs, keep this rp _ich = _iRun = 0; // valid by pointing at cp = 0 } move: if(cchMove) // Need to move some cch between { // this run and next (See GetRun(0)->_cch += cchMove; // CRchTxtPtr::ReplaceRange()) rp.GetRun(0)->_cch -= cchMove; Assert(GetRun(0)->_cch >= 0); Assert(rp.GetRun(0)->_cch >= 0); Assert(_iRun < rp._iRun); if(!rp.GetRun(0)->_cch) // If all chars moved out of rp's rp.Remove(1, pf); // run, delete it if(cchMove < 0) // Moved -cchMove chars from this { // run to next if(!GetRun(0)->_cch) Remove(1, pf); else _iRun++; // Keep this run ptr in sync with _ich = -cchMove; // cp (can't use NextRun() due } // to Invariants) } AdjustForward(); // Don't leave ptr at EOR unless } // there are no more runs /* * CFormatRunPtr::InsertFormat(cch, ifmt, pf) * * @mfunc * Insert cch chars with format ifmt into format runs starting at * this run ptr * * @rdesc * count of characters added * * @devnote * It is the caller's responsibility to ensure that we are in the * "normal" or "empty" state. A format run pointer doesn't know about * CTxtStory, so it can't create the run array without outside help. */ LONG CFormatRunPtr::InsertFormat( LONG cch, //@parm # chars to insert LONG ifmt, //@parm format to use IFormatCache *pf) //@parm pointer to IFormatCache to AddRefFormat { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CFormatRunPtr::InsertFormat"); LONG cRun; CFormatRun *pRun; CFormatRun *pRunPrev; LONG cchRun; // Current-run length, LONG ich; // offset, and LONG iFormat; // format _TEST_INVARIANT_ Assert(_pRuns); if(!IsValid()) { // Empty run case (occurs when inserting after all text is deleted) pRun = Add(1, NULL); goto StoreNewRunData; // (located at end of function) } // Go to previous run if at a boundary case AdjustBackward(); pRun = Elem(_iRun); // Try other cases cchRun = pRun->_cch; iFormat = pRun->_iFormat; ich = _ich; // Same run case. Note that there is an additional boundary case; if we // are the _end_ of one run, then the next run may have the necessary // format. if(ifmt == iFormat) // IP already has correct fmt { pRun->_cch += cch; _ich += cch; // Inc offset to keep in sync return cch; } if(_ich == pRun->_cch && _iRun < _pRuns->Count() - 1) { AdjustForward(); pRun = Elem(_iRun); Assert(pRun); if(pRun->_iFormat == ifmt) { pRun->_cch += cch; _ich += cch; return cch; } AdjustBackward(); } // Prior run case (needed when formatting change occurs on line break // and caret is at beginning of new line) if(!ich && _iRun > 0 ) // IP at start of run { pRunPrev = GetPtr(pRun, -1); if( ifmt == pRunPrev->_iFormat) // Prev run has same format: { // add count to prev run and pRunPrev->_cch += cch; return cch; } } // Create new run[s] cases. There is a special case for a format // run of zero length: just re-use it. if(!pRun->_cch) { // This assert has been toned down to ignore a plain text control // being forced into IME Rich Composition. AssertSz( /* FALSE */ pRun->_iFormat == -1 && Count() == 1, "CFormatRunPtr::InsertFormat: 0-length run"); pf->Release(pRun->_iFormat); } else // Need to create 1 or 2 new { // runs for insertion cRun = 1; // Default 1 new run if(ich && ich < cchRun) // Not at beginning or end of cRun++; // run, so need two new runs // The following insert call adds one or two runs at the current // position. If the new run is inserted at the beginning or end // of the current run, the latter needs no change; however, if // the new run splits the current run in two, both pieces have // to be updated (cRun == 2 case). pRun = Insert(cRun); // Insert cRun run(s) if(!pRun) // Out of RAM. Can't insert { // new format, but can keep _ich += cch; // run ptr and format runs GetRun(0)->_cch += cch; // valid. Note: doesn't return cch; // signal any error; no access } // to _ped->_fErrSpace if(ich) // Not at beginning of run, { pRunPrev = pRun; // Previous run is current run IncPtr(pRun); // New run is next run VALIDATE_PTR(pRun); pRun->_cch = cch; // Keep NextRun() invariant happy NextRun(); // Point this runptr at it too if(cRun == 2) // Are splitting current run { // _iFormat's are already set AssertSz(pRunPrev->_iFormat == iFormat, "CFormatRunPtr::InsertFormat: bad format inserted"); pRunPrev->_cch = ich; // Divide up original cch GetPtr(pRun, 1)->_cch // accordingly = cchRun - ich; pf->AddRef(iFormat); // Addref iFormat for extra run } } } StoreNewRunData: pf->AddRef(ifmt); // Addref ifmt ZeroMemory(pRun, sizeof(*pRun)); pRun->_iFormat = ifmt; // Store insert format and count pRun->_cch = cch; // of new run _ich = cch; // cp goes at end of insertion return cch; } /* * CFormatRunPtr::MergeRuns(iRun, pf) * * @mfunc * Merge adjacent runs that have the same format between this run * and that for

* * @comm * Changes this run ptr */ void CFormatRunPtr::MergeRuns( LONG iRun, //@parm last run to check (can preceed or follow // ) IFormatCache *pf) //@parm pointer to IFormatCache to ReleaseFormat { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CFormatRunPtr::MergeRuns"); LONG cch; LONG cRuns = iRun - _iRun; LONG iDirection = 1; // Default going forward CFormatRun *pRun; _TEST_INVARIANT_ if(cRuns < 0) { cRuns = -cRuns; iDirection = -1; } if(!IsValid()) // Allow starting run to be { // invalid Assert(FALSE); // I think this is old... ChgRun(iDirection); } while(cRuns--) { if(!GetRun(0)->_cch && !_iRun && _iRun < Count() - 1) { if(iDirection > 0) PrevRun(); Remove(1, pf); continue; } pRun = GetRun(0); // Save the current run if(!ChgRun(iDirection)) // Go to next (or prev) run return; // No more runs to check if(pRun->SameFormat(GetRun(0))) { // Like formatted runs if(iDirection > 0) // Point at the first of the PrevRun(); // two runs cch = GetRun(0)->_cch; // Save its count Remove(1, pf); // Remove it GetRun(0)->_cch += cch; // Add its count to the other's, } // i.e., they're merged } } /* * CFormatRunPtr::Remove(cRun, flag, pf) * * @mfunc * Remove cRun runs starting at _iRun */ void CFormatRunPtr::Remove( LONG cRun, IFormatCache *pf) { CFormatRun *pRun = GetRun(0); // Point at run(s) to delete for(LONG j = 0; j < cRun; j++, IncPtr(pRun)) pf->Release(pRun->_iFormat); // Decrement run reference count CRunPtr::Remove(cRun); } /* * CFormatRunPtr::SetFormat(ifmt, cch, pf, pLevel) * * @mfunc * Set format for up to cch chars of this run to ifmt, splitting run * as needed, and returning the character count actually processed * * @rdesc * character count of run chunk processed, CP_INFINITE on failure * this points at next run * * Comments: * Changes this run ptr. cch must be >= 0. * * Note 1) for the first run in a series, _ich may not = 0, and 2) cch * may be , =, or the count remaining in the run. The algorithm * doesn't split runs when the format doesn't change. */ LONG CFormatRunPtr::SetFormat( LONG ifmt, //@parm format index to use LONG cch, //@parm character count of remaining format range IFormatCache * pf, //@parm pointer to IFormatCache to CBiDiLevel* pLevel) //@parm pointer to BiDi level structure { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CFormatRunPtr::SetFormat"); // AddRefFormat/ReleaseFormat LONG cchChunk; LONG iFormat; CFormatRun * pRun; CFormatRun * pChgRun; // run that was reformatted CBiDiLevel level; _TEST_INVARIANT_ if(!IsValid()) return 0; pRun = GetRun(0); // pRun points at current run in cchChunk = pRun->_cch - _ich; // this function iFormat = pRun->_iFormat; level = pRun->_level; pChgRun = pRun; AssertSz(cch, "Have to have characters to format!"); AssertSz(pRun->_cch, "uh-oh, empty format run detected"); if(ifmt != iFormat || (pLevel && level != *pLevel)) // New and current formats differ { AssertSz(cchChunk, "Caller did not call AdjustForward"); if(_ich) // Not at either end of run: need { // to split into two runs of if(!(pRun = Insert(1))) // counts _ich and _pRun->_cch { // - _ich, respectively return CP_INFINITE; // Out of RAM: do nothing; just } // keep current format pRun->_cch = _ich; pRun->_iFormat = iFormat; // New run has same format pRun->_level = level; // and same level pf->AddRef(iFormat); // Increment format ref count NextRun(); // Go to second (original) run IncPtr(pRun); // Point pRun at current run pRun->_cch = cchChunk; // Note: IncPtr is a bit more pChgRun = pRun; } // efficient than GetRun, but // trickier to code right if(cch < cchChunk) // cch doesn't cover whole run: { // need to split into two runs if(!(pRun = Insert(1))) { // Out of RAM, so formatting's wrong, oh well. We actually // "processed" all of the characters, so return that (though // the tail end formatting isn't split out right) return cch; } pRun->_cch = cch; // New run gets the cch pRun->_iFormat = ifmt; // and the new format pChgRun = pRun; IncPtr(pRun); // Point pRun at current run pRun->_cch = cchChunk - cch; // Set leftover count } else // cch as big or bigger than { // current run pf->Release(iFormat); // Free run's current format pRun->_iFormat = ifmt; // Change it to new format pChgRun = pRun; } // May get merged later pf->AddRef(ifmt); // Increment new format ref count } else if(!cchChunk) { pRun->_cch += cch; // Add cch to end of current run cchChunk = cch; // Report that all cch are done IncPtr(pRun); pRun->_cch -= cch; // Remove count from next run if(!pRun->_cch) // Next run is now empty, so { // remove it _iRun++; Remove(1, pf); _iRun--; // Backup to start run } } // Record embedding level to changed run if (pLevel) pChgRun->_level = *pLevel; cch = min(cch, cchChunk); Move(cch); AdjustForward(); return cch; } /* * CFormatRunPtr::GetFormat() * * @mfunc * return format index at current run pointer position * * @rdesc * current format index */ short CFormatRunPtr::GetFormat() const { TRACEBEGIN(TRCSUBSYSBACK, TRCSCOPEINTERN, "CFormatRunPtr::GetFormat"); _TEST_INVARIANT_ return IsValid() ? GetRun(0)->_iFormat : -1; } /* * CFormatRunPtr::SplitFormat(IFormatCache*) * * @mfunc * Split a format run * * @rdesc * If succeeded the run pointer moves to the next splitted run */ void CFormatRunPtr::SplitFormat(IFormatCache* pf) { if (!_ich || _ich == GetRun(0)->_cch) return; CFormatRun* pRun = GetRun(0); LONG iFormat = pRun->_iFormat; LONG cch = pRun->_cch - _ich; CBiDiLevel level = pRun->_level; if (pRun = Insert(1)) { pRun->_cch = _ich; pRun->_iFormat = iFormat; pRun->_level = level; pf->AddRef(iFormat); NextRun(); IncPtr(pRun); pRun->_cch = cch; } } /* * CFormatRunPtr::SetLevel(level) * * @mfunc * Set run's embedding level */ void CFormatRunPtr::SetLevel (CBiDiLevel& level) { if (!IsValid()) { Assert(FALSE); return; } CFormatRun* pRun = GetRun(0); if (pRun) pRun->_level = level; } BYTE CFormatRunPtr::GetLevel (CBiDiLevel* pLevel) { CFormatRun* pRun; if (!IsValid() || !(pRun = GetRun(0))) { Assert(FALSE); if (pLevel) { pLevel->_value = 0; pLevel->_fStart = FALSE; } return 0; } if (pLevel) *pLevel = pRun->_level; return pRun->_level._value; } /* * CFormatRunPtr::AdjustFormatting(cch, pf) * * @mfunc * Use the same format index for the cch chars at this run ptr * as that immediately preceeding it (if on run edge). * * @devnote * This runptr ends up pointing at what was the preceeding run, * since the current run has been moved into the preceeding run. * * FUTURE: might be better to take the cch equal to chars in * the following run. */ void CFormatRunPtr::AdjustFormatting( LONG cch, //@parm Count of chars to extend formatting IFormatCache *pf) //@parm Format cache ptr for AddRef/Release { if(!IsValid()) return; // Nothing to merge CFormatRunPtr rp(*this); CBiDiLevel level; // Move this run ptr to end of AdjustBackward(); // preceeding run (if at run edge) rp.AdjustForward(); // (merge may delete run at entry) if(_iRun != rp._iRun) // On a format edge: copy previous { // format index over GetLevel(&level); rp.SetFormat(GetFormat(), cch, pf, &level); // Format cch chars at this rp.MergeRuns(_iRun, pf); // runptr } } ///////////////////////////// CCFRunPtr /////////////////////////////// CCFRunPtr::CCFRunPtr(const CRchTxtPtr &rtp) : CFormatRunPtr(rtp._rpCF) { _ped = rtp.GetPed(); } CCFRunPtr::CCFRunPtr(const CFormatRunPtr &rp, CTxtEdit *ped) : CFormatRunPtr(rp) { _ped = ped; } /* * CCFRunPtr::IsMask(dwMask, MaskOp) * * @mfunc * return TRUE according to the mask operation MaskOp operating on * _dwEffects. * * @rdesc * TRUE if bits in CCharFormat::dwEffects correspond to those in dwMask */ BOOL CCFRunPtr::IsMask( DWORD dwMask, //@parm Bit mask to use on dwEffects MASKOP MaskOp) //@parm Logic operation for bits { DWORD dwEffects = _ped->GetCharFormat(GetFormat())->_dwEffects; if(MaskOp == MO_EXACT) // Bit masks must be identical return dwEffects == dwMask; dwEffects &= dwMask; if(MaskOp == MO_OR) // TRUE if one or more effect bits return dwEffects != 0; // identified by mask are on if(MaskOp == MO_AND) // TRUE if all effect bits return dwEffects == dwMask; // identified by mask are on AssertSz(FALSE, "CCFRunPtr::IsMask: illegal mask operation"); return FALSE; } /* * CCFRunPtr::IsInHidden() * * @mfunc * return TRUE if CCharFormat for this run ptr has CFE_HIDDEN bit set * * @rdesc * TRUE if CCharFormat for this run ptr has CFE_HIDDEN bit set */ BOOL CCFRunPtr::IsInHidden() { if (!IsValid()) return FALSE; // No format run, not hidden AdjustForward(); BOOL fHidden = IsHidden(); if(_ich) return fHidden; AdjustBackward(); return fHidden && IsHidden(); } /* * CCFRunPtr::FindUnhidden() * * @mfunc * Find nearest expanded CF going forward. If none, find nearest going * backward. If none, go to start of document * * @rdesc * cch to nearest expanded CF as explained in function description * * @devnote * changes this run ptr */ LONG CCFRunPtr::FindUnhidden() { LONG cch = FindUnhiddenForward(); if(IsHidden()) cch = FindUnhiddenBackward(); return cch; } /* * CCFRunPtr::FindUnhiddenForward() * * @mfunc * Find nearest expanded CF going forward. If none, go to EOD * * @rdesc * cch to nearest expanded CF going forward * * @devnote * changes this run ptr */ LONG CCFRunPtr::FindUnhiddenForward() { LONG cch = 0; AdjustForward(); while(IsHidden()) { cch += GetCchLeft(); if(!NextRun()) break; } return cch; } /* * CCFRunPtr::MatchFormatSignature * * @mfunc * Match the current format's font signature with the script (index to codepage). * It takes care single-codepage fonts which implicitly supports ASCII range. * * @rdesc * return how font matched */ inline int CCFRunPtr::MatchFormatSignature ( const CCharFormat* pCF, int iCharRep, int iMatchCurrent, QWORD * pqwFontSig) { QWORD qwFontSig = 0; if (GetFontSignatureFromFace(pCF->_iFont, &qwFontSig) != 0) { if (pqwFontSig) *pqwFontSig = qwFontSig; if (iMatchCurrent & MATCH_ASCII && fc().GetInfoFlags(pCF->_iFont).fNonBiDiAscii) return MATCH_ASCII; if (FontSigFromCharRep(iCharRep) & ~FASCII & qwFontSig) return MATCH_FONT_SIG; } return 0; } /* * CCFRunPtr::GetPreferredFontInfo(iCharRep, &iCharRepRet, &iFont, &yHeight, &bPitchAndFamily, * iFormat, iMatchCurrent, piFormatOut ) * * @mfunc * Find the preferred font for the given code page around the range. * * @rdesc * boolean true if suitable font found, false otherwise. */ bool CCFRunPtr::GetPreferredFontInfo( BYTE iCharRep, BYTE & iCharRepRet, SHORT& iFont, SHORT& yHeight, // return in twips BYTE& bPitchAndFamily, int iFormat, int iMatchCurrent, int *piFormatOut) { int i; bool fr = false; static int const MAX_FONTSEARCH = 256; const CCharFormat *pCF; const CCharFormat *pCFCurrent; const CCharFormat *pCFPrevious = NULL; int iMatch = 0; // how signature match? QWORD qwFontSigCurrent = 0; SHORT yNewHeight = 0; bool fUseUIFont = _ped->fUseUIFont() || _ped->Get10Mode(); Assert(!(iMatchCurrent & MATCH_ASCII) || iCharRep == ANSI_INDEX); if(_ped->fUseUIFont()) pCFCurrent = _ped->GetCharFormat(-1); // Plain text or UI font specified else pCFCurrent = _ped->GetCharFormat(iFormat != -1 ? iFormat : GetFormat()); if (iMatchCurrent == GET_HEIGHT_ONLY) // Just do the font autosizing. { fr = true; pCF = NULL; goto DO_SIZE; } if ((iMatchCurrent & MATCH_FONT_SIG) && (iMatch = MatchFormatSignature(pCFCurrent, iCharRep, iMatchCurrent, &qwFontSigCurrent)) != 0) { pCF = pCFCurrent; // Setup to use it } else { int iFormatOut; // Try searching backwards if (IsValid()) // If doc has CF runs AdjustBackward(); i = MAX_FONTSEARCH; // Don't be searching for years iFormatOut = GetFormat(); pCF = _ped->GetCharFormat(iFormatOut); while (i--) { if(iCharRep == pCF->_iCharRep) // Equal charset ids? { pCFPrevious = pCF; break; } if (!PrevRun()) // Done searching? break; iFormatOut = GetFormat(); pCF = _ped->GetCharFormat(iFormatOut); } pCF = pCFPrevious; if (piFormatOut && pCF) { *piFormatOut = iFormatOut; return true; // Done since we only ask for the format. } } // Try match charset if requested if(!pCF && iMatchCurrent == MATCH_CURRENT_CHARSET) { CCcs* pccs = _ped->GetCcs(pCFCurrent, W32->GetYPerInchScreenDC()); if (pccs) { if(pccs->BestCharRep(iCharRep, DEFAULT_INDEX, MATCH_CURRENT_CHARSET) != DEFAULT_INDEX) pCF = pCFCurrent; // Current font can do it pccs->Release(); } } // Try default document format if (!pCF) { pCF = _ped->GetCharFormat(-1); if(iCharRep != pCF->_iCharRep) // Diff charset ids? pCF = NULL; } DO_SIZE: yHeight = pCFCurrent->_yHeight; // Assume current height if (!pCF) { // Default to table if no match. fr = W32->GetPreferredFontInfo( iCharRep, fUseUIFont, iFont, (BYTE&)yNewHeight, bPitchAndFamily ); if (!_ped->_fAutoFontSizeAdjust && iCharRep == THAI_INDEX) // Kick in font size adjusting in first bind to Thai. _ped->_fAutoFontSizeAdjust = TRUE; } if (pCF) { // Found previous or current font iFont = pCF->_iFont; bPitchAndFamily = pCF->_bPitchAndFamily; if (pCF == pCFCurrent && (iMatchCurrent & MATCH_FONT_SIG) && (IsFECharRep(pCF->_iCharRep) && W32->IsFECodePageFont(qwFontSigCurrent) || iMatch == MATCH_ASCII && iCharRep == ANSI_INDEX)) { // The current font matches the requested signature. // If it's a East Asia or ASCII font, we leave the charset intact. iCharRepRet = pCF->_iCharRep; return true; } } if (_ped->_fAutoFontSizeAdjust && iFont != pCFCurrent->_iFont) { if (IsValid()) { // If the last run format is available. We will scale the size relative to it. AdjustBackward(); if (GetIch() > 0) { pCFCurrent = _ped->GetCharFormat(GetFormat()); yHeight = pCFCurrent->_yHeight; } AdjustForward(); } if (iFont != pCFCurrent->_iFont) { // Scale the height relative to the preceding format if (pCF) yNewHeight = GetFontLegitimateSize(iFont, fUseUIFont, iCharRep); if (yNewHeight) { // Get legitimate size of current font SHORT yDefHeight = GetFontLegitimateSize(pCFCurrent->_iFont, fUseUIFont, pCFCurrent->_iCharRep); // Calculate the new height relative to the current height if (yDefHeight) { if (fUseUIFont) { // For UIFont, we only convert from one preferred size to another preferred size. if (pCFCurrent->_yHeight / TWIPS_PER_POINT == yDefHeight) yHeight = yNewHeight * TWIPS_PER_POINT; } else yHeight = (SHORT)MulDiv(pCFCurrent->_yHeight, yNewHeight, yDefHeight); } } } } if (!yHeight) yHeight = (SHORT)MulDiv(pCFCurrent->_yHeight, yNewHeight, 10); return pCF || fr; } /* * CCFRunPtr::FindUnhiddenBackward() * * @mfunc * Find nearest expanded CF going backward. If none, go to BOD * * @rdesc * cch to nearest expanded CF going backward * * @devnote * changes this run ptr */ LONG CCFRunPtr::FindUnhiddenBackward() { LONG cch = 0; AdjustBackward(); while(IsHidden()) { cch -= GetIch(); if(!_iRun) break; _ich = 0; AdjustBackward(); } return cch; } ///////////////////////////// CPFRunPtr /////////////////////////////// CPFRunPtr::CPFRunPtr(const CRchTxtPtr &rtp) : CFormatRunPtr(rtp._rpPF) { _ped = rtp.GetPed(); } /* * CPFRunPtr::FindHeading(cch, lHeading) * * @mfunc * Find heading with number lHeading (e.g., = 1 for Heading 1) or above * in a range starting at this PFrun pointer. If successful, this run * ptr points at the matching run; else it remains unchanged. * * @rdesc * cch to matching heading or tomBackward if not found * * @devnote * changes this run ptr */ LONG CPFRunPtr::FindHeading( LONG cch, //@parm Max cch to move LONG& lHeading) //@parm Lowest lHeading to match { LONG cchSave = cch; LONG ichSave = _ich; LONG iRunSave = _iRun; LONG OutlineLevel; Assert((unsigned)lHeading <= NHSTYLES); if(!IsValid()) return tomBackward; while(TRUE) { OutlineLevel = GetOutlineLevel(); if (!(OutlineLevel & 1) && (!lHeading || (lHeading - 1)*2 >= OutlineLevel)) { lHeading = OutlineLevel/2 + 1; // Return heading # found return cchSave - cch; // Return how far away it was } if(cch >= 0) { cch -= GetCchLeft(); if(cch <= 0 || !NextRun()) break; } else { cch += GetIch(); if(cch > 0 || !_iRun) break; AdjustBackward(); } } _ich = ichSave; _iRun = iRunSave; return tomBackward; // Didn't find desired heading } /* * CPFRunPtr::FindRowEnd(TableLevel) * * @mfunc * Advance this ptr just past table-row terminator that matches * the passed-in table level * * @rdesc * TRUE if matching table row end is found * * @devnote * changes this run ptr only if TableLevel is found within cch chars */ BOOL CPFRunPtr::FindRowEnd( LONG TableLevel) //@parm Table level to match { LONG ichSave = _ich; LONG iRunSave = _iRun; Assert(IsValid()); do { if(IsTableRowDelimiter() && GetPF()->_bTableLevel == (BYTE)TableLevel) { NextRun(); // Bypass delimiter return TRUE; } } while(NextRun()); _ich = ichSave; // Restore run ptr indices _iRun = iRunSave; return FALSE; // Didn't find desired heading } /* * CPFRunPtr::IsCollapsed() * * @mfunc * return TRUE if CParaFormat for this run ptr has PFE_COLLAPSED bit set * * @rdesc * TRUE if CParaFormat for this run ptr has PFE_COLLAPSED bit set */ BOOL CPFRunPtr::IsCollapsed() { return (_ped->GetParaFormat(GetFormat())->_wEffects & PFE_COLLAPSED) != 0; } /* * CPFRunPtr::IsTableRowDelimiter() * * @mfunc * return TRUE if CParaFormat for this run ptr has PFE_TABLEROWDELIMITER bit set * * @rdesc * TRUE if CParaFormat for this run ptr has PFE_TABLEROWDELIMITER bit set */ BOOL CPFRunPtr::IsTableRowDelimiter() { return (_ped->GetParaFormat(GetFormat())->_wEffects & PFE_TABLEROWDELIMITER) != 0; } /* * CPFRunPtr::InTable() * * @mfunc * return TRUE if CParaFormat for this run ptr has PFE_TABLE bit set * * @rdesc * TRUE if CParaFormat for this run ptr has PFE_TABLE bit set */ BOOL CPFRunPtr::InTable() { return (_ped->GetParaFormat(GetFormat())->_wEffects & PFE_TABLE) != 0; } /* * CPFRunPtr::FindExpanded() * * @mfunc * Find nearest expanded PF going forward. If none, find nearest going * backward. If none, go to start of document * * @rdesc * cch to nearest expanded PF as explained in function description * * @devnote * Moves this run ptr the amount returned (cch) */ LONG CPFRunPtr::FindExpanded() { LONG cch, cchRun; for(cch = 0; IsCollapsed(); cch += cchRun) // Try to find expanded PF { // run going forward cchRun = GetCchLeft(); if(!NextRun()) // Aren't any { Move(-cch); // Go back to starting point return FindExpandedBackward(); // Try to find expanded PF } // run going backward } return cch; } /* * CPFRunPtr::FindExpandedForward() * * @mfunc * Find nearest expanded PF going forward. If none, go to EOD * * @rdesc * cch to nearest expanded PF going forward * * @devnote * advances this run ptr the amount returned (cch) */ LONG CPFRunPtr::FindExpandedForward() { LONG cch = 0; while(IsCollapsed()) { LONG cchLeft = GetCchLeft(); _ich += cchLeft; // Update _ich in case cch += cchLeft; // if(!NextRun()) breaks if(!NextRun()) break; } return cch; } /* * CPFRunPtr::FindExpandedBackward() * * @mfunc * Find nearest expanded PF going backward. If none, go to BOD * * @rdesc * cch to nearest expanded PF going backward * * @devnote * Moves this run ptr the amount returned (cch) */ LONG CPFRunPtr::FindExpandedBackward() { LONG cch = 0; while(IsCollapsed()) { cch -= GetIch(); _ich = 0; if(!_iRun) break; AdjustBackward(); } return cch; } /* * CPFRunPtr::GetOutlineLevel() * * @mfunc * Find outline level this rp is pointing at * * @rdesc * Outline level this rp is pointing at */ LONG CPFRunPtr::GetOutlineLevel() { const CParaFormat *pPF = _ped->GetParaFormat(GetFormat()); LONG OutlineLevel = pPF->_bOutlineLevel; AssertSz(IsHeadingStyle(pPF->_sStyle) ^ (OutlineLevel & 1), "CPFRunPtr::GetOutlineLevel: sStyle/bOutlineLevel mismatch"); return OutlineLevel; } /* * CPFRunPtr::GetStyle() * * @mfunc * Find style this rp is pointing at * * @rdesc * Style this rp is pointing at */ LONG CPFRunPtr::GetStyle() { const CParaFormat *pPF = _ped->GetParaFormat(GetFormat()); LONG Style = pPF->_sStyle; AssertSz(IsHeadingStyle(Style) ^ (pPF->_bOutlineLevel & 1), "CPFRunPtr::GetStyle: sStyle/bOutlineLevel mismatch"); return Style; } /* * CPFRunPtr::ResolveRowStartPF() * * @mfunc * Resolve table row start PF corresponding to the current table row * end. Assumes that all table rows contained in the current row are * resolved, which should be the case for nested tables in RTF. * * @rdesc * TRUE iff success */ BOOL CPFRunPtr::ResolveRowStartPF() { AdjustBackward(); LONG iFormat = GetFormat(); Assert(IsTableRowDelimiter()); const CParaFormat *pPF = NULL; while(PrevRun()) { pPF = _ped->GetParaFormat(GetFormat()); if((pPF->_wEffects & PFE_TABLEROWDELIMITER) && pPF->_iTabs == -1) break; } Assert(IsTableRowDelimiter()); Assert(pPF->_iTabs == -1); CFormatRun* pRun = GetRun(0); IParaFormatCache *pf = GetParaFormatCache(); pf->Release(pRun->_iFormat); pf->AddRef(iFormat); pRun->_iFormat = iFormat; return TRUE; } /* * CPFRunPtr::GetMinTableLevel(cch) * * @mfunc * Get the lowest table level in the range of cch chars from this * run ptr. This is the lesser of the level ending at the range * cpMost and that starting at cpMin. Leave this run ptr at cpMin. * * @rdesc * Lowest table level in the cch chars from this run ptr */ LONG CPFRunPtr::GetMinTableLevel( LONG cch) //@parm cch to check for table level { if(cch > 0) AdjustBackward(); const CParaFormat *pPF = GetPF(); LONG Level = pPF->_bTableLevel; // Default: level at active end if(cch) { Move(-cch); // Go find table level at other pPF = GetPF(); // end of range if(pPF->_bTableLevel < Level) Level = pPF->_bTableLevel; if(cch < 0) // Range active end at cpMin Move(cch); // Start at cpMin } AssertSz(Level >= 0, "CPFRunPtr::GetMinTableLevel: invalid table level"); return Level; } /* * CPFRunPtr::GetTableLevel() * * @mfunc * Get table level this run ptr is at * * @rdesc * Table level this run ptr is at */ LONG CPFRunPtr::GetTableLevel() { const CParaFormat *pPF = _ped->GetParaFormat(GetFormat()); AssertSz(!(pPF->_wEffects & PFE_TABLE) || pPF->_bTableLevel > 0, "CPFRunPtr::GetTableLevel: invalid table level"); return pPF->_bTableLevel; }