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.
1699 lines
43 KiB
1699 lines
43 KiB
/*
|
|
* @doc
|
|
*
|
|
* @module - MEASURE.CPP
|
|
*
|
|
* CMeasurer class
|
|
*
|
|
* Authors:
|
|
* Original RichEdit code: David R. Fulmer <nl>
|
|
* Christian Fortini, Murray Sargent, Rick Sailor
|
|
*
|
|
* History: <nl>
|
|
* 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
|