/* * @doc * * @module - MEASURE.CPP * * CMeasurer class * * Authors: * Original RichEdit code: David R. Fulmer * Christian Fortini, Murray Sargent, Rick Sailor * * History: * KeithCu: Fixed zoom, restructured WYSIWYG, performance/cleanup * * Copyright (c) 1995-2000 Microsoft Corporation. All rights reserved. */ #include "_common.h" #include "_measure.h" #include "_font.h" #include "_disp.h" #include "_edit.h" #include "_frunptr.h" #include "_objmgr.h" #include "_coleobj.h" #include "_layout.h" #include "_uspi.h" ASSERTDATA void CMeasurer::Init(const CDisplay *pdp) { CTxtEdit * ped = GetPed(); _pdp = pdp; _pddReference = pdp; _pccs = NULL; _pPF = NULL; _plo = NULL; _dxBorderWidths = 0; _chPassword = ped->TxGetPasswordChar(); _wNumber = 0; _cchLine = 0; _ihyphPrev = 0; _fRenderer = FALSE; _fGlyphing = _fFallback = _fTarget = FALSE; _fMeasure = FALSE; _dvpWrapLeftRemaining = _dvpWrapRightRemaining = -1; if(pdp->GetWordWrap()) { const CDevDesc *pddTarget = pdp->GetTargetDev(); if(pddTarget) _pddReference = pddTarget; } _dvpInch = pdp->GetDypInch(); _dupInch = pdp->GetDxpInch(); if (pdp->IsMain()) { _dvpInch = MulDiv(_dvpInch, pdp->GetZoomNumerator(), pdp->GetZoomDenominator()); _dupInch = MulDiv(_dupInch, pdp->GetZoomNumerator(), pdp->GetZoomDenominator()); } if (pdp->SameDevice(_pddReference)) { _dvrInch = _dvpInch; _durInch = _dupInch; } else { _dvrInch = _pddReference->GetDypInch(); _durInch = _pddReference->GetDxpInch(); } //Set _dulLayout by default to be width for measuring; //In the table scenario, it will be set elsewhere. if(!_pdp->GetWordWrap()) _dulLayout = duMax; else if (_pdp->GetDulForTargetWrap()) _dulLayout = _pdp->GetDulForTargetWrap(); else _dulLayout = DUtoLU(_pdp->GetDupView()); } CMeasurer::CMeasurer (const CDisplay* const pdp) : CRchTxtPtr (pdp->GetPed()) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::CMeasurer"); Init(pdp); } CMeasurer::CMeasurer (const CDisplay* const pdp, const CRchTxtPtr &tp) : CRchTxtPtr (tp) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::CMeasurer"); Init(pdp); } CMeasurer::~CMeasurer() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::~CMeasurer"); if(_pccs) _pccs->Release(); } /* * CMeasurer::SetGlyphing(fGlyphing) * * @mfunc * A state flag inside the measurer to record whether or not you * are in the process of doing glyphing. If we are in a situation * where the _pddReference and the _pdp have different DCs, then we * need to throw away the pccs. */ void CMeasurer::SetGlyphing( BOOL fGlyphing) //@parm Currently doing glyphing { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::SetGlyphing"); Assert(fGlyphing == TRUE || fGlyphing == FALSE); if (fGlyphing != _fGlyphing) { if (_pddReference->_hdc != _pdp->_hdc) { if (_pccs) _pccs->Release(); _pccs = NULL; } _fGlyphing = fGlyphing; } } /* * CMeasurer::SetUseTargetDevice(fUseTargetDevice) * * @mfunc * Sets whether you want to use the target device or not * for getting metrics * FUTURE (keithcu) Make this a parameter */ void CMeasurer::SetUseTargetDevice( BOOL fUseTargetDevice) //@parm Use target device metrics? { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::SetUseTargetDevice"); Assert(fUseTargetDevice == TRUE || fUseTargetDevice == FALSE); if (fUseTargetDevice != _fTarget) { if (_dvpInch != _dvrInch || _dupInch != _durInch) { if (_pccs) _pccs->Release(); _pccs = NULL; } _fTarget = fUseTargetDevice; } } /* * CMeasurer::NewLine (fFirstInPara) * * @mfunc * Initialize this measurer at the start of a new line */ void CMeasurer::NewLine( BOOL fFirstInPara) //@parm Flag for setting up _fFirstInPara { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::NewLine"); _li.Init(); // Zero all members if(fFirstInPara) _li._fFirstInPara = TRUE; // Need to know if first in para _cchLine = 0; } /* * CMeasurer::NewLine(&li) * * @mfunc * Initialize this measurer at the start of a given line */ void CMeasurer::NewLine( const CLine &li) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::NewLine"); _li = li; _li._cch = 0; _li._dup = 0; // Can't calculate upStart till we get an HDC _li._upStart = 0; _wNumber = _li._bNumber; _cchLine = li._cch; } /* * CMeasurer::MeasureText (cch) * * @mfunc * Measure a stretch of text from current running position. * * If the user requests us to measure n characters, we measure n + 1. * and then subtract off the width of the last character. This gives * us proper value in _dupAddLast. * REVIEW (keithcu) This looks ugly. Think about it some more. * * @rdesc * width of text (in device units), < 0 if failed */ LONG CMeasurer::MeasureText( LONG cch) //@parm Number of characters to measure { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureText"); if(Measure(duMax, min(cch + 1, _cchLine), 0) == MRET_FAILED) return -1; if (cch < _cchLine) { _li._dup -= _dupAddLast; _li._cch--; } return _li._dup; } /* * CMeasurer::MeasureLine (dulMax, uiFlags, pliTarget) * * @mfunc * Measure a line of text from current cp and determine line break. * On return *this contains line metrics for _pddReference device. * * @rdesc * TRUE if success, FALSE if failed */ BOOL CMeasurer::MeasureLine( UINT uiFlags, //@parm Flags controlling the process (see Measure()) CLine *pliTarget) //@parm Returns target-device line metrics (optional) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureLine"); // This state must be preserved across the two possible line width // calculations so we save it here. BYTE bNumberSave = _li._bNumber; const CDevDesc *pddTarget = NULL; if(_pdp->GetWordWrap()) { // Target devices are only interesting if word wrap is on because the // only really interesting thing a target device can tell us is where // the word breaks will occur. pddTarget = _pdp->GetTargetDev(); if(pddTarget) SetUseTargetDevice(TRUE); } // Compute line break LONG lRet = Measure(-1, -1, uiFlags); // Stop here if failed if(lRet == MRET_FAILED) return FALSE; // Return target metrics if requested if(pliTarget) *pliTarget = _li; SetUseTargetDevice(FALSE); // Recompute to get metrics on rendering device if(pddTarget || lRet == MRET_NOWIDTH) { long cch = _li._cch; Move(-cch); // move back to BOL NewLine(uiFlags & MEASURE_FIRSTINPARA); // Restore the line number _li._bNumber = bNumberSave; lRet = Measure(duMax, cch, uiFlags); if(lRet) { Assert(lRet != MRET_NOWIDTH); return FALSE; } } // Now that we know the line width, compute line shift due // to alignment, and add it to the left position _li._upStart += MeasureLineShift(); return TRUE; } /* * CMeasurer::RecalcLineHeight (pccs, pCF) * * @mfunc * Reset height of line we are measuring if new run of text is taller * than current maximum in line. */ void CMeasurer::RecalcLineHeight( CCcs *pccs, const CCharFormat * const pCF) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::RecalcLineHeight"); // Compute line height LONG vpOffset, vpAdjust; pccs->GetOffset(pCF, _fTarget ? _dvrInch : _dvpInch, &vpOffset, &vpAdjust); if (GetPF()->_bLineSpacingRule == tomLineSpaceExactly) vpOffset = 0; LONG vpHeight = pccs->_yHeight; LONG vpDescent = pccs->_yDescent; SHORT yFEAdjust = pccs->AdjustFEHeight(FAdjustFELineHt()); if (yFEAdjust) { vpHeight += (yFEAdjust << 1); vpDescent += yFEAdjust; } LONG vpAscent = vpHeight - vpDescent; LONG vpAboveBase = max(vpAscent, vpAscent + vpOffset); LONG vpBelowBase = max(vpDescent, vpDescent - vpOffset); _li._dvpHeight = (SHORT)(max(vpAboveBase, _li._dvpHeight - _li._dvpDescent) + max(vpBelowBase, _li._dvpDescent)); _li._dvpDescent = (SHORT)max(vpBelowBase, _li._dvpDescent); } /* * CMeasurer::Measure (dulMax, cchMax, uiFlags) * * @mfunc * Measure given amount of text, start at current running position * and storing # chars measured in _cch. * Can optionally determine line break based on a dulMax and * break out at that point. * * @rdesc * 0 success * MRET_FAILED if failed * MRET_NOWIDTH if second pass is needed to compute correct width * * @devnote * The uiFlags parameter has the following meanings: * MEASURE_FIRSTINPARA this is first line of paragraph * MEASURE_BREAKATWORD break out on a word break * MEASURE_BREAKBEFOREWIDTH break before dulMax * * The calling chain must be protected by a CLock, since this present * routine access the global (shared) FontCache facility. */ LONG CMeasurer::Measure( LONG dulMax, //@parm Max width of line in logical units (-1 uses CDisplay width) LONG cchMax, //@parm Max chars to process (-1 if no limit) UINT uiFlags) //@parm Flags controlling the process (see above) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::Measure"); LONG cch; // cchChunk count down LONG cchChunk; // cch of cst-format contiguous run LONG cchNonWhite; // cch of last nonwhite char in line LONG cchText = GetTextLength(); WCHAR ch; // Temporary char BOOL fFirstInPara = uiFlags & MEASURE_FIRSTINPARA; BOOL fLastChObj = FALSE; LONG lRet = 0; const WCHAR*pch; CTxtEdit * ped = GetPed(); COleObject *pobj; LONG dupMax; LONG uAdd = 0; // Character width LONG dupSoftHyphen = 0; // Most recent soft hyphen width LONG dupNonWhite; // dup for last nonwhite char in line // This variable is used to keep track of whether there is a height change // so that we know whether we need to recalc the line in certain line break cases. BOOL fHeightChange = FALSE; const INT MAX_SAVED_WIDTHS = 31; // power of 2 - 1 INT i, index, iSavedWidths = 0; struct { SHORT width; SHORT vpHeight; SHORT vpDescent; } savedWidths[MAX_SAVED_WIDTHS+1]; _pPF = GetPF(); // Be sure current CParaFormat // ptr is up to date Assert(_li._cch == 0); // Init fliFirstInPara flag for new line if(fFirstInPara) { _li._fFirstInPara = TRUE; if(IsInOutlineView() && IsHeadingStyle(_pPF->_sStyle)) _li._dvpHeight = (short)max(_li._dvpHeight, BITMAP_HEIGHT_HEADING + 1); } AssertSz(!_pPF->IsListNumbered() && !_wNumber || (uiFlags & MEASURE_BREAKBEFOREWIDTH) || !_pdp->IsMultiLine() || _wNumber > 20 || _wNumber == (i = GetParaNumber()), "CMeasurer::Measure: incorrect list number"); _li._upStart = MeasureLeftIndent(); // Set left indent // Compute width to break out at if(dulMax < 0) dupMax = LUtoDU(_dulLayout); else if (dulMax != duMax) dupMax = LUtoDU(dulMax); else dupMax = duMax; //If we are told to measure a fixed width (as in CchFromUp) then ignore //affect of left and right indent. if (dulMax < 0) { LONG uCaretT = (_pdp->IsMain() && !GetPed()->TxGetReadOnly()) ? ped->GetCaretWidth() : 0; dupMax -= (MeasureRightIndent() + _li._upStart + uCaretT); } dupMax = max(dupMax, 0); // Compute max count of characters to process cch = cchText - GetCp(); if(cchMax < 0 || cchMax > cch) cchMax = cch; cchNonWhite = _li._cch; // Default nonwhite parms dupNonWhite = _li._dup; for( ; cchMax > 0; // Measure up to cchMax cchMax -= cchChunk, Move(cchChunk)) // chars { pch = GetPch(cch); cch = min(cch, cchMax); // Compute constant-format cchChunk = GetCchLeftRunCF(); cch = min(cch, cchChunk); // Counter for next while cchChunk = cch; // Save chunk size const CCharFormat *pCF = GetCF(); DWORD dwEffects = pCF->_dwEffects; if(dwEffects & CFE_HIDDEN) // Ignore hidden text { uAdd = 0; _li._cch += cchChunk; continue; } if(!Check_pccs()) // Be sure _pccs is current return MRET_FAILED; // Adjust line height for new format run if(cch > 0 && *pch && (IsRich() || ped->HasObjects())) { // Note: the EOP only contributes to the height calculation for the // line if there are no non-white space characters on the line or // the paragraph is a bullet paragraph. The bullet paragraph // contribution to the line height is done in AdjustLineHeight. // REVIEW (Victork) // Another, similar topic is height of spaces. // They don't (normally) influence line height in LS, // they do in CMeasurer::Measure code. // Proposed ways to solve it: // - have fSpacesOnly flag in run // - move current (line height) logic down after next character-scanning loop if(!cchNonWhite || *pch != CR && *pch != LF) { // Determine if the current run is the tallest text on this // line and if so, increase the height of the line. LONG vpHeightOld = _li._dvpHeight; RecalcLineHeight(_pccs, pCF); // Test for a change in line height. This only happens when // this is not the first character in the line and (surprise) // the height changes. if (vpHeightOld && vpHeightOld != _li._dvpHeight) fHeightChange = TRUE; } } while(cch > 0) { // Process next char uAdd = 0; // Default zero width ch = *pch; if(_chPassword && !IN_RANGE(LF, ch, CR)) ch = _chPassword; if(dwEffects & CFE_ALLCAPS) CharUpperBuff(&ch, 1); if(ch == WCH_EMBEDDING) { _li._fHasSpecialChars = TRUE; pobj = GetObjectFromCp(GetCp() + cchChunk - cch); if(pobj) { LONG vpAscent, vpDescent; pobj->MeasureObj(_fTarget ? _dvrInch : _dvpInch, _fTarget ? _durInch : _dupInch, uAdd, vpAscent, vpDescent, _li._dvpDescent, GetTflow()); // Only update height for line if the object is going // to be on this line. if(!_li._cch || _li._dup + uAdd <= dupMax) { if (vpAscent > _li._dvpHeight - _li._dvpDescent) _li._dvpHeight = vpAscent + _li._dvpDescent; } } if(_li._dup + uAdd > dupMax) fLastChObj = TRUE; } // The following if succeeds if ch isn't a CELL, BS, TAB, LF, // VT, FF, or CR else if(!IN_RANGE(CELL, ch, CR)) // Not TAB or EOP { // Get char width if (!_pccs->Include(ch, uAdd)) { AssertSz(FALSE, "CMeasurer::Measure char not in font"); return MRET_FAILED; } if(IN_RANGE(NBSPACE, ch, EURO)) // Rules out ASCII, CJK { switch(ch) // char for NBSPACE & { // special hyphens case EURO: case NBHYPHEN: case SOFTHYPHEN: case NBSPACE: case EMSPACE: case ENSPACE: _li._fHasSpecialChars = TRUE; if (ch == SOFTHYPHEN && (_li._dup + uAdd < dupMax || !_li._cch)) { dupSoftHyphen = uAdd; // Save soft hyphen width uAdd = 0; // Use 0 unless at EOL } break; } } else if(_chPassword && IN_RANGE(0xDC00, *pch, 0xDFFF)) uAdd = 0; } else if(ch == TAB) { _li._fHasSpecialChars = TRUE; uAdd = MeasureTab(ch); } else if(ch == FF && ped->Get10Mode()) // RichEdit 1.0 treats _pccs->Include(ch, uAdd); // FFs as normal chars else // Done with line goto eop; // Go process EOP chars index = iSavedWidths++ & MAX_SAVED_WIDTHS; savedWidths[index].width = (SHORT)uAdd; savedWidths[index].vpHeight = _li._dvpHeight; savedWidths[index].vpDescent = _li._dvpDescent; _li._dup += uAdd; if(_li._dup > dupMax && (uiFlags & MEASURE_BREAKBEFOREWIDTH || _li._cch > 0)) goto overflow; _li._cch++; pch++; cch--; if(ch != ' ') // If not whitespace char, { cchNonWhite = _li._cch; // update nonwhitespace dupNonWhite = _li._dup; // count and width } } // while(cch > 0) } // for(;cchMax > 0;...) goto eol; // All text exhausted // End Of Paragraph char encountered (CR, LF, VT, or FF, but mostly CR) eop: Move(cchChunk - cch); // Position tp at EOP cch = AdvanceCRLF(); // Bypass possibly multibyte EOP _li._cchEOP = (BYTE)cch; // Store EOP cch _li._cch += cch; // Increment line count if(ch == CR || ped->fUseCRLF() && ch == LF || ch == CELL) _li._fHasEOP = TRUE; AssertSz(ped->fUseCRLF() || cch == 1, "CMeasurer::Measure: EOP isn't a single char"); AssertSz(_pdp->IsMultiLine() || GetCp() == cchText, "CMeasurer::Measure: EOP in single-line control"); eol: // End of current line if(uiFlags & MEASURE_BREAKATWORD) // Compute count of whitespace _li._dup = dupNonWhite; // chars at EOL goto done; overflow: // Went past max width for line _li._dup -= uAdd; --iSavedWidths; Move(cchChunk - cch); // Position *this at overflow // position if(uiFlags & MEASURE_BREAKATWORD) // If required, adjust break on { // word boundary // We should not have the EOP flag set here. The case to watch out // for is when we reuse a line that used to have an EOP. It is the // responsibility of the measurer to clear this flag as appropriate. Assert(_li._cchEOP == 0); _li._cchEOP = 0; // Just in case if(ch == TAB) { // If the last character measured is a tab, leave it on the // next line to allow tabbing off the end of line as in Word goto done; } LONG cpStop = GetCp(); // Remember current cp cch = -FindWordBreak(WB_LEFTBREAK, _li._cch+1); if(cch == 0 && fLastChObj) // If preceding char is an goto done; // object, put current char // on next line Assert(cch >= 0); if(cch + 1 < _li._cch) // Break char not at BOL { ch = _rpTX.GetPrevChar(); if (ch == TAB) // If break char is a TAB, { // put it on the next line cch++; // as in Word Move(-1); } else if(ch == SOFTHYPHEN) _li._dup += dupSoftHyphen; _li._cch -= cch; } else if(cch == _li._cch && cch > 1 && _rpTX.GetChar() == ' ') // Blanks all the way back to { // BOL. Bypass first blank Move(1); cch--; _li._cch = 1; } else // Advance forward to end of SetCp(cpStop); // measurement Assert(_li._cch > 0); // Now search at start of word to figure how many white chars at EOL LONG cchWhite = 0; if(GetCp() < cchText) { pch = GetPch(cch); cch = 0; if(ped->TxWordBreakProc((WCHAR *)pch, 0, sizeof(WCHAR), WB_ISDELIMITER, GetCp())) { cch = FindWordBreak(WB_RIGHT); Assert(cch >= 0); } cchWhite = cch; _li._cch += cch; ch = GetChar(); if(IN_RANGE(CELL, ch, CR) && !IN_RANGE(8, ch, TAB)) // skip *only* 1 EOP -jOn { if(ch == CR || ch == CELL) _li._fHasEOP = TRUE; _li._cchEOP = (BYTE)AdvanceCRLF(); _li._cch += _li._cchEOP; goto done; } } i = cpStop - GetCp(); if(i) { if(i > 0) i += cchWhite; if(i > 0 && i < iSavedWidths && i < MAX_SAVED_WIDTHS) { while (i-- > 0) { iSavedWidths = (iSavedWidths - 1) & MAX_SAVED_WIDTHS; _li._dup -= savedWidths[iSavedWidths].width; } iSavedWidths = (iSavedWidths - 1) & MAX_SAVED_WIDTHS; _li._dvpHeight = savedWidths[iSavedWidths].vpHeight; _li._dvpDescent = savedWidths[iSavedWidths].vpDescent; } else { // Need to recompute width from scratch. lRet = MRET_NOWIDTH; } } else { // i == 0 means that we are breaking on the first letter in a word. // Therefore, we want to set the width to the total non-white space // calculated so far because that does not include the size of the // character that caused the break nor any of the white space // preceeding the character that caused the break. if(!fHeightChange) _li._dup = dupNonWhite; else { // Need to recompute from scratch so that we can get the // correct height for the control lRet = MRET_NOWIDTH; } } } done: _dupAddLast = uAdd; if(!_li._dvpHeight) // If no height yet, use CheckLineHeight(); // default height AdjustLineHeight(); return lRet; } /* * CMeasurer::UpdateWrapState(dvpLine, dvpDescent, fLeft) * * @mfunc * After formatting a line, update the current state of wrapped objects */ void CMeasurer::UpdateWrapState( USHORT &dvpLine, USHORT &dvpDescent, BOOL fLeft) { if (fLeft && _li._cObjectWrapLeft || !fLeft && _li._cObjectWrapRight) { COleObject *pobj = FindFirstWrapObj(fLeft); LONG & dvpWrapRemaining = fLeft ? _dvpWrapLeftRemaining : _dvpWrapRightRemaining; if (dvpWrapRemaining == -1) { if (fLeft) _li._fFirstWrapLeft = 1; else _li._fFirstWrapRight = 1; LONG dup, dvpAscent, dvpDescent; pobj->MeasureObj(_dvpInch, _dupInch, dup, dvpAscent, dvpDescent, 0, GetTflow()); dvpWrapRemaining = dvpAscent + dvpDescent; } if (_li._fHasEOP && (_pPF->_wEffects & PFE_TEXTWRAPPINGBREAK)) { LONG dvpRemaining = dvpWrapRemaining - dvpLine; if (dvpRemaining > 0) { dvpLine += dvpRemaining; dvpDescent += dvpRemaining; } } dvpWrapRemaining -= dvpLine; if (dvpWrapRemaining <= 0) { dvpWrapRemaining = -1; RemoveFirstWrap(fLeft); } } } /* * CMeasurer::UpdateWrapState (&dvpLine, &dvpDescent) * * @mfunc * Update object wrap state */ void CMeasurer::UpdateWrapState( USHORT &dvpLine, USHORT &dvpDescent) { //If we are wrapping around an object, update dvpWrapUsed values //and remove objects from queue if they have been used up. if (IsMeasure() && _rgpobjWrap.Count()) { UpdateWrapState(dvpLine, dvpDescent, TRUE); UpdateWrapState(dvpLine, dvpDescent, FALSE); } } /* * CMeasurer::GetCcsFontFallback (pCF) * * @mfunc * Create the fallback font cache for given CF * * @rdesc * CCcs corresponding to font fallback given by pCF */ CCcs* CMeasurer::GetCcsFontFallback ( const CCharFormat *pCF, WORD wScript) { CCharFormat CF = *pCF; CCcs* pccs = NULL; SHORT iDefHeight; CTxtEdit* ped = GetPed(); BYTE bCharRep = CF._iCharRep; #ifndef NOCOMPLEXSCRIPTS CUniscribe *pusp = ped->Getusp(); if (pusp && wScript != 0) { pusp->GetComplexCharRep(pusp->GeteProp(wScript), ped->GetCharFormat(-1)->_iCharRep, bCharRep); } #endif bool fr = W32->GetPreferredFontInfo(bCharRep, ped->fUseUIFont() ? true : false, CF._iFont, (BYTE&)iDefHeight, CF._bPitchAndFamily); if (fr) { CF._iCharRep = bCharRep; pccs = GetCcs(&CF); // Create fallback font cache entry } return pccs; } /* * CMeasurer::ApplyFontCache (fFallback, wScript) * * @mfunc * Apply a new font cache on the fly (leave backing store intact) * * @rdesc * CCcs corresponding to font fallback if fFallback; else to GetCF() */ CCcs* CMeasurer::ApplyFontCache ( BOOL fFallback, WORD wScript) { if (_fFallback ^ fFallback) { CCcs* pccs = fFallback ? GetCcsFontFallback(GetCF(), wScript) : GetCcs(GetCF()); if (pccs) { if (_pccs) _pccs->Release(); _pccs = pccs; _fFallback = fFallback; } } return _pccs; } /* * CMeasurer::GetCcs (pCF) * * @mfunc * Wrapper around font cache's GetCCcs function * We use a NULL DC unless the device is a printer. * * @rdesc * CCcs corresponding to pCF */ CCcs* CMeasurer::GetCcs( const CCharFormat *pCF) { HDC hdc = NULL; if (_fTarget) { if (_pddReference->_hdc && GetDeviceCaps(_pddReference->_hdc, TECHNOLOGY) == DT_RASPRINTER) hdc = _pddReference->_hdc; } else if (_pdp->_hdc && GetDeviceCaps(_pdp->_hdc, TECHNOLOGY) == DT_RASPRINTER) hdc = _pdp->_hdc; DWORD dwFlags = GetTflow(); if (_fGlyphing && _pdp->_hdc != _pddReference->_hdc) dwFlags |= FGCCSUSETRUETYPE; if(GetPasswordChar()) pCF = GetPed()->GetCharFormat(-1); return GetPed()->GetCcs(pCF, _fTarget ? _dvrInch : _dvpInch, dwFlags, hdc); } /* * CMeasurer::CheckLineHeight() * * @mfunc * If no height yet, use default height */ void CMeasurer::CheckLineHeight() { CCcs *pccs = GetCcs(GetPed()->GetCharFormat(-1)); _li._dvpHeight = pccs->_yHeight; _li._dvpDescent = pccs->_yDescent; SHORT yFEAdjust = pccs->AdjustFEHeight(FAdjustFELineHt()); if (yFEAdjust) { _li._dvpHeight += (yFEAdjust << 1); _li._dvpDescent += yFEAdjust; } pccs->Release(); } /* * CMeasurer::Check_pccs(fBullet) * * @mfunc * Check if new character format run or whether we don't yet have a font * * @rdesc * Current CCcs * * * @devnote * The calling chain must be protected by a CLock, since this present * routine access the global (shared) FontCache facility. */ CCcs *CMeasurer::Check_pccs( BOOL fBullet) { if(fBullet) { if(_pccs) // Release old Format cache _pccs->Release(); _pccs = GetCcsBullet(NULL); _iFormat = -10; // Be sure to reset font next time return _pccs; } const CCharFormat *pCF = GetCF(); if(FormatIsChanged()) { // New CF run or format for this line not yet initialized ResetCachediFormat(); if(_pccs) // Release old Format cache _pccs->Release(); _pccs = GetCcs(pCF); _fFallback = 0; if(!_pccs) { //FUTURE (keithcu) If this fails, just dig up the first pccs you can find AssertSz(FALSE, "CMeasurer::Measure could not get _pccs"); return NULL; } } return _pccs; } /* * CMeasurer::AdjustLineHeight() * * @mfunc * Adjust for space before/after and line spacing rules. * No effect for plain text. * * @future * Base multiple line height calculations on largest font height rather * than on line height (_vpHeight), since the latter may be unduly large * due to embedded objects. Word does this correctly. */ void CMeasurer::AdjustLineHeight() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::AdjustLineHeight"); if(!IsRich() || IsInOutlineView()) // Plain text and outline mode return; // don't use special line // spacings const CParaFormat * pPF = _pPF; DWORD dwRule = pPF->_bLineSpacingRule; LONG dvpAfter = 0; // Default no space after LONG dvpBefore = 0; // Default no space before LONG dvpSpacing = pPF->_dyLineSpacing; LONG vpHeight = LVtoDV(dvpSpacing); LONG vpAscent = _li._dvpHeight - _li._dvpDescent; if(_li._fFirstInPara) dvpBefore = LVtoDV(pPF->_dySpaceBefore); // Space before paragraph AssertSz(dvpBefore >= 0, "CMeasurer::AdjustLineHeight - bogus value for dvpBefore"); if(vpHeight < 0) // Negative heights mean use _li._dvpHeight = (SHORT)(-vpHeight); // the magnitude exactly else if(dwRule) // Line spacing rule is active { switch (dwRule) { case tomLineSpace1pt5: dvpAfter = _li._dvpHeight >> 1; // Half-line space after break; // (per line) case tomLineSpaceDouble: dvpAfter = _li._dvpHeight; // Full-line space after break; // (per line) case tomLineSpaceAtLeast: if(_li._dvpHeight >= vpHeight) break; // Fall thru to space exactly case tomLineSpaceExactly: _li._dvpHeight = (SHORT)max(vpHeight, 1); break; case tomLineSpaceMultiple: // Multiple-line space after // Prevent dvpAfter from being negative because dvpSpacing is small - a-rsail if (dvpSpacing < 20) dvpSpacing = 20; dvpAfter = (_li._dvpHeight*dvpSpacing)/20 // (20 units per line) - _li._dvpHeight; } } if(_li._fHasEOP) dvpAfter += LVtoDV(pPF->_dySpaceAfter); // Space after paragraph end // Add in space before/after if (dvpAfter < 0) { // Overflow - since we forced dvpSpacing to 20 above, the // only reason for a negative is overflow. In case of overflow, // we simply force the value to the max and then fix the // other resulting overflows. dvpAfter = LONG_MAX; } AssertSz((dvpBefore >= 0), "CMeasurer::AdjustLineHeight - invalid before"); _li._dvpHeight = (SHORT)(_li._dvpHeight + dvpBefore + dvpAfter); if (_li._dvpHeight < 0) { // Overflow! // The reason for the -2 is then we don't have to worry about // overflow in the table check. _li._dvpHeight = SHRT_MAX - 2; } _li._dvpDescent = (SHORT)(_li._dvpDescent + dvpAfter); if (_li._dvpDescent < 0) { // Overflow in descent AssertSz(_li._dvpHeight == SHRT_MAX - 2, "Descent overflowed when height didn't"); // Allow old ascent _li._dvpDescent = SHRT_MAX - 2 - vpAscent; AssertSz(_li._dvpDescent >= 0, "descent adjustment < 0"); } AssertSz((_li._dvpHeight >= 0) && (_li._dvpDescent >= 0), "CMeasurer::AdjustLineHeight - invalid line heights"); } /* * CMeasurer::GetPBorderWidth (dxlLine) * * @mfunc * Convert logical width to device width and ensure that * device width is at least 1 pixel if logical width is nonzero. * * @rdesc * Device width of border */ LONG CMeasurer::GetPBorderWidth( LONG dxlLine) //@parm Logical border width { dxlLine &= 0xFF; LONG dxpLine = LUtoDU(dxlLine); if(dxlLine) dxpLine = max(dxpLine, 1); return dxpLine; } /* * CMeasurer::MeasureLeftIndent() * * @mfunc * Compute and return left indent of line in device units * * @rdesc * Left indent of line in device units * * @comm * Plain text is sensitive to StartIndent and RightIndent settings, * but usually these are zero for plain text. */ LONG CMeasurer::MeasureLeftIndent() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureLeftIndent"); AssertSz(_pPF != NULL, "CMeasurer::MeasureLeftIndent _pPF not set!"); LONG ulLeft = _pPF->_dxStartIndent; // Use logical units // up to return if(IsRich()) { LONG dulOffset = _pPF->_dxOffset; BOOL fFirstInPara = _li._fFirstInPara; if(IsInOutlineView()) { ulLeft = lDefaultTab/2 * (_pPF->_bOutlineLevel + 1); if(!fFirstInPara) dulOffset = 0; } if(fFirstInPara) { if(_pPF->_wNumbering && !_pPF->IsNumberSuppressed()) { // Add offset to text on first line if(_pPF->_wNumberingTab) // If _wNumberingTab != 0, dulOffset = _pPF->_wNumberingTab;// use it LONG Alignment = _pPF->_wNumberingStyle & 3; if(Alignment != tomAlignRight) { LONG du = DUtoLU(MeasureBullet()); if(Alignment == tomAlignCenter) du /= 2; dulOffset = max(du, dulOffset); // Use max of bullet and } } // offset else dulOffset = 0; } ulLeft += dulOffset; } return (ulLeft <= 0) ? 0 : LUtoDU(ulLeft); } /* * CMeasurer::HitTest(x) * * @mfunc * Return HITTEST for displacement x in this line. Can't be specific * about text area (_upStart to _upStart + _dupLineMax), since need to measure * to get appropriate cp (done elsewhere) * * @rdesc * HITTEST for a displacement x in this line */ HITTEST CMeasurer::HitTest( LONG x) //@parm Displacement to test hit { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::HitTest"); UpdatePF(); LONG u = UFromX(x); if(u < 0) return HT_LeftOfText; // For RightOfText, allow for a little "hit space" of _li.GetHeight() to // allow user to select EOP at end of line if (u > _li._upStart + _li._dup + _li.GetHeight() && GetPed()->GetSelMin() == GetCp() + _li._cch - _li._cchEOP) { return HT_RightOfText; } if(u >= _li._upStart) // Caller can refine this return HT_Text; // with CLine::CchFromUp() if(IsRich() && _li._fFirstInPara) { LONG dup; if(_pPF->_wNumbering) { // Doesn't handle case where Bullet is wider than following dx dup = LUtoDU(max(_pPF->_dxOffset, _pPF->_wNumberingTab)); if(u >= _li._upStart - dup) return HT_BulletArea; } if(IsInOutlineView()) { dup = LUtoDU(lDefaultTab/2 * _pPF->_bOutlineLevel); if(u >= dup && u < dup + (_pPF->_bOutlineLevel & 1 ? LUtoDU(lDefaultTab/2) : _pdp->Zoom(BITMAP_WIDTH_HEADING))) { return HT_OutlineSymbol; } } } return HT_LeftOfText; } /* * CMeasurer::MeasureRightIndent() * * @mfunc * Compute and return right indent of line in device units * * @rdesc * Right indent of line in device units * * @comm * Plain text is sensitive to StartIndent and RightIndent settings, * but usually these are zero for plain text. */ LONG CMeasurer::MeasureRightIndent() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureRightIndent"); LONG dulRight = _pPF->_dxRightIndent; _upRight = LUtoDU(max(dulRight, 0)); return _upRight; } /* * CMeasurer::MeasureTab() * * @mfunc * Computes and returns the width from the current position to the * next tab stop (in device units). * * @rdesc * Width from current position to next tab stop */ LONG CMeasurer::MeasureTab( unsigned ch) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureTab"); LONG uCur = _li._dup + MeasureLeftIndent(); const CParaFormat * pPF = _pPF; LONG cTab = pPF->_bTabCount; LONG duDefaultTab = lDefaultTab; LONG duIndent = LUtoDU(pPF->_dxStartIndent + pPF->_dxOffset); LONG duOffset = pPF->_dxOffset; LONG duOutline = 0; LONG h = 0; LONG uT; LONG uTab; AssertSz(cTab >= 0 || cTab <= MAX_TAB_STOPS, "Illegal tab count"); if(IsInOutlineView()) duOutline = lDefaultTab/2 * (pPF->_bOutlineLevel + 1); if(cTab) { const LONG *pl = pPF->GetTabs(); for(uTab = 0; cTab--; pl++) // Try explicit tab stops 1st { uT = GetTabPos(*pl) + duOutline; // (2 most significant nibbles if(uT > _dulLayout) // Ignore tabs wider than layout area break; //REVIEW (keithcu) This is not proper hungarian uT = LUtoDU(uT); // are for type/style) if(uT + h > uCur) // Allow text in table cell to { // move into cell gap (h > 0) if(duOffset > 0 && uT < duIndent)// Explicit tab in a hanging return uT - uCur; // indent takes precedence uTab = uT; break; } } if(duOffset > 0 && uCur < duIndent) // If no tab before hanging return duIndent - uCur; // indent, tab to indent if(uTab) // Else use tab position return uTab - uCur; } duDefaultTab = GetTabPos(GetPed()->GetDefaultTab()); AssertSz(duDefaultTab > 0, "CMeasurer::MeasureTab: Default tab is bad"); duDefaultTab = LUtoDU(duDefaultTab); duDefaultTab = max(duDefaultTab, 1); // Don't ever divide by 0 return duDefaultTab - uCur%duDefaultTab; // Round up to nearest } /* * CMeasurer::MeasureLineShift () * * @mfunc * Computes and returns the line u shift due to alignment * * @rdesc * Line u shift due to alignment * * @comm * Plain text is sensitive to StartIndent and RightIndent settings, * but usually these are zero for plain text. */ LONG CMeasurer::MeasureLineShift() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureLineShift"); WORD wAlignment = _pPF->_bAlignment; LONG uShift; LONG dup; CTxtEdit * ped = GetPed(); if(IsInOutlineView() || !IN_RANGE(PFA_RIGHT, wAlignment, PFA_CENTER)) return 0; if(!_pdp->GetWordWrap()) dup = _pdp->GetDupView(); else dup = LUtoDU(_dulLayout); // Normal view with center or flush-right para. Move right accordingly uShift = dup - _li._upStart - MeasureRightIndent() - _li._dup; uShift -= ped->GetCaretWidth(); uShift = max(uShift, 0); // Don't allow alignment to go < 0 // Can happen with a target device if(wAlignment == PFA_CENTER) uShift /= 2; return uShift; } /* * CMeasurer::MeasureBullet() * * @mfunc * Computes bullet/numbering dimensions * * @rdesc * Return bullet/numbering string width */ LONG CMeasurer::MeasureBullet() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::MeasureBullet"); CCharFormat CF; CCcs *pccs = GetCcsBullet(&CF); LONG dup = 0; if(pccs) { WCHAR szBullet[CCHMAXNUMTOSTR]; GetBullet(szBullet, pccs, &dup); RecalcLineHeight(pccs, &CF); pccs->Release(); } return dup; } /* * CMeasurer::GetBullet(pch, pccs, pdup) * * @mfunc * Computes bullet/numbering string, string length, and width * * @rdesc * Return bullet/numbering string length */ LONG CMeasurer::GetBullet( WCHAR *pch, //@parm Bullet string to receive bullet text CCcs *pccs, //@parm CCcs to use LONG *pdup) //@parm Out parm for bullet width { Assert(pccs && pch); LONG cch = _pPF->NumToStr(pch, _li._bNumber); LONG dupChar; LONG i; LONG dup = 0; pch[cch++] = ' '; // Ensure a little extra space for(i = cch; i--; dup += dupChar) { if(!pccs->Include(*pch++, dupChar)) { TRACEERRSZSC("CMeasurer::GetBullet(): Error filling CCcs", E_FAIL); } } if(pdup) *pdup = dup; return cch; } /* * CMeasurer::GetCcsBullet(pCFRet) * * @mfunc * Get CCcs for numbering/bullet font. If bullet is suppressed because * this isn't the beginning of a paragraph (e.g., previous character is * VT or if GetCcs() fails, it returns NULL. * * @rdesc * ptr to bullet CCcs, or NULL (GetCcs() failed or not start of para) * * @devnote * The calling chain must be protected by a CLock, since this present * routine access the global (shared) FontCache facility. */ CCcs * CMeasurer::GetCcsBullet( CCharFormat *pCFRet) //@parm option character format to return { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CMeasurer::GetCcsBullet"); if(!_li._fFirstInPara) return NULL; // Number/bullet suppressed CCharFormat CF; CCcs * pccs; const CCharFormat * pCF; CCharFormat * pCFUsed = pCFRet ? pCFRet : &CF; // Bullet CF is given by that for EOP in bullet's paragraph. CTxtPtr tp(_rpTX); CFormatRunPtr rpCF(_rpCF); rpCF.Move(tp.FindEOP(tomForward)); rpCF.AdjustBackward(); pCF = GetPed()->GetCharFormat(rpCF.GetFormat()); // Construct bullet (or numbering) CCharFormat *pCFUsed = *pCF; if(_pPF->_wNumbering == PFN_BULLET) // Traditional bullet uses { // Symbol font bullet, but... pCFUsed->_iCharRep = SYMBOL_INDEX, pCFUsed->_bPitchAndFamily = FF_DONTCARE; pCFUsed->_iFont = IFONT_SYMBOL; } // Since we always cook up bullet character format, no need to cache it pccs = GetCcs(pCFUsed); #if DEBUG if(!pccs) { TRACEERRSZSC("CMeasurer::GetCcsBullet(): no CCcs", E_FAIL); } #endif // DEBUG return pccs; } /* * CMeasurer::SetNumber(wNumber) * * @mfunc * Store number if numbered paragraph */ void CMeasurer::SetNumber( WORD wNumber) { _pPF = GetPF(); if(!_pPF->IsListNumbered()) wNumber = 0; else if (!wNumber && !_pPF->IsNumberSuppressed()) wNumber = 1; _wNumber = wNumber; } /* * CMeasurer::FindCpDraw(cpStart, cobjectPrev, fLeft) * * @mfunc * Find the cp corresponding to the nth previous object to be placed. * (If a line stores a 2 in the _cObjectWrapLeft for example, it means * you need to walk backwards 2 objects to find the object to be drawn * on this line.) * * @rdesc * cp corresponding to the nth previous object */ LONG CMeasurer::FindCpDraw( LONG cpStart, int cobjectPrev, BOOL fLeft) { LONG cch = 0; LONG cObjects = -1; while (cobjectPrev > 0) { // BUGBUG: this test should really be after the CountObjects() call, // but we are making a change with minimal impact just before // a major release. if (!cObjects) return tomForward; cch += GetPed()->GetObjectMgr()->CountObjects(cObjects, cpStart + cch); COleObject *pobj = GetObjectFromCp(cpStart + cch); if (!pobj) return tomForward; if (pobj->FWrapTextAround() && pobj->FAlignToRight() == !fLeft) cobjectPrev--; } return cpStart + cch; } /* * CMeasurer::AddObjectToQueue(pobjAdd) * * @mfunc * After formatting a line, update the current state of wrapped objects */ void CMeasurer::AddObjectToQueue( COleObject *pobjAdd) { if (!IsMeasure()) return; for (int iobj = 0; iobj < _rgpobjWrap.Count(); iobj++) { COleObject *pobj = _rgpobjWrap.GetAt(iobj); if (pobj == pobjAdd) return; } COleObject **ppobj = _rgpobjWrap.Add(1, 0); *ppobj = pobjAdd; } /* * CMeasurer::CountQueueEntries(fLeft) * * @mfunc * Return count of objects queued up * * @rdesc * Count of objects queued up */ int CMeasurer::CountQueueEntries( BOOL fLeft) { int cEntries = 0; for (int iobj = 0; iobj < _rgpobjWrap.Count(); iobj++) { COleObject *pobj = _rgpobjWrap.GetAt(iobj); if (!pobj->FAlignToRight() == fLeft) cEntries++; } return cEntries; } /* * CMeasurer::RemoveFirstWrap(fLeft) * * @mfunc * Remove the object from the queue--after it * has been been placed. */ void CMeasurer::RemoveFirstWrap( BOOL fLeft) { for (int iobj = 0; iobj < _rgpobjWrap.Count(); iobj++) { COleObject *pobj = _rgpobjWrap.GetAt(iobj); if (!pobj->FAlignToRight() == fLeft) { _rgpobjWrap.Remove(iobj, 1); return; } } } /* * CMeasurer::FindFirstWrapObj(fLeft) * * @mfunc * Find the first object queued up to be wrapped. * * @rdesc * First object queued up to be wrapped. */ COleObject* CMeasurer::FindFirstWrapObj( BOOL fLeft) { for (int iobj = 0; iobj < _rgpobjWrap.Count(); iobj++) { COleObject *pobj = _rgpobjWrap.GetAt(iobj); if (!pobj->FAlignToRight() == fLeft) return pobj; } return 0; } /* * CMeasurer::XFromU(u) * * @mfunc * Given a U position on a line, convert it to X. In * RTL paragraphs, the U position of 0 on a line is * on the right edge of the control. * * @rdesc * x coordinate corresponding to u in current rotation */ LONG CMeasurer::XFromU(LONG u) { if (_pPF->IsRtlPara()) { CTxtEdit * ped = GetPed(); LONG uCaret = _pdp->IsMain() ? ped->GetCaretWidth() : 0; LONG dupLayout = LUtoDU(_dulLayout); if (_plo && _plo->IsNestedLayout()) ; else if(!_pdp->GetWordWrap()) dupLayout = max(_pdp->GetDupLineMax(), _pdp->GetDupView()); return dupLayout - u - uCaret; } return u; } LONG CMeasurer::UFromX(LONG x) { if (_pPF->IsRtlPara()) return XFromU(x); return x; } #ifndef NOLINESERVICES extern BOOL g_fNoLS; extern BOOL g_OLSBusy; /* * CMeasurer::GetPols() * * @mfunc * Get ptr to LineServices object. If LineServices not enabled, * return NULL. * * @rdesc * POLS */ COls *CMeasurer::GetPols() { CTxtEdit *ped = GetPed(); if(g_fNoLS || !ped->fUseLineServices()) // Not using LineServices return NULL; if(!g_pols) // Starting up LS: g_pols = new COls(); // create new COls if(g_pols) // Have the COls { if(g_pols->Init(this) != NOERROR) // Switch to new one { delete g_pols; g_pols = NULL; } g_OLSBusy = TRUE; UpdatePF(); } return g_pols; } #endif