2815 lines
71 KiB
C++
2815 lines
71 KiB
C++
/*
|
|
* @doc INTERNAL
|
|
*
|
|
* @module - RENDER.CPP |
|
|
* CRenderer class
|
|
*
|
|
* Authors:
|
|
* RichEdit 1.0 code: David R. Fulmer
|
|
* Christian Fortini (initial conversion to C++)
|
|
* Murray Sargent
|
|
* Keith Curtis (simplified, cleaned up, added support
|
|
* for non-western textflows.)
|
|
*
|
|
* Copyright (c) 1995-2000 Microsoft Corporation. All rights reserved.
|
|
*/
|
|
|
|
#include "_common.h"
|
|
#include "_render.h"
|
|
#include "_font.h"
|
|
#include "_disp.h"
|
|
#include "_edit.h"
|
|
#include "_select.h"
|
|
#include "_objmgr.h"
|
|
#include "_coleobj.h"
|
|
#include "_layout.h"
|
|
|
|
// Default colors for background and text on window's host printer
|
|
const COLORREF RGB_WHITE = RGB(255, 255, 255);
|
|
const COLORREF RGB_BLACK = RGB(0, 0, 0);
|
|
const COLORREF RGB_BLUE = RGB(0, 0, 255);
|
|
|
|
ASSERTDATA
|
|
|
|
extern const COLORREF g_Colors[];
|
|
static HBITMAP g_hbitmapSubtext = 0;
|
|
static HBITMAP g_hbitmapExpandedHeading = 0;
|
|
static HBITMAP g_hbitmapCollapsedHeading = 0;
|
|
static HBITMAP g_hbitmapEmptyHeading = 0;
|
|
|
|
void ShiftRect(
|
|
RECTUV &rc, //@parm rectangle
|
|
LONG dup, //@parm shift in u direction
|
|
LONG dvp) //@parm shift in v direction
|
|
{
|
|
rc.left -= dup;
|
|
rc.right -= dup;
|
|
rc.top -= dvp;
|
|
rc.bottom -= dvp;
|
|
}
|
|
|
|
HRESULT InitializeOutlineBitmaps()
|
|
{
|
|
g_hbitmapSubtext = LoadBitmap(hinstRE, MAKEINTRESOURCE(BITMAP_ID_SUBTEXT));
|
|
g_hbitmapExpandedHeading = LoadBitmap(hinstRE, MAKEINTRESOURCE(BITMAP_ID_EXPANDED_HEADING));
|
|
g_hbitmapCollapsedHeading = LoadBitmap(hinstRE, MAKEINTRESOURCE(BITMAP_ID_COLLAPSED_HEADING));
|
|
g_hbitmapEmptyHeading = LoadBitmap(hinstRE, MAKEINTRESOURCE(BITMAP_ID_EMPTY_HEADING));
|
|
|
|
if (!g_hbitmapSubtext ||
|
|
!g_hbitmapExpandedHeading ||
|
|
!g_hbitmapCollapsedHeading ||
|
|
!g_hbitmapEmptyHeading)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
void ReleaseOutlineBitmaps()
|
|
{
|
|
if (g_hbitmapSubtext)
|
|
{
|
|
DeleteObject(g_hbitmapSubtext);
|
|
DeleteObject(g_hbitmapExpandedHeading);
|
|
DeleteObject(g_hbitmapCollapsedHeading);
|
|
DeleteObject(g_hbitmapEmptyHeading);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CBrush::~CBrush()
|
|
*/
|
|
CBrush::~CBrush()
|
|
{
|
|
if(_hbrush) // Is NULL if all line borders have 0 width
|
|
{ // in which case, _hbrushOld is undefined
|
|
SelectObject(_pre->GetDC(), _hbrushOld);
|
|
DeleteObject(_hbrush);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CBrush::Draw(x1, y1, x2, y2, dxpLine, cr, fHideGridLines)
|
|
*
|
|
* @mfunc
|
|
* Draw a line from (x1, y1) to (x2, y2) with the pen width dxpLine
|
|
* and color cr.
|
|
*/
|
|
void CBrush::Draw(
|
|
LONG u1, //@parm Line starting x coord
|
|
LONG v1, //@parm Line starting y coord
|
|
LONG u2, //@parm Line ending x coord
|
|
LONG v2, //@parm Line ending y coord
|
|
LONG dxpLine, //@parm Width of line to draw
|
|
COLORREF cr, //@parm Color to use
|
|
BOOL fHideGridLines)//@parm If TRUE, hide 0-width gridlines
|
|
{
|
|
if(!dxpLine)
|
|
{
|
|
if(fHideGridLines) // Hide 0-width grid lines
|
|
return;
|
|
cr = RGB(192, 192, 192); // Display 0-width grid lines as 1-pixel
|
|
dxpLine = 1; // gray lines as in Word
|
|
}
|
|
|
|
HDC hdc = _pre->GetDC();
|
|
|
|
if(!_hbrush || _cr != cr)
|
|
{
|
|
HBRUSH hbrush = CreateSolidBrush(cr);
|
|
HBRUSH hbrushOld = (HBRUSH)SelectObject(hdc, hbrush);
|
|
|
|
if(!_hbrush)
|
|
_hbrushOld = hbrushOld; // Save original brush
|
|
else
|
|
DeleteObject(hbrushOld);
|
|
|
|
_hbrush = hbrush; // Update CPen state
|
|
_cr = cr;
|
|
}
|
|
RECTUV rcuv; // Convert to rcuv in case of rotation
|
|
|
|
rcuv.left = u1;
|
|
rcuv.top = v1;
|
|
if(u1 == u2) // Vertical line
|
|
{ // (in uv space)
|
|
rcuv.right = rcuv.left + dxpLine;
|
|
rcuv.bottom = v2;
|
|
}
|
|
else // Horizontal line
|
|
{ // (in uv space)
|
|
rcuv.right = u2;
|
|
rcuv.bottom = rcuv.top + dxpLine;
|
|
}
|
|
|
|
RECT rc; // Convert uv to xy space
|
|
_pre->GetPdp()->RectFromRectuv(rc, rcuv);
|
|
|
|
PatBlt(hdc, rc.left, rc.top, rc.right - rc.left,
|
|
rc.bottom - rc.top, PATCOPY);
|
|
}
|
|
|
|
/*
|
|
* IsTooSimilar(cr1, cr2)
|
|
*
|
|
* @mfunc
|
|
* Return TRUE if the colors cr1 and cr2 are so similar that they
|
|
* are hard to distinguish. Used for deciding to use reverse video
|
|
* selection instead of system selection colors.
|
|
*
|
|
* @rdesc
|
|
* TRUE if cr1 is too similar to cr2 to be used for selection
|
|
*
|
|
* @devnote
|
|
* The formula below uses RGB. It might be better to use some other
|
|
* color representation such as hue, saturation, and luminosity
|
|
*/
|
|
BOOL IsTooSimilar(
|
|
COLORREF cr1, //@parm First color for comparison
|
|
COLORREF cr2) //@parm Second color for comparison
|
|
{
|
|
if((cr1 | cr2) & 0xFF000000) // One color and/or the other
|
|
return FALSE; // isn't RGB, so algorithm
|
|
// doesn't apply
|
|
LONG DeltaR = abs(GetRValue(cr1) - GetRValue(cr2));
|
|
LONG DeltaG = abs(GetGValue(cr1) - GetGValue(cr2));
|
|
LONG DeltaB = abs(GetBValue(cr1) - GetBValue(cr2));
|
|
|
|
return DeltaR + DeltaG + DeltaB < 80;
|
|
}
|
|
|
|
/*
|
|
* GetShadedColor(crf, crb, iShading)
|
|
*
|
|
* @mfunc
|
|
* Return shaded color given by a mixture of crf and crb as determined
|
|
* by iShading. Used for table cell coloration.
|
|
*
|
|
* @rdesc
|
|
* Shaded color
|
|
*/
|
|
COLORREF GetShadedColor(
|
|
COLORREF crf,
|
|
COLORREF crb,
|
|
LONG iShading)
|
|
{
|
|
if ((crb | crf) & 0xFF000000 || // One or the other isn't an RGB
|
|
!iShading) // or no shading:
|
|
{
|
|
return crb; // just use crb
|
|
}
|
|
|
|
DWORD red = ((300 - iShading)*GetRValue(crb) + iShading*GetRValue(crf))/300;
|
|
DWORD green = ((300 - iShading)*GetGValue(crb) + iShading*GetGValue(crf))/300;
|
|
DWORD blue = ((300 - iShading)*GetBValue(crb) + iShading*GetBValue(crf))/300;
|
|
|
|
return RGB(red, green, blue);
|
|
}
|
|
|
|
|
|
// CRenderer class
|
|
|
|
CRenderer::CRenderer (const CDisplay * const pdp) :
|
|
CMeasurer (pdp)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::CRenderer");
|
|
|
|
Init();
|
|
}
|
|
|
|
CRenderer::CRenderer (const CDisplay * const pdp, const CRchTxtPtr &rtp) :
|
|
CMeasurer (pdp, rtp)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::CRenderer");
|
|
|
|
Init();
|
|
}
|
|
|
|
CRenderer::~CRenderer()
|
|
{
|
|
if(_hdcBitmap)
|
|
{
|
|
SelectObject(_hdcBitmap, _hbitmapSave);
|
|
DeleteDC(_hdcBitmap);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::Init()
|
|
*
|
|
* @mfunc
|
|
* Initialize most things to zero
|
|
*/
|
|
void CRenderer::Init()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::Init");
|
|
_fRenderer = TRUE;
|
|
CTxtEdit *ped = GetPed();
|
|
_cpAccelerator = ped->GetCpAccelerator();
|
|
|
|
static const RECTUV zrect = { 0, 0, 0, 0 };
|
|
_rcView = zrect;
|
|
_rcRender = zrect;
|
|
_rc = zrect;
|
|
_dupLine = 0;
|
|
_dwFlags = 0;
|
|
_hdcBitmap = NULL;
|
|
_ptCur.u = 0;
|
|
_ptCur.v = 0;
|
|
_plogpalette = NULL;
|
|
|
|
CDocInfo *pDocInfo = ped->GetDocInfoNC();
|
|
if(pDocInfo)
|
|
{
|
|
_dxBitmap = _pdp->LXtoDX(pDocInfo->_xExtGoal*pDocInfo->_xScale / 100);
|
|
_dyBitmap = _pdp->LYtoDY(pDocInfo->_yExtGoal*pDocInfo->_yScale / 100);
|
|
}
|
|
|
|
_crCurTextColor = CLR_INVALID;
|
|
|
|
_fRenderSelection = ped->GetSel() && ped->GetSel()->GetShowSelection();
|
|
_fErase = !_pdp->IsTransparent();
|
|
|
|
if(!ped->fInOurHost() || !_pdp->IsPrinter())
|
|
{
|
|
// If we are displaying to a window, or we are not in the window's
|
|
// host, we use the colors specified by the host. For text and
|
|
// foreground.
|
|
_crBackground = ped->TxGetBackColor();
|
|
_crTextColor = ped->TxGetForeColor();
|
|
}
|
|
else
|
|
{
|
|
// When the window's host is printing, the default colors are white
|
|
// for the background and black for the text.
|
|
_crBackground = RGB_WHITE;
|
|
_crTextColor = RGB_BLACK;
|
|
}
|
|
|
|
_hdc = _pdp->GetDC();
|
|
|
|
::SetBkColor (_hdc, _crBackground);
|
|
_crCurBackground = _crBackground;
|
|
|
|
// For hack around ExtTextOutW OnWin9x EMF problems
|
|
_fEnhancedMetafileDC = IsEnhancedMetafileDC(_hdc);
|
|
|
|
_fDisplayDC = GetDeviceCaps(_hdc, TECHNOLOGY) == DT_RASDISPLAY;
|
|
|
|
// Set text alignment
|
|
// Its much faster to draw using top/left alignment than to draw
|
|
// using baseline alignment.
|
|
SetTextAlign(_hdc, TA_TOP | TA_LEFT);
|
|
SetBkMode(_hdc, TRANSPARENT);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::StartRender (&rcView, &rcRender)
|
|
*
|
|
* @mfunc
|
|
* Prepare this renderer for rendering operations
|
|
*
|
|
* @rdesc
|
|
* FALSE if nothing to render, TRUE otherwise
|
|
*/
|
|
BOOL CRenderer::StartRender (
|
|
const RECTUV &rcView, //@parm View rectangle
|
|
const RECTUV &rcRender) //@parm Rectangle to render
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::StartRender");
|
|
|
|
// Set view and rendering rects
|
|
_rcView = rcView;
|
|
_rcRender = rcRender;
|
|
|
|
// If this isn't a metafile, we set a flag indicating whether we
|
|
// can safely erase the background
|
|
if(_pdp->IsMetafile() || !_pdp->IsMain())
|
|
{
|
|
// Since this isn't the main display or it is a metafile,
|
|
// we want to ignore the logic to render selections
|
|
_fRenderSelection = FALSE;
|
|
|
|
if(_fErase) // If control is not transparent,
|
|
EraseTextOut(_hdc, &rcRender); // clear display
|
|
|
|
// This is a metafile or a printer so clear the render rectangle
|
|
// and then pretend we are transparent.
|
|
_fErase = FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::EraseLine()
|
|
*
|
|
* @mfunc
|
|
* Erase the line
|
|
*/
|
|
void CRenderer::EraseLine()
|
|
{
|
|
Assert(_fEraseOnFirstDraw);
|
|
COLORREF crOld = SetBkColor(_hdc, _crBackground);
|
|
|
|
EraseTextOut(_hdc, &_rcErase);
|
|
SetBkColor(_hdc, crOld);
|
|
_fEraseOnFirstDraw = FALSE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::EraseRect(prc, crBack)
|
|
*
|
|
* @mfunc
|
|
* Erase a specific rectangle for special table cell background color
|
|
*
|
|
* @rdesc
|
|
* Old value of _fErase
|
|
*/
|
|
BOOL CRenderer::EraseRect(
|
|
const RECTUV *prc, //@parm RECT to erase
|
|
COLORREF crBack) //@parm Background color to use
|
|
{
|
|
SetDefaultBackColor(crBack);
|
|
EraseTextOut(_hdc, prc, TRUE);
|
|
BOOL fErase = _fErase;
|
|
_fErase = FALSE;
|
|
return fErase;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::IsSimpleBackground()
|
|
*
|
|
* @mfunc
|
|
* Return TRUE if the background is opaque
|
|
*/
|
|
BOOL CRenderer::IsSimpleBackground() const
|
|
{
|
|
CDocInfo *pDocInfo = GetPed()->GetDocInfoNC();
|
|
|
|
if (!pDocInfo || pDocInfo->_nFillType != 7 && !IN_RANGE(1, pDocInfo->_nFillType, 3))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::EraseTextOut(hdc, prc, fSimple)
|
|
*
|
|
* @mfunc
|
|
* Erase a specific area
|
|
*/
|
|
void CRenderer::EraseTextOut(
|
|
HDC hdc,
|
|
const RECTUV *prc,
|
|
BOOL fSimple)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::EraseTextOut");
|
|
|
|
CDocInfo *pDocInfo = GetPed()->GetDocInfoNC();
|
|
RECT rc;
|
|
|
|
_pdp->RectFromRectuv(rc, *prc);
|
|
|
|
if (fSimple || IsSimpleBackground()) // No special background
|
|
{
|
|
W32->EraseTextOut(hdc, &rc);
|
|
return;
|
|
}
|
|
|
|
// To do background gradients and bitmap fills with rotated coords, need
|
|
// to use rc as above and translate calls to _pdp->GetUpScroll() and
|
|
// _pdp->GetVpScroll(). For directions other than tflowES get rid of
|
|
// screen width and height offsets used in PointFromPointuv(), but keep
|
|
// the minus signs.
|
|
LONG uScroll = _pdp->GetUpScroll();
|
|
LONG vScroll = _pdp->GetVpScroll();
|
|
POINT ptScroll = {uScroll, vScroll}; // Default unrotated
|
|
|
|
TFLOW tflow = _pdp->GetTflow();
|
|
switch(tflow)
|
|
{
|
|
case tflowSW: // Vertical
|
|
ptScroll.x = -vScroll;
|
|
ptScroll.y = uScroll;
|
|
break;
|
|
|
|
case tflowWN:
|
|
ptScroll.x = -uScroll;
|
|
ptScroll.y = -vScroll;
|
|
break;
|
|
|
|
case tflowNE:
|
|
ptScroll.x = vScroll;
|
|
ptScroll.y = -uScroll;
|
|
break;
|
|
}
|
|
|
|
if(IN_RANGE(1, pDocInfo->_nFillType, 3))
|
|
{
|
|
if(!pDocInfo->_hBitmapBack)
|
|
return;
|
|
|
|
if(!_hdcBitmap)
|
|
{
|
|
// Setup compatible DC to use for background BitBlts for
|
|
// the lifetime of this renderer
|
|
_hdcBitmap = CreateCompatibleDC(hdc);
|
|
if(!_hdcBitmap)
|
|
return; // Out of memory
|
|
_hbitmapSave = (HBITMAP)SelectObject(_hdcBitmap, pDocInfo->_hBitmapBack);
|
|
}
|
|
|
|
LONG wBitmap = _dxBitmap;
|
|
LONG hBitmap = _dyBitmap;
|
|
|
|
LONG yb = (ptScroll.y + rc.top) % hBitmap;
|
|
if(yb < 0)
|
|
yb += hBitmap;
|
|
LONG h = hBitmap - yb;
|
|
LONG y = rc.top;
|
|
|
|
while(y < rc.bottom)
|
|
{
|
|
if(y + h > rc.bottom) // Don't overshoot bottom
|
|
h = rc.bottom - y;
|
|
LONG xb = (ptScroll.x + rc.left) % wBitmap;
|
|
if(xb < 0) // xb can be < 0 if ptScroll.x < 0
|
|
xb += wBitmap;
|
|
LONG w = wBitmap - xb;
|
|
LONG x = rc.left;
|
|
|
|
while(x < rc.right)
|
|
{
|
|
if(x + w > rc.right) // Don't overshoot right
|
|
w = rc.right - x;
|
|
BitBlt(hdc, x, y, w, h, _hdcBitmap, xb, yb, SRCCOPY);
|
|
x += w;
|
|
w = wBitmap;
|
|
xb = 0;
|
|
}
|
|
y += h;
|
|
h = hBitmap;
|
|
yb = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Gradient fill backgrounds
|
|
LONG Angle = pDocInfo->_sFillAngle;
|
|
COLORREF crb = pDocInfo->_crColor;
|
|
COLORREF crf = pDocInfo->_crBackColor;
|
|
LONG di = ptScroll.x; // Default vertical values
|
|
LONG h = 0;
|
|
HPEN hpen = NULL;
|
|
HPEN hpenEntry = NULL;
|
|
LONG iFirst = rc.left;
|
|
LONG iLim = rc.right;
|
|
LONG iShading;
|
|
|
|
switch(Angle)
|
|
{
|
|
case -45: // Diagonal down
|
|
case -135: // Diagonal up
|
|
h = rc.bottom - rc.top;
|
|
if(Angle == -45)
|
|
{
|
|
di -= ptScroll.y + rc.top;
|
|
iFirst -= h;
|
|
h = -h;
|
|
}
|
|
else
|
|
{
|
|
di += ptScroll.y + rc.top;
|
|
iLim += h;
|
|
}
|
|
break;
|
|
|
|
case 0: // Horizontal
|
|
iFirst = rc.top;
|
|
iLim = rc.bottom;
|
|
di = ptScroll.y;
|
|
break;
|
|
}
|
|
|
|
if(!crf) // Moderate black a bit (needs work)
|
|
crf = RGB(100, 100, 100);
|
|
|
|
for(LONG i = iFirst; i < iLim; i++)
|
|
{
|
|
iShading = (di + i) % 600;
|
|
if(iShading < 0) // Pattern moves up screen
|
|
iShading += 600;
|
|
if(iShading > 300)
|
|
iShading = 600 - iShading;
|
|
|
|
iShading = max(iShading, 30);
|
|
iShading = min(iShading, 270);
|
|
|
|
if(hpen)
|
|
DeleteObject(hpen);
|
|
hpen = CreatePen(PS_SOLID, 0, GetShadedColor(crf, crb, iShading));
|
|
if(!hpenEntry)
|
|
hpenEntry = (HPEN)SelectObject(hdc, hpen);
|
|
else
|
|
SelectObject(hdc, hpen);
|
|
|
|
POINT rgpt[2];
|
|
if(Angle) // -90 (vertical) or
|
|
{ // -135 (diagonal)
|
|
if(i > rc.right) // Don't let diagonal overshoot
|
|
{
|
|
rgpt[0].x = rc.right;
|
|
rgpt[0].y = rc.top + (i - rc.right);
|
|
}
|
|
else
|
|
{
|
|
rgpt[0].x = i;
|
|
rgpt[0].y = rc.top;
|
|
}
|
|
if(i - h < iFirst) // Don't let diagonal undershoot
|
|
{
|
|
rgpt[1].x = iFirst - 1;
|
|
rgpt[1].y = rc.bottom - (iFirst - 1 - (i - h));
|
|
}
|
|
else
|
|
{
|
|
rgpt[1].x = i - h;
|
|
rgpt[1].y = rc.bottom;
|
|
}
|
|
}
|
|
else // Horizontal (0 degrees)
|
|
{
|
|
rgpt[0].x = rc.left;
|
|
rgpt[0].y = i;
|
|
rgpt[1].x = rc.right;
|
|
rgpt[1].y = i;
|
|
}
|
|
Polyline(hdc, rgpt, 2); // Use Polyline() so as not to
|
|
} // break WinCE
|
|
if(hpen)
|
|
{
|
|
DeleteObject(hpen);
|
|
SelectObject(hdc, hpenEntry);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::DrawWrappedObjects(pliFirst, pliLast, cpFisrt, ptFirst, fLeft)
|
|
*
|
|
* @mfunc
|
|
* Draw all wrapped objects in the range on the left or right side.
|
|
*
|
|
*/
|
|
void CRenderer::DrawWrappedObjects(CLine *pliFirst, CLine *pliLast, LONG cpFirst, const POINTUV &ptFirst)
|
|
{
|
|
for (BOOL fLeft = 0; fLeft != 2; fLeft ++) //For left and right sides...
|
|
{
|
|
CLine *pli = pliFirst;
|
|
LONG cp = cpFirst;
|
|
POINTUV pt = ptFirst;
|
|
|
|
//If the first line is part-way through an object, then back up to the beginning.
|
|
if (fLeft && pli->_cObjectWrapLeft || !fLeft && pli->_cObjectWrapRight)
|
|
{
|
|
while (fLeft ? !pli->_fFirstWrapLeft : !pli->_fFirstWrapRight)
|
|
{
|
|
pli--;
|
|
pt.v -= pli->GetHeight();
|
|
cp -= pli->_cch;
|
|
}
|
|
}
|
|
|
|
for (;pli <= pliLast; cp += pli->_cch, pt.v += pli->GetHeight(), pli++)
|
|
{
|
|
//Did we find an object which needs to be drawn?
|
|
if (fLeft && pli->_fFirstWrapLeft || !fLeft && pli->_fFirstWrapRight)
|
|
{
|
|
LONG cpObj = FindCpDraw(cp + 1, fLeft ? pli->_cObjectWrapLeft : pli->_cObjectWrapRight, fLeft);
|
|
COleObject *pobj = GetObjectFromCp(cpObj);
|
|
if (!pobj)
|
|
return;
|
|
|
|
LONG dvpAscent, dvpDescent, dup;
|
|
pobj->MeasureObj(_dvpInch, _dupInch, dup, dvpAscent, dvpDescent, 0, GetTflow());
|
|
|
|
POINTUV ptDraw = pt;
|
|
if (!fLeft) //Right align images
|
|
ptDraw.u += _pdp->GetDupView() - dup;
|
|
|
|
RECTUV rc = {_rcRender.left, _rcView.top, _rcRender.right, _rcView.bottom};
|
|
|
|
pobj->DrawObj(_pdp, _dvpInch, _dupInch, _hdc, &rc, _pdp->IsMetafile(),
|
|
&ptDraw, dvpAscent + dvpDescent, 0, GetTflow());
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::EndRender(pliFirst, pliLast, cpFirst, &ptFirst)
|
|
*
|
|
* @mfunc
|
|
* Any final operations which are to happen after we've drawn
|
|
* all of the lines.
|
|
*/
|
|
void CRenderer::EndRender(
|
|
CLine * pliFirst,
|
|
CLine * pliLast,
|
|
LONG cpFirst,
|
|
const POINTUV &ptFirst)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::EndRender");
|
|
AssertSz(_hdc, "CRenderer::EndRender() - No rendering DC");
|
|
|
|
if(_fErase && _ptCur.v < _rcRender.bottom)
|
|
{
|
|
RECTUV rc = _rcRender;
|
|
rc.top = _ptCur.v;
|
|
EraseTextOut(_hdc, &rc);
|
|
}
|
|
DrawWrappedObjects(pliFirst, pliLast, cpFirst, ptFirst);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::NewLine (&li)
|
|
*
|
|
* @mfunc
|
|
* Init this CRenderer for rendering the specified line
|
|
*/
|
|
void CRenderer::NewLine (
|
|
const CLine &li)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::NewLine");
|
|
|
|
_li = li;
|
|
|
|
Assert(GetCp() + _li._cch <= GetTextLength());
|
|
|
|
_cchLine = li._cch;
|
|
_dupLine = _li._dup;
|
|
_li._dup = 0;
|
|
_ptCur.u = _rcView.left;
|
|
if(!_pPF->InTable())
|
|
_ptCur.u -= _pdp->GetUpScroll();
|
|
_fSelected = _fSelectedPrev = FALSE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetupOffscreenDC(dup, dvp)
|
|
*
|
|
* @mfunc
|
|
* Setup renderer for using an offscreen DC
|
|
*
|
|
* @rdesc
|
|
* NULL - an error occurred<nl>
|
|
* ~NULL - DC to save
|
|
*/
|
|
HDC CRenderer::SetupOffscreenDC(
|
|
LONG& dup, //@parm Offset to u
|
|
LONG& dvp, //@parm Offset to v
|
|
BOOL fLastLine)
|
|
{
|
|
// Save render DC
|
|
CTxtEdit *ped = GetPed();
|
|
BOOL fInOurHost = ped->fInOurHost();
|
|
|
|
HDC hdcSave = _hdc;
|
|
|
|
//If we've already erased (can't prevent flicker now!)
|
|
//or this is some weird textflow, then don't do offscreens.
|
|
if (!_fErase || GetTflow() != tflowES || ped->GetBackgroundType() != -1)
|
|
return NULL;
|
|
|
|
RECTUV rc;
|
|
RECT rcBitmap;
|
|
rc.left = _rcRender.left;
|
|
rc.right = _rcRender.right;
|
|
rc.top = _rc.top;
|
|
rc.bottom = _rc.bottom;
|
|
_pdp->RectFromRectuv(rcBitmap, rc);
|
|
|
|
if (_osdc.GetDC() == NULL)
|
|
{
|
|
if (!_osdc.Init(_hdc, rcBitmap.right - rcBitmap.left, rcBitmap.bottom - rcBitmap.top, _crBackground))
|
|
return NULL;
|
|
|
|
HPALETTE hpal = fInOurHost ? ped->TxGetPalette() : (HPALETTE) GetCurrentObject(_hdc, OBJ_PAL);
|
|
_osdc.SelectPalette(hpal);
|
|
}
|
|
else
|
|
{
|
|
LONG dx, dy;
|
|
_osdc.GetDimensions(&dx, &dy);
|
|
//REVIEW (keithcu) Simplify?
|
|
if (IsUVerticalTflow(GetTflow()))
|
|
{
|
|
if (dx < rcBitmap.bottom - rcBitmap.top)
|
|
{
|
|
if (_osdc.Realloc(_rc.bottom - _rc.top + dy / 16, dy)) //Resize the bitmap, plus a little room
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
else if (dy < rcBitmap.bottom - rcBitmap.top)
|
|
{
|
|
if (_osdc.Realloc(dx, _rc.bottom - _rc.top + dy / 16)) //Resize the bitmap, plus a little room
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
_hdc = _osdc.GetDC();
|
|
_crCurTextColor = CLR_INVALID;
|
|
if(_pccs)
|
|
{
|
|
// There is current a character format for the run so we need to
|
|
// get in sync with that since the offscreen DC isn't necessarily
|
|
// set to that font.
|
|
// Get the character format and set up the font
|
|
SetFontAndColor(GetCF());
|
|
}
|
|
|
|
// We are rendering to a transparent background
|
|
_fErase = FALSE;
|
|
|
|
// Clear bitmap
|
|
::SetBkColor(_hdc, _crBackground);
|
|
_osdc.FillBitmap(rcBitmap.right - rcBitmap.left, rcBitmap.bottom - rcBitmap.top);
|
|
|
|
//If the first line, erase to edge of rcRender
|
|
if (_rc.top <= _rcView.top)
|
|
{
|
|
//Clear top of rcRender if necessary
|
|
RECTUV rcErase = _rcRender;
|
|
|
|
rcErase.top = min(_rcView.top, _rcRender.top);
|
|
rcErase.bottom = _rc.top;
|
|
|
|
if (rcErase.bottom > rcErase.top)
|
|
EraseTextOut(hdcSave, &rcErase);
|
|
}
|
|
|
|
// Restore background color if necessary
|
|
if(_crBackground != _crCurBackground)
|
|
::SetBkColor(_hdc, _crCurBackground);
|
|
|
|
SetBkMode(_hdc, TRANSPARENT);
|
|
|
|
// Store v adjustment to use in rendering off-screen bitmap
|
|
dvp = _rc.top;
|
|
|
|
// Store u adjustment to use in rendering off-screen bitmap
|
|
dup = _rcRender.left;
|
|
|
|
// Normalize _rc, _rcView, & _rcRender
|
|
ShiftRect( _rc, dup, dvp);
|
|
ShiftRect( _rcView, dup, dvp);
|
|
ShiftRect(_rcRender, dup, dvp);
|
|
|
|
// Normalize _ptCur for rendering to off-screen bitmap
|
|
_ptCur.u -= dup;
|
|
_ptCur.v -= dvp;
|
|
|
|
return hdcSave;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderOffscreenBitmap(hdc, dup, yAdj)
|
|
*
|
|
* @mfunc
|
|
* Render off screen bitmap and restore the state of the render.
|
|
*/
|
|
void CRenderer::RenderOffscreenBitmap(
|
|
HDC hdc, //@parm DC to render to
|
|
LONG dup, //@parm offset to real u base
|
|
LONG dvp) //@parm offset to real v base
|
|
{
|
|
// Palettes for rendering bitmap
|
|
HPALETTE hpalOld = NULL;
|
|
HPALETTE hpalNew = NULL;
|
|
|
|
// Restore pt
|
|
_ptCur.u += dup;
|
|
_ptCur.v += dvp;
|
|
|
|
// Restore rect
|
|
LONG dupTemp = -dup;
|
|
LONG dvpTemp = -dvp;
|
|
ShiftRect( _rc, dupTemp, dvpTemp);
|
|
ShiftRect( _rcView, dupTemp, dvpTemp);
|
|
ShiftRect(_rcRender, dupTemp, dvpTemp);
|
|
|
|
// Create a palette if one is needed
|
|
if(_plogpalette)
|
|
W32->ManagePalette(hdc, _plogpalette, hpalOld, hpalNew);
|
|
|
|
RECTUV rcuv = {dup, dvp, dup + _rcRender.right - _rcRender.left, dvp + _rc.bottom - _rc.top};
|
|
RECT rc;
|
|
_pdp->RectFromRectuv(rc, rcuv);
|
|
// Render bitmap to real DC and restore _ptCur & _rc
|
|
_osdc.RenderBitMap(hdc, rc.left, rc.top, _rcRender.right - _rcRender.left, _rc.bottom - _rc.top);
|
|
|
|
// Restore palette after render if necessary
|
|
if(_plogpalette)
|
|
{
|
|
W32->ManagePalette(hdc, _plogpalette, hpalOld, hpalNew);
|
|
CoTaskMemFree(_plogpalette);
|
|
_plogpalette = NULL;
|
|
}
|
|
|
|
// Restore HDC to actual render DC
|
|
_hdc = hdc;
|
|
|
|
// Set this flag to what it should be for restored DC
|
|
_fErase = TRUE;
|
|
|
|
_crCurTextColor = CLR_INVALID;
|
|
|
|
// Reset screen DC font
|
|
// Set up font on non-screen DC
|
|
// Force color resynch
|
|
if(!FormatIsChanged()) // Not on a new block,
|
|
SetFontAndColor(GetCF()); // so just set font and color
|
|
else
|
|
{ // On new block,
|
|
ResetCachediFormat(); // so reset everything
|
|
SetNewFont();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderLine (&li, fLastLine)
|
|
*
|
|
* @mfunc
|
|
* Render visible part of current line
|
|
*
|
|
* @rdesc
|
|
* TRUE if success, FALSE if failed
|
|
*
|
|
* @devnote
|
|
* Only call this from CLine::RenderLine()
|
|
*/
|
|
BOOL CRenderer::RenderLine (
|
|
CLine & li, //@parm Line to render
|
|
BOOL fLastLine) //@parm True if last line in layout
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::RenderLine");
|
|
|
|
BYTE bUnderlineSave = 0;
|
|
LONG cch;
|
|
LONG cchChunk;
|
|
LONG cchInTextRun;
|
|
BOOL fAccelerator = FALSE;
|
|
const WCHAR * pstrToRender;
|
|
CTempWcharBuf twcb;
|
|
WCHAR chPassword = GetPasswordChar();
|
|
|
|
UpdatePF();
|
|
|
|
// This is used as a temporary buffer so that we can guarantee that we will
|
|
// display an entire format run in one ExtTextOut.
|
|
WCHAR * pszTempBuffer = NULL;
|
|
|
|
NewLine(li); // Init render at start of line
|
|
_fLastChunk = FALSE;
|
|
_ptCur.u += _li._upStart; // Add in line left indent
|
|
|
|
// Allow for special rendering at start of line
|
|
LONG cpSelMin, cpSelMost;
|
|
LONG dup, dvp;
|
|
HDC hdcSave = StartLine(li, fLastLine, cpSelMin, cpSelMost, dup, dvp);
|
|
|
|
cch = _li._cch;
|
|
if(chPassword && IsRich())
|
|
{
|
|
// It is kind of stupid to allow rich text password edit controls.
|
|
// However, it does make it that much easier to create a password
|
|
// edit control since you don't have to know to change the box to
|
|
// plain. Anyway, if there is such a thing, we don't want to put
|
|
// out password characters for EOPs in general and the final EOP
|
|
// specifically. Therefore, the following ...
|
|
if(_pdp->IsMultiLine())
|
|
cch -= _li._cchEOP;
|
|
else
|
|
cch = GetPed()->GetAdjustedTextLength();
|
|
}
|
|
|
|
for(; cch > 0; cch -= cchChunk)
|
|
{
|
|
// Initial chunk (number of characters to render in a single TextOut)
|
|
// is min between CHARFORMAT run length and line length. Start with
|
|
// count of characters left in current format run
|
|
cchChunk = GetCchLeftRunCF();
|
|
AssertSz(cchChunk != 0, "empty CHARFORMAT run");
|
|
|
|
DWORD dwEffects = GetCF()->_dwEffects;
|
|
if(dwEffects & CFE_HIDDEN) // Don't display hidden text
|
|
{
|
|
Move(cchChunk);
|
|
continue;
|
|
}
|
|
if(GetChar() == NOTACHAR) // Ignore NOTACHAR code
|
|
{
|
|
Move(1);
|
|
continue;
|
|
}
|
|
|
|
// Limit chunk to count of characters we want to display.
|
|
cchChunk = min(cch, cchChunk);
|
|
|
|
// Get count of characters in text run
|
|
pstrToRender = _rpTX.GetPch(cchInTextRun);
|
|
AssertSz(cchInTextRun > 0, "empty text run");
|
|
|
|
if (cchInTextRun < cchChunk || chPassword || dwEffects & CFE_ALLCAPS)
|
|
{
|
|
// The count of contiguous chars in the backing store run is
|
|
// less than the count of characters we wish to display or this
|
|
// is a password control or we want all caps. We copy the data
|
|
// out of the backing store.
|
|
if(!pszTempBuffer)
|
|
{
|
|
// Allocate buffer big enough to handle all future
|
|
// requests in this loop.
|
|
pszTempBuffer = twcb.GetBuf(cch);
|
|
if (!pszTempBuffer)
|
|
{
|
|
CCallMgr * pcallmgr = GetPed()->GetCallMgr();
|
|
|
|
if (pcallmgr)
|
|
pcallmgr->SetOutOfMemory();
|
|
|
|
return FALSE; // Fail to allocate memory
|
|
}
|
|
}
|
|
_rpTX.GetText(cchChunk, pszTempBuffer);
|
|
pstrToRender = pszTempBuffer; // Point at buffer
|
|
if(chPassword)
|
|
{
|
|
// Fill buffer with password characters
|
|
for (int i = 0, j = 0; i < cchChunk; i++)
|
|
{
|
|
if(!IN_RANGE(0xDC00, pszTempBuffer[i], 0xDFFF))
|
|
pszTempBuffer[j++] = chPassword;
|
|
}
|
|
cch -= cchChunk - j;
|
|
Move(cchChunk - j);
|
|
cchChunk = j;
|
|
}
|
|
else if(dwEffects & CFE_ALLCAPS)
|
|
CharUpperBuff(pszTempBuffer, cchChunk);
|
|
}
|
|
|
|
if(_cpAccelerator != -1)
|
|
{
|
|
LONG cpCur = GetCp(); // Get current cp
|
|
|
|
// Does accelerator character fall in this chunk?
|
|
if (cpCur < _cpAccelerator &&
|
|
cpCur + cchChunk > _cpAccelerator)
|
|
{
|
|
// Yes. Reduce chunk to char just before accelerator
|
|
cchChunk = _cpAccelerator - cpCur;
|
|
}
|
|
// Is this character the accelerator?
|
|
else if(cpCur == _cpAccelerator)
|
|
{ // Set chunk size to 1 since only
|
|
cchChunk = 1; // want to output underlined char
|
|
fAccelerator = TRUE; // Tell downstream routines that
|
|
// we're handling accelerator
|
|
_cpAccelerator = -1; // Only 1 accelerator per line
|
|
}
|
|
}
|
|
|
|
// Reduce chunk to account for selection if we are rendering for a
|
|
// display that cares about selections.
|
|
if(_fRenderSelection && cpSelMin != cpSelMost)
|
|
{
|
|
LONG cchSel = cpSelMin - GetCp();
|
|
if(cchSel > 0)
|
|
cchChunk = min(cchChunk, cchSel);
|
|
|
|
else if(GetCp() < cpSelMost)
|
|
{
|
|
cchSel = cpSelMost - GetCp();
|
|
if(cchSel >= cch)
|
|
_fSelectToEOL = TRUE;
|
|
else
|
|
cchChunk = min(cchChunk, cchSel);
|
|
|
|
_fSelected = TRUE; // cpSelMin <= GetCp() < cpSelMost
|
|
} // so current run is selected
|
|
}
|
|
|
|
// If start of CCharFormat run, select font and color
|
|
if(FormatIsChanged() || _fSelected != _fSelectedPrev)
|
|
{
|
|
ResetCachediFormat();
|
|
_fSelectedPrev = _fSelected;
|
|
if(!SetNewFont())
|
|
return FALSE; // Failed
|
|
}
|
|
|
|
if(fAccelerator)
|
|
{
|
|
bUnderlineSave = _bUnderlineType;
|
|
SetupUnderline(CFU_UNDERLINE, 0);
|
|
}
|
|
|
|
// Allow for further reduction of the chunk and rendering of
|
|
// interleaved rich text elements
|
|
if(_li._fHasSpecialChars && RenderChunk(cchChunk, pstrToRender, cch))
|
|
{
|
|
AssertSz(cchChunk > 0, "CRenderer::RenderLine(): cchChunk == 0");
|
|
_fSelected = FALSE;
|
|
continue;
|
|
}
|
|
|
|
AssertSz(cchChunk > 0,"CRenderer::RenderLine() - cchChunk == 0");
|
|
|
|
_fLastChunk = (cchChunk == cch);
|
|
RenderText(pstrToRender, cchChunk); // Render the text
|
|
|
|
if(fAccelerator)
|
|
{
|
|
_bUnderlineType = bUnderlineSave;
|
|
fAccelerator = FALSE; // Turn off special accelerator
|
|
} // processing
|
|
Move(cchChunk);
|
|
|
|
// Break if we went past right of render rect.
|
|
if(_ptCur.u >= _rcRender.right)
|
|
{
|
|
cch -= cchChunk;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EndLine(hdcSave, dup, dvp);
|
|
Move(cch);
|
|
return TRUE; // Success
|
|
}
|
|
|
|
/*
|
|
* CRenderer::EndLine (hdcSave, dup, dvp)
|
|
*
|
|
* @mfunc
|
|
* Finish up rendering of line, drawing table borders, rendering
|
|
* offscreen DC, and erasing to right of render rect if necessary.
|
|
*/
|
|
void CRenderer::EndLine(
|
|
HDC hdcSave,
|
|
LONG dup,
|
|
LONG dvp)
|
|
{
|
|
if(hdcSave)
|
|
RenderOffscreenBitmap(hdcSave, dup, dvp);
|
|
|
|
// Handle setting background color. We need to do this for each line
|
|
// because we return the background color to the default after each
|
|
// line so that opaquing will work correctly.
|
|
if(_crBackground != _crCurBackground)
|
|
{
|
|
::SetBkColor(_hdc, _crBackground); // Tell window background color
|
|
_crCurBackground = _crBackground;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::GetColorFromIndex (icr, fForeColor, pPF)
|
|
*
|
|
* @mfunc
|
|
* Returns COLORREF corresponding to color index icr
|
|
*
|
|
* @rdesc
|
|
* Get COLORREF corresponding to color index icr as follows:
|
|
* icr = 1 to 16 is g_Colors[icr-1]
|
|
* icr = 17 is pCF->_crTextColor
|
|
* icr = 18 is pCF->_crBackColor
|
|
* else CRenderer autocolor corresponding to fForeColor
|
|
*/
|
|
COLORREF CRenderer::GetColorFromIndex(
|
|
LONG icr, //@parm Color index
|
|
BOOL fForeColor, //@parm TRUE if foreground color (for autocolor)
|
|
const CParaFormat *pPF) const //@parm PF for two custom colors
|
|
{
|
|
icr &= 0x1F; // Mask off other indices
|
|
|
|
if(!IN_RANGE(1, icr, 18))
|
|
return fForeColor ? _crTextColor : _crBackground; // autocolor
|
|
|
|
if(IN_RANGE(1, icr, 16)) // One of standard 16 colors
|
|
return g_Colors[icr - 1];
|
|
|
|
// Two custom colors
|
|
return (icr == 17) ? pPF->_crCustom1 : pPF->_crCustom2;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::GetShadedColorFromIndices (icrf, icrb, iShading, pPF)
|
|
*
|
|
* @mfunc
|
|
* Returns COLORREF corresponding to color index icr
|
|
*
|
|
* @rdesc
|
|
* Get COLORREF corresponding to foreground/background indices
|
|
* icrf and icrb according to shading iShading
|
|
*/
|
|
COLORREF CRenderer::GetShadedColorFromIndices(
|
|
LONG icrf, //@parm Foreground color index
|
|
LONG icrb, //@parm Background color index
|
|
LONG iShading, //@parm Shading in .01 percent
|
|
const CParaFormat *pPF) const //@parm PF for two custom colors
|
|
{
|
|
Assert(iShading <= 200);
|
|
|
|
COLORREF crb = GetColorFromIndex (icrb, FALSE, pPF);
|
|
COLORREF crf = GetColorFromIndex (icrf, TRUE, pPF);
|
|
|
|
return GetShadedColor(crf, crb, (iShading*3)/2);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::DrawTableBorders (pPF, u, vHeightRow, iDrawBottomLine, dulRow, pPFAbove)
|
|
*
|
|
* @mfunc
|
|
* Draws table borders. If iDrawBottomLine is nonzero, draw bottom line
|
|
* as well as others. If iDrawBottomLine & 1, width of bottom line is
|
|
* included in vHeightRow; else if iDrawBottomLine is nonzero, draw bottom
|
|
* line immediately below the row and return the extra height.
|
|
*
|
|
* @rdesc
|
|
* Extra dvp if extra bottom line is drawn
|
|
*/
|
|
LONG CRenderer::DrawTableBorders(
|
|
const CParaFormat *pPF, //@parm PF with cell data
|
|
LONG u, //@parm u position to start table row borders
|
|
LONG vHeightRow, //@parm Height of row
|
|
LONG iDrawBottomLine, //@parm Flags on drawing bottom line
|
|
LONG dulRow, //@parm Length of row
|
|
const CParaFormat *pPFAbove)//@parm PF for row above
|
|
{
|
|
CBrush brush(this);
|
|
LONG cCell = pPF->_bTabCount;
|
|
LONG cCellAbove = 0;
|
|
COLORREF cr;
|
|
LONG dupRow = LUtoDU(dulRow);
|
|
LONG dvp = 0;
|
|
LONG dxlLine;
|
|
LONG dxlLinePrevRight = 0;
|
|
LONG dxpLine;
|
|
BOOL fHideGridlines = GetPed()->fHideGridlines() || !fDisplayDC();
|
|
BOOL fRTLRow = pPF->IsRtl();
|
|
LONG iCell;
|
|
LONG icr;
|
|
const CELLPARMS *prgCellParms = pPF->GetCellParms();
|
|
const CELLPARMS *prgCellParmsAbove = NULL;
|
|
LONG vTop = _ptCur.v;
|
|
LONG vBot = vTop + vHeightRow;
|
|
LONG v = vBot;
|
|
|
|
if(pPFAbove)
|
|
{
|
|
prgCellParmsAbove = pPFAbove->GetCellParms();
|
|
cCellAbove = pPFAbove->_bTabCount;
|
|
}
|
|
if(_fErase)
|
|
{
|
|
//Erase left and right edges of table
|
|
LONG cpSelMin, cpSelMost;
|
|
RECTUV rc = {_rcRender.left, vTop, u, vBot};
|
|
|
|
EraseTextOut(_hdc, &rc);
|
|
|
|
rc.left = u + dupRow;
|
|
rc.right = _rcRender.right;
|
|
EraseTextOut(_hdc, &rc);
|
|
|
|
//If first line, erase to edge of rcRender
|
|
if (rc.top <= _rcView.top)
|
|
rc.top = _rcRender.top;
|
|
rc.left = 0;
|
|
rc.bottom = vTop;
|
|
EraseTextOut(_hdc, &rc);
|
|
|
|
// Display row selection mark if row is selected
|
|
GetPed()->GetSelRangeForRender(&cpSelMin, &cpSelMost);
|
|
Assert(_rpTX.IsAfterTRD(ENDFIELD));
|
|
if(GetCp() <= cpSelMost && GetCp() > cpSelMin)
|
|
{ // Row is selected
|
|
COLORREF crSave = _crBackground;
|
|
LONG dup;
|
|
if(!_pccs)
|
|
_pccs = GetCcs(GetCF());
|
|
if(_pccs && _pccs->Include(' ', dup))
|
|
{
|
|
rc.left = u + dupRow +
|
|
GetPBorderWidth(prgCellParms[cCell-1].GetBrdrWidthRight()/2);
|
|
rc.right = rc.left + dup;
|
|
rc.top = vTop + GetPBorderWidth(prgCellParms->GetBrdrWidthTop());
|
|
rc.top++;
|
|
rc.bottom= vBot;
|
|
if(iDrawBottomLine & 1)
|
|
rc.bottom = vBot - GetPBorderWidth(prgCellParms->GetBrdrWidthBottom());
|
|
SetBkColor(_hdc, GetPed()->TxGetSysColor(COLOR_HIGHLIGHT));
|
|
EraseTextOut(_hdc, &rc, TRUE);
|
|
SetBkColor(_hdc, crSave);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(iDrawBottomLine) // Row bottom border
|
|
{
|
|
LONG dxp = GetPBorderWidth(prgCellParms->GetBrdrWidthLeft())/2;
|
|
LONG u1 = u - dxp;
|
|
LONG u2 = u + dupRow;
|
|
dxpLine = GetPBorderWidth(prgCellParms->GetBrdrWidthBottom());
|
|
cr = GetColorFromIndex(prgCellParms->GetColorIndexBottom(), TRUE, pPF);
|
|
if(iDrawBottomLine & 1) // Line width incl in cell height
|
|
{ // Don't draw left vertical lines
|
|
v -= dxpLine; // over bottom line
|
|
if(!dxpLine && !fHideGridlines)
|
|
v--; // Overlay cell bottom with gray
|
|
} // gridline
|
|
else // Line width not incl in cell height
|
|
{
|
|
dvp = dxpLine; // Return extra width due to bottom line
|
|
if(!dxpLine && !fHideGridlines)
|
|
dvp = 1;
|
|
vBot += dvp; // Set up outside vertical lines
|
|
}
|
|
brush.Draw(u1, v, u2, v, dxpLine, cr, fHideGridlines);
|
|
}
|
|
LONG uPrev, uCur = u;
|
|
LONG dul = 0;
|
|
LONG dup;
|
|
|
|
if(fRTLRow)
|
|
uCur = u + dupRow;
|
|
|
|
for(LONG i = cCell; i >= 0; i--)
|
|
{
|
|
// Draw cell side border
|
|
if(i) // Left border
|
|
{
|
|
icr = prgCellParms->GetColorIndexLeft();
|
|
dxlLine = prgCellParms->GetBrdrWidthLeft();
|
|
dxlLine = max(dxlLine, dxlLinePrevRight);
|
|
dxlLinePrevRight = prgCellParms->GetBrdrWidthRight();
|
|
}
|
|
else // Right border
|
|
{
|
|
prgCellParms--;
|
|
icr = prgCellParms->GetColorIndexRight();
|
|
dxlLine = dxlLinePrevRight;
|
|
v = vBot; // Be sure bottom right corner is square
|
|
}
|
|
cr = GetColorFromIndex(icr, TRUE, pPF);
|
|
dxpLine = GetPBorderWidth(dxlLine);
|
|
brush.Draw(uCur - dxpLine/2, vTop, uCur - dxpLine/2, v, dxpLine, cr, fHideGridlines);
|
|
|
|
if(i)
|
|
{
|
|
dul += GetCellWidth(prgCellParms->uCell); // Stay logical to
|
|
dup = LUtoDU(dul); // avoid roundoff
|
|
uPrev = uCur;
|
|
uCur = u + dup;
|
|
if(fRTLRow)
|
|
uCur = u + dupRow - dup;
|
|
if(!IsLowCell(prgCellParms->uCell))
|
|
{ // Cell top border
|
|
dxlLine = prgCellParms->GetBrdrWidthTop();
|
|
if(prgCellParmsAbove) // Choose thicker of this row's top
|
|
{ // & above row's bottom borders
|
|
iCell = prgCellParmsAbove->ICellFromUCell(dul, cCellAbove);
|
|
if(iCell >= 0)
|
|
{
|
|
LONG dxlAbove = prgCellParmsAbove[iCell].GetBrdrWidthBottom();
|
|
dxlLine = max(dxlLine, dxlAbove);
|
|
}
|
|
}
|
|
dxpLine = GetPBorderWidth(dxlLine);
|
|
cr = GetColorFromIndex(prgCellParms->GetColorIndexTop(), TRUE, pPF);
|
|
brush.Draw(uPrev, vTop, uCur, vTop, dxpLine, cr, fHideGridlines);
|
|
}
|
|
prgCellParms++;
|
|
}
|
|
}
|
|
if(prgCellParmsAbove && !pPFAbove->IsRtl())
|
|
{ // Draw more top borders if row
|
|
LONG dulAbove = 0; // above extends beyond current
|
|
for(i = cCellAbove; i > 0; i--) // row (LTR rows only for now)
|
|
{
|
|
dulAbove += GetCellWidth(prgCellParmsAbove->uCell);
|
|
if(dulAbove > dul)
|
|
{
|
|
dup = LUtoDU(dulAbove);
|
|
if(i == 1)
|
|
dup += GetPBorderWidth((prgCellParmsAbove->GetBrdrWidthRight()+1)/2);
|
|
uPrev = uCur;
|
|
uCur = u + dup;
|
|
dxpLine = GetPBorderWidth(prgCellParmsAbove->GetBrdrWidthBottom());
|
|
cr = GetColorFromIndex(prgCellParmsAbove->GetColorIndexBottom(), TRUE, pPFAbove);
|
|
brush.Draw(uPrev, vTop, uCur, vTop, dxpLine, cr, fHideGridlines);
|
|
}
|
|
prgCellParmsAbove++;
|
|
}
|
|
}
|
|
return dvp;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::UpdatePalette (pobj)
|
|
*
|
|
* @mfunc
|
|
* Stores palette information so that we can render any OLE objects
|
|
* correctly in a bitmap.
|
|
*/
|
|
void CRenderer::UpdatePalette(
|
|
COleObject *pobj) //@parm OLE object wrapper.
|
|
{
|
|
#ifndef NOPALETTE
|
|
LOGPALETTE *plogpalette = NULL;
|
|
LOGPALETTE *plogpaletteMerged;
|
|
IViewObject *pviewobj;
|
|
|
|
// Get IViewObject interface information so we can build a palette
|
|
// to render the object correctly.
|
|
if (((pobj->GetIUnknown())->QueryInterface(IID_IViewObject,
|
|
(void **) &pviewobj)) != NOERROR)
|
|
{
|
|
// Couldn't get it, so pretend this didn't happen
|
|
return;
|
|
}
|
|
|
|
// Get logical palette information from object
|
|
if(pviewobj->GetColorSet(DVASPECT_CONTENT, -1, NULL, NULL,
|
|
NULL, &plogpalette) != NOERROR || !plogpalette)
|
|
{
|
|
// Couldn't get it, so pretend this didn't happen
|
|
goto CleanUp;
|
|
}
|
|
|
|
if(!_plogpalette)
|
|
{ // No palette entries yet
|
|
_plogpalette = plogpalette; // Just use the one returned
|
|
goto CleanUp;
|
|
}
|
|
|
|
// We have had other palette entries. We just reallocate the table
|
|
// and put the newest entry on the end. This is crude, we might
|
|
// sweep the table and actually merge it. However, this code
|
|
// should be executed relatively infrequently and therefore, crude
|
|
// should be good enough.
|
|
|
|
// Allocate a new table - Note the " - 1" in the end has to do with
|
|
// the fact that LOGPALETTE is defined to have one entry already.
|
|
AssertSz(_plogpalette->palNumEntries + plogpalette->palNumEntries >= 1,
|
|
"CRenderer::UpdatePalette - invalid palettes to merge");
|
|
plogpaletteMerged = (LOGPALETTE *) CoTaskMemAlloc(sizeof(LOGPALETTE) +
|
|
((_plogpalette->palNumEntries + plogpalette->palNumEntries - 1) * sizeof(PALETTEENTRY)));
|
|
|
|
if(!plogpaletteMerged) // Memory allocation failed
|
|
goto CleanTempPalette; // Just pretend it didn't happen
|
|
|
|
// Copy in original table.
|
|
memcpy(&plogpaletteMerged->palPalEntry[0], &_plogpalette->palPalEntry[0],
|
|
_plogpalette->palNumEntries * sizeof(PALETTEENTRY));
|
|
|
|
// Put new data at end
|
|
memcpy(&plogpaletteMerged->palPalEntry[_plogpalette->palNumEntries],
|
|
&plogpalette->palPalEntry[0],
|
|
plogpalette->palNumEntries * sizeof(PALETTEENTRY));
|
|
|
|
// Set the version number and count
|
|
plogpaletteMerged->palVersion = plogpalette->palVersion;
|
|
plogpaletteMerged->palNumEntries = _plogpalette->palNumEntries
|
|
+ plogpalette->palNumEntries;
|
|
|
|
// Replace current palette table with merged table
|
|
CoTaskMemFree(_plogpalette);
|
|
_plogpalette = plogpaletteMerged;
|
|
|
|
CleanTempPalette:
|
|
CoTaskMemFree(plogpalette);
|
|
|
|
CleanUp:
|
|
|
|
// Release object we got since we don't need it any more
|
|
pviewobj->Release();
|
|
#endif // NOPALETTE
|
|
}
|
|
|
|
|
|
/*
|
|
* CRenderer::RenderChunk (&cchChunk, pchRender, cch)
|
|
*
|
|
* @mfunc
|
|
* Method reducing the length of the chunk (number of character
|
|
* rendered in one RenderText) and to render items interleaved in text.
|
|
*
|
|
* @rdesc
|
|
* TRUE if this method actually rendered the chunk,
|
|
* FALSE if it just updated cchChunk and rendering is still needed
|
|
*/
|
|
BOOL CRenderer::RenderChunk(
|
|
LONG & cchChunk, //@parm in: chunk cch; out: # chars rendered
|
|
// if return TRUE; else # chars yet to render
|
|
const WCHAR *pchRender, //@parm pchRender render up to cchChunk chars
|
|
LONG cch) //@parm # chars left to render on line
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::RenderChunk");
|
|
|
|
LONG cchValid = cchChunk;
|
|
LONG i;
|
|
const WCHAR *pchT;
|
|
|
|
// Search for object in chunk
|
|
for(pchT = pchRender, i = 0; i < cchValid && *pchT != WCH_EMBEDDING; i++)
|
|
pchT++;
|
|
|
|
if(i == 0)
|
|
{
|
|
// First character is object so display object
|
|
COleObject *pobj = GetObjectFromCp(GetCp());
|
|
if(pobj)
|
|
{
|
|
LONG dvpAscent, dvpDescent, dupObject;
|
|
pobj->MeasureObj(_dvpInch, _dupInch, dupObject, dvpAscent, dvpDescent, _li._dvpDescent, GetTflow());
|
|
|
|
if (W32->FUsePalette() && _li._fUseOffscreenDC && _pdp->IsMain())
|
|
{
|
|
// Keep track of palette needed for rendering bitmap
|
|
UpdatePalette(pobj);
|
|
}
|
|
|
|
SetClipLeftRight(dupObject);
|
|
|
|
pobj->DrawObj(_pdp, _dvpInch, _dupInch, _hdc, &GetClipRect(), _pdp->IsMetafile(), &_ptCur,
|
|
_li._dvpHeight - _li._dvpDescent, _li._dvpDescent, GetTflow());
|
|
|
|
_ptCur.u += dupObject;
|
|
_li._dup += dupObject;
|
|
}
|
|
cchChunk = 1;
|
|
// Both tabs and object code need to advance the run pointer past
|
|
// each character processed.
|
|
Move(1);
|
|
return TRUE;
|
|
}
|
|
cchChunk -= cchValid - i; // Limit chunk to char before object
|
|
|
|
// Handle other special characters
|
|
LONG cchT = 0;
|
|
for(pchT = pchRender; cchT < cchChunk; pchT++, cchT++)
|
|
{
|
|
switch(*pchT)
|
|
{
|
|
case EURO: //NOTE: (keithcu) Euro's need this special logic only for printing/metafiles
|
|
case TAB:
|
|
case NBSPACE:
|
|
case SOFTHYPHEN:
|
|
case NBHYPHEN:
|
|
case EMSPACE:
|
|
case ENSPACE:
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if(!cchT)
|
|
{
|
|
// First char is a tab, render it and any that follow
|
|
if(*pchT == TAB)
|
|
cchChunk = RenderTabs(cchChunk);
|
|
else
|
|
{
|
|
WCHAR chT = *pchT;
|
|
|
|
if (*pchT == NBSPACE)
|
|
chT = ' ';
|
|
else if (*pchT == NBHYPHEN || *pchT == SOFTHYPHEN)
|
|
chT = '-';
|
|
|
|
if(*pchT != SOFTHYPHEN || cch == 1) // Only render hyphens/blank at EOL
|
|
RenderText(&chT, 1);
|
|
|
|
Move(1); // Skip those within line
|
|
cchChunk = 1;
|
|
}
|
|
Assert (cchChunk > 0);
|
|
return TRUE;
|
|
}
|
|
cchChunk = cchT; // Update cchChunk not to incl trailing tabs
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetClipRect()
|
|
*
|
|
* @mfunc
|
|
* Helper to set clipping rect for the line
|
|
*/
|
|
void CRenderer::SetClipRect()
|
|
{
|
|
_rc = _rcRender;
|
|
|
|
_rc.top = _ptCur.v;
|
|
_rc.bottom = _rc.top + _li._dvpHeight;
|
|
|
|
_rc.top = max(_rc.top, _rcView.top);
|
|
_rc.bottom = min(_rc.bottom, _rcView.bottom);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetClipLeftRight (dup)
|
|
*
|
|
* @mfunc
|
|
* Helper to sets left and right of clipping/erase rect.
|
|
*
|
|
* @rdesc
|
|
* Sets _rc left and right
|
|
*/
|
|
void CRenderer::SetClipLeftRight(
|
|
LONG dup) //@parm Width of chunk to render
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::SetClipLeftRight");
|
|
|
|
//Nominal value
|
|
_rc.left = _ptCur.u;
|
|
_rc.right = _rc.left + dup;
|
|
|
|
//Constrain left and right based on rcView, rcRender
|
|
_rc.left = max(_rc.left, _rcRender.left);
|
|
|
|
_rc.right = max(_rc.right, _rc.left);
|
|
_rc.right = min(_rc.right, _rcRender.right);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::GetConvertMode()
|
|
*
|
|
* @mfunc
|
|
* Return the mode that should really be used in the RenderText call
|
|
*/
|
|
CONVERTMODE CRenderer::GetConvertMode()
|
|
{
|
|
CONVERTMODE cm = (CONVERTMODE)_pccs->_bConvertMode;
|
|
|
|
// For hack around ExtTextOutW Win95 problems.
|
|
if (cm != CVT_LOWBYTE && W32->OnWin9x() && (_pdp->IsMetafile() || _fEnhancedMetafileDC))
|
|
return CVT_WCTMB;
|
|
|
|
if (cm != CVT_LOWBYTE && _pdp->IsMetafile() && !_fEnhancedMetafileDC)
|
|
return CVT_WCTMB; // WMF cant store Unicode so we cant use ExtTextOutW
|
|
|
|
return cm;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderExtTextOut (ptuv, fuOptions, prc, pwchRun, cch, rgdxp)
|
|
*
|
|
* @mfunc
|
|
* Calls ExtTextOut and handles disabled text. There is duplicate logic in OlsDrawGlyphs, but
|
|
* the params are different so that was simplest way.
|
|
*
|
|
*/
|
|
extern ICustomTextOut *g_pcto;
|
|
void CRenderer::RenderExtTextOut(
|
|
POINTUV ptuv,
|
|
UINT fuOptions,
|
|
RECT *prc,
|
|
PCWSTR pch,
|
|
UINT cch,
|
|
const INT *rgdxp)
|
|
{
|
|
CONVERTMODE cm = GetConvertMode();
|
|
|
|
if (prc->left >= prc->right || prc->top >= prc->bottom)
|
|
return;
|
|
|
|
DWORD dwETOFlags = GetTflow();
|
|
if (_fFEFontOnNonFEWin9x)
|
|
dwETOFlags |= fETOFEFontOnNonFEWin9x;
|
|
if (_pccs->_fCustomTextOut)
|
|
dwETOFlags |= fETOCustomTextOut;
|
|
|
|
if(_fDisabled)
|
|
{
|
|
if(_crForeDisabled != _crShadowDisabled)
|
|
{
|
|
// The shadow should be offset by a hairline point, namely
|
|
// 3/4 of a point. Calculate how big this is in device units,
|
|
// but make sure it is at least 1 pixel.
|
|
DWORD offset = MulDiv(3, _dvpInch, 4*72);
|
|
offset = max(offset, 1);
|
|
|
|
// Draw shadow
|
|
SetTextColor(_crShadowDisabled);
|
|
|
|
POINTUV ptuvT = ptuv;
|
|
ptuvT.u += offset;
|
|
ptuvT.v += offset;
|
|
POINT pt;
|
|
_pdp->PointFromPointuv(pt, ptuvT, TRUE);
|
|
|
|
W32->REExtTextOut(cm, _pccs->_wCodePage, _hdc, pt.x, pt.y,
|
|
fuOptions, prc, pch, cch, rgdxp, dwETOFlags);
|
|
|
|
// Now set drawing mode to transparent
|
|
fuOptions &= ~ETO_OPAQUE;
|
|
SetBkMode(_hdc, TRANSPARENT);
|
|
}
|
|
SetTextColor(_crForeDisabled);
|
|
}
|
|
|
|
POINT pt;
|
|
_pdp->PointFromPointuv(pt, ptuv, TRUE);
|
|
|
|
W32->REExtTextOut(cm, _pccs->_wCodePage, _hdc, pt.x, pt.y, fuOptions, prc, pch, cch, rgdxp, dwETOFlags);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderText (pch, cch)
|
|
*
|
|
* @mfunc
|
|
* Render text in the current context of this CRenderer
|
|
*
|
|
* @devnote
|
|
* Renders text only: does not do tabs or OLE objects
|
|
*/
|
|
void CRenderer::RenderText(
|
|
const WCHAR *pch, //@parm Text to render
|
|
LONG cch) //@parm Length of text to render
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::RenderText");
|
|
|
|
LONG dvp, cchT;
|
|
|
|
// Variables used for calculating length of underline.
|
|
LONG dupSelPastEOL = 0;
|
|
BOOL fCell = FALSE;
|
|
UINT fuOptions = _pdp->IsMain() ? ETO_CLIPPED : 0;
|
|
LONG dup;
|
|
LONG dupT;
|
|
CTempBuf rgdu;
|
|
|
|
//Reset clip rectangle to greater of view/render rectangle
|
|
_rc.left = _rcRender.left;
|
|
_rc.right = _rcRender.right;
|
|
|
|
// Trim all nondisplayable linebreaking chars off end
|
|
while(cch && IsASCIIEOP(pch[cch - 1]))
|
|
cch--;
|
|
|
|
if(cch && pch[cch-1] == CELL)
|
|
{
|
|
fCell = TRUE;
|
|
cch--;
|
|
}
|
|
|
|
int *pdu = (int *)rgdu.GetBuf(cch * sizeof(int));
|
|
|
|
// Measure width of text to write so next point to write can be
|
|
// calculated.
|
|
dup = 0;
|
|
|
|
for(cchT = 0;
|
|
cchT < cch && dup < _rc.right - _ptCur.u;
|
|
cchT++)
|
|
{
|
|
dupT = 0;
|
|
if (!_pccs->Include(*pch, dupT))
|
|
{
|
|
TRACEERRSZSC("CRenderer::RenderText(): Error filling CCcs", E_FAIL);
|
|
return;
|
|
}
|
|
|
|
if (pdu)
|
|
*pdu++ = dupT;
|
|
pch++;
|
|
dup += dupT;
|
|
}
|
|
|
|
// Go back to start of chunk
|
|
cch = cchT;
|
|
pch -= cch;
|
|
if (pdu)
|
|
pdu -= cch;
|
|
|
|
if(_fLastChunk && _fSelectToEOL && _li._cchEOP)
|
|
{
|
|
// Use the width of the current font's space to highlight
|
|
if(!_pccs->Include(' ', dupT))
|
|
{
|
|
TRACEERRSZSC("CRenderer::RenderText(): Error no length of space", E_FAIL);
|
|
return;
|
|
}
|
|
dupSelPastEOL = dupT;
|
|
dup += dupSelPastEOL;
|
|
_fSelectToEOL = FALSE; // Reset the flag
|
|
}
|
|
|
|
_li._dup += dup;
|
|
|
|
// Setup for drawing selections via ExtTextOut.
|
|
if(_fSelected || _crBackground != _crCurBackground)
|
|
{
|
|
SetClipLeftRight(dup);
|
|
if(_fSelected && fCell)
|
|
{
|
|
// Needs work, but this is a start. _rcRender has the cell
|
|
// boundaries, so we need to use them on the right calls.
|
|
_rc.right = _rcRender.right;
|
|
}
|
|
fuOptions = ETO_CLIPPED | ETO_OPAQUE;
|
|
}
|
|
|
|
dvp = _ptCur.v + _li._dvpHeight - _li._dvpDescent + _pccs->_yDescent - _pccs->_yHeight;
|
|
|
|
LONG dvpOffset, dvpAdjust;
|
|
_pccs->GetOffset(GetCF(), _dvpInch, &dvpOffset, &dvpAdjust);
|
|
dvp -= dvpOffset + dvpAdjust;
|
|
|
|
POINTUV ptuv = {_ptCur.u, dvp};
|
|
RECT rc;
|
|
_pdp->RectFromRectuv(rc, _rc);
|
|
|
|
//For 1 char runs, we may need to swap the character we output.
|
|
WCHAR ch;
|
|
if (cch == 1)
|
|
{
|
|
switch(*pch)
|
|
{
|
|
case EMSPACE:
|
|
case ENSPACE:
|
|
ch = ' ';
|
|
pch = &ch;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RenderExtTextOut(ptuv, fuOptions, &rc, pch, cch, pdu);
|
|
|
|
// Calculate width to draw for underline/strikeout
|
|
// FUTURE (keithcu) Don't underline trailing spaces?
|
|
if(_bUnderlineType != CFU_UNDERLINENONE || _fStrikeOut)
|
|
{
|
|
LONG dupToDraw = dup - dupSelPastEOL;
|
|
LONG upStart = _ptCur.u;
|
|
LONG upEnd = upStart + dupToDraw;
|
|
|
|
upStart = max(upStart, _rcRender.left);
|
|
|
|
upEnd = min(upEnd, _rcRender.right);
|
|
|
|
dupToDraw = upEnd - upStart;
|
|
|
|
if(dupToDraw > 0)
|
|
{
|
|
LONG y = _ptCur.v + _li._dvpHeight - _li._dvpDescent;
|
|
|
|
y -= dvpOffset + dvpAdjust;
|
|
|
|
// Render underline if required
|
|
if(_bUnderlineType != CFU_UNDERLINENONE)
|
|
RenderUnderline(upStart, y + _pccs->_dyULOffset, dupToDraw, _pccs->_dyULWidth);
|
|
|
|
// Render strikeout if required
|
|
if(_fStrikeOut)
|
|
RenderStrikeOut(upStart, y + _pccs->_dySOOffset, dupToDraw, _pccs->_dySOWidth);
|
|
}
|
|
}
|
|
|
|
_fSelected = FALSE;
|
|
_ptCur.u += dup; // Update current point
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderTabs (cchMax)
|
|
*
|
|
* @mfunc
|
|
* Render a span of zero or more tab characters in chunk *this
|
|
*
|
|
* @rdesc
|
|
* number of tabs rendered
|
|
*
|
|
* @devnote
|
|
* *this is advanced by number of tabs rendered
|
|
* MS - tabs should be rendered using opaquing rect of adjacent string
|
|
*/
|
|
LONG CRenderer::RenderTabs(
|
|
LONG cchMax) //@parm Max cch to render (cch in chunk)
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::RenderTabs");
|
|
|
|
LONG cch = cchMax;
|
|
LONG ch = GetChar();
|
|
LONG chPrev = 0;
|
|
LONG upTab, upTabs;
|
|
|
|
for(upTabs = 0; cch && ch == TAB; cch--)
|
|
{
|
|
upTab = MeasureTab(ch);
|
|
_li._dup += upTab; // Advance internal width
|
|
upTabs += upTab; // Accumulate width of tabbed
|
|
Move(1); // region
|
|
chPrev = ch;
|
|
ch = GetChar();
|
|
}
|
|
|
|
if(_li._dup > _dupLine)
|
|
{
|
|
upTabs = 0;
|
|
_li._dup = _dupLine;
|
|
}
|
|
|
|
if(upTabs)
|
|
{
|
|
LONG dup = 0;
|
|
LONG upGap = 0;
|
|
|
|
if(_fSelected && chPrev == CELL && ch != CR && _pPF->InTable())
|
|
{
|
|
LONG cpSelMin, cpSelMost;
|
|
GetPed()->GetSelRangeForRender(&cpSelMin, &cpSelMost);
|
|
if(GetCp() == cpSelMin || GetCp() == cpSelMost)
|
|
{
|
|
upGap = LUtoDU(_pPF->_dxOffset);
|
|
if(GetCp() == cpSelMost)
|
|
{
|
|
dup = upGap;
|
|
upGap = 0;
|
|
}
|
|
}
|
|
}
|
|
SetClipLeftRight(upTabs - dup);
|
|
if(_rc.left < _rc.right) // Something to erase
|
|
{
|
|
if(_fSelected) // Use selection background color
|
|
{
|
|
COLORREF cr = GetPed()->TxGetSysColor(COLOR_HIGHLIGHT);
|
|
if (!UseXOR(cr))
|
|
|
|
{
|
|
::SetBkColor (_hdc, cr);
|
|
_crCurTextColor = GetPed()->TxGetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
}
|
|
else
|
|
{
|
|
const CCharFormat* pCF = GetCF();
|
|
::SetBkColor (_hdc, (pCF->_dwEffects & CFE_AUTOBACKCOLOR) ?
|
|
_crBackground ^ RGB_WHITE : pCF->_crBackColor ^ RGB_WHITE);
|
|
_crCurTextColor = (pCF->_dwEffects & CFE_AUTOCOLOR) ?
|
|
_crTextColor ^ RGB_WHITE : pCF->_crTextColor ^ RGB_WHITE;
|
|
}
|
|
}
|
|
|
|
// Paint background with appropriate color
|
|
if(_fSelected || _crBackground != _crCurBackground)
|
|
EraseTextOut(_hdc, &_rc, TRUE);
|
|
|
|
// Render underline if required
|
|
dup = _rc.right - _rc.left;
|
|
LONG vp = _ptCur.v + _li._dvpHeight - _li._dvpDescent;
|
|
|
|
LONG dvpOffset, dvpAdjust;
|
|
_pccs->GetOffset(GetCF(), _dvpInch, &dvpOffset, &dvpAdjust);
|
|
vp -= dvpOffset + dvpAdjust;
|
|
|
|
if(_bUnderlineType != CFU_UNDERLINENONE)
|
|
RenderUnderline(_rc.left, vp + _pccs->_dyULOffset, dup, _pccs->_dyULWidth);
|
|
|
|
// Render strikeout if required
|
|
if(_fStrikeOut)
|
|
RenderStrikeOut(_rc.left, vp + _pccs->_dySOOffset, dup, _pccs->_dySOWidth);
|
|
|
|
if(_fSelected) // Restore colors
|
|
::SetBkColor(_hdc, _crCurBackground);
|
|
}
|
|
_ptCur.u += upTabs; // Update current point
|
|
}
|
|
return cchMax - cch; // Return # tabs rendered
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetNewFont()
|
|
*
|
|
* @mfunc
|
|
* Select appropriate font and color in the _hdc based on the
|
|
* current character format. Also sets the background color
|
|
* and mode.
|
|
*
|
|
* @rdesc
|
|
* TRUE if it succeeds
|
|
*
|
|
* @devnote
|
|
* The calling chain must be protected by a CLock, since this present
|
|
* routine access the global (shared) FontCache facility.
|
|
*/
|
|
BOOL CRenderer::SetNewFont()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::SetNewFont");
|
|
|
|
const CCharFormat *pCF = GetCF();
|
|
DWORD dwEffects = pCF->_dwEffects;
|
|
BOOL fDisplay = fDisplayDC();
|
|
|
|
// Release previous font in use
|
|
if(_pccs)
|
|
_pccs->Release();
|
|
|
|
Assert(_fTarget == FALSE);
|
|
_pccs = GetCcs(pCF);
|
|
|
|
if(!_pccs)
|
|
{
|
|
TRACEERRSZSC("CRenderer::SetNewFont(): no CCcs", E_FAIL);
|
|
return FALSE;
|
|
}
|
|
|
|
// Select font in _hdc
|
|
AssertSz(_pccs->_hfont, "CRenderer::SetNewFont _pccs->_hfont is NULL");
|
|
|
|
SetFontAndColor(pCF);
|
|
|
|
// Assume no underlining
|
|
_bUnderlineType = CFU_UNDERLINENONE;
|
|
|
|
// We want to draw revision marks and native hyperlinks with underlining,
|
|
// so just fake out our font information.
|
|
if((dwEffects & (CFE_UNDERLINE | CFE_REVISED)) ||
|
|
(dwEffects & (CFE_LINKPROTECTED | CFE_LINK)) == CFE_LINK ||
|
|
fDisplay && GetTmpUnderline(pCF->_sTmpDisplayAttrIdx))
|
|
{
|
|
if (dwEffects & CFE_LINK)
|
|
SetupUnderline(CFU_UNDERLINE, 0);
|
|
else
|
|
{
|
|
BYTE bTmpUnderlineIdx = 0;
|
|
|
|
if (fDisplay)
|
|
bTmpUnderlineIdx = GetTmpUnderline(pCF->_sTmpDisplayAttrIdx);
|
|
|
|
if (bTmpUnderlineIdx)
|
|
{
|
|
COLORREF crTmpUnderline;
|
|
|
|
GetTmpUnderlineColor(pCF->_sTmpDisplayAttrIdx, crTmpUnderline);
|
|
SetupUnderline(bTmpUnderlineIdx, 0, crTmpUnderline);
|
|
}
|
|
else
|
|
SetupUnderline(pCF->_bUnderlineType, pCF->_bUnderlineColor);
|
|
}
|
|
}
|
|
|
|
_fStrikeOut = (dwEffects & (CFE_STRIKEOUT | CFE_DELETED)) != 0;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetupUnderline (bULType, bULColorIdx, crULColor)
|
|
*
|
|
* @mfunc
|
|
* Setup internal variables for underlining
|
|
*/
|
|
void CRenderer::SetupUnderline(
|
|
BYTE bULType,
|
|
BYTE bULColorIdx,
|
|
COLORREF crULColor)
|
|
{
|
|
_bUnderlineType = bULType;
|
|
_crUnderlineClr = crULColor;
|
|
|
|
if (bULColorIdx)
|
|
GetPed()->GetEffectColor(bULColorIdx, &_crUnderlineClr);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::UseXOR (cr)
|
|
*
|
|
* @mfunc
|
|
* Return if reverse video selection should be used for the nominal
|
|
* selection color cr. RichEdit 1.0 mode always uses reverse video
|
|
* selection. Else use it if cr is too close to the current window
|
|
* background.
|
|
*
|
|
* @rdesc
|
|
* Return if caller should use reverse video for cr
|
|
*/
|
|
BOOL CRenderer::UseXOR(
|
|
COLORREF cr) //@parm Color to compare _crBackground to
|
|
{
|
|
return GetPed()->Get10Mode() ||
|
|
(_crBackground != GetPed()->TxGetSysColor(COLOR_WINDOW) &&
|
|
IsTooSimilar(_crBackground, cr));
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetFontAndColor (pCF)
|
|
*
|
|
* @mfunc
|
|
* Select appropriate font and color in the _hdc based on the
|
|
* current character format. Also sets the background color
|
|
* and mode.
|
|
*/
|
|
void CRenderer::SetFontAndColor(
|
|
const CCharFormat *pCF) //@parm Character format for colors
|
|
{
|
|
CTxtEdit *ped = GetPed();
|
|
|
|
_fDisabled = FALSE;
|
|
if((pCF->_dwEffects & (CFE_AUTOCOLOR | CFE_DISABLED))
|
|
== (CFE_AUTOCOLOR | CFE_DISABLED))
|
|
{
|
|
_fDisabled = TRUE;
|
|
|
|
_crForeDisabled = ped->TxGetSysColor(COLOR_3DSHADOW);
|
|
_crShadowDisabled = ped->TxGetSysColor(COLOR_3DHILIGHT);
|
|
}
|
|
|
|
_fFEFontOnNonFEWin9x = FALSE;
|
|
if (IsFECharRep(pCF->_iCharRep) && W32->OnWin9x() && !W32->OnWin9xFE())
|
|
_fFEFontOnNonFEWin9x = TRUE;
|
|
|
|
SelectFont(_hdc, _pccs->_hfont);
|
|
|
|
// Compute height and descent if not yet done
|
|
if(_li._dvpHeight == -1)
|
|
{
|
|
SHORT dvpAdjustFE = _pccs->AdjustFEHeight(!fUseUIFont() && ped->_pdp->IsMultiLine());
|
|
// Note: this assumes plain text
|
|
// Should be used only for single line control
|
|
_li._dvpHeight = _pccs->_yHeight + (dvpAdjustFE << 1);
|
|
_li._dvpDescent = _pccs->_yDescent + dvpAdjustFE;
|
|
}
|
|
SetTextColor(GetTextColor(pCF)); // Set current text color
|
|
|
|
COLORREF cr;
|
|
|
|
if(_fSelected) // Set current background color
|
|
{
|
|
cr = GetPed()->TxGetSysColor(COLOR_HIGHLIGHT);
|
|
if (UseXOR(cr))
|
|
{
|
|
// There are 2 cases to be concerned with
|
|
// 1) if the background color is the same as the selection color or
|
|
// 2) if 1.0 Window and the background color is NOT system default
|
|
cr = (pCF->_dwEffects & CFE_AUTOBACKCOLOR) ?
|
|
_crBackground ^ RGB_WHITE : pCF->_crBackColor ^ RGB_WHITE;
|
|
}
|
|
}
|
|
else if(!fDisplayDC() ||
|
|
!(GetTmpBackColor(pCF->_sTmpDisplayAttrIdx, cr))) // Any temp. background color?
|
|
{ // No, use regular background color
|
|
if(pCF->_dwEffects & CFE_AUTOBACKCOLOR)
|
|
cr = _crBackground;
|
|
else // Text run has some kind of back color
|
|
cr = pCF->_crBackColor;
|
|
}
|
|
|
|
if(cr != _crCurBackground)
|
|
{
|
|
::SetBkColor(_hdc, cr); // Tell window background color
|
|
_crCurBackground = cr; // Remember current background color
|
|
_fBackgroundColor = _crBackground != cr; // Change render settings so we
|
|
} // won't fill with background color
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetDefaultBackColor (cr)
|
|
*
|
|
* @mfunc
|
|
* Select given background color in the _hdc. Used for setting
|
|
* background color in table cells.
|
|
*/
|
|
void CRenderer::SetDefaultBackColor(
|
|
COLORREF cr) //@parm Background color to use
|
|
{
|
|
if(cr == tomAutoColor)
|
|
cr = GetPed()->TxGetBackColor(); // Printer needs work...
|
|
|
|
if(_crBackground != cr)
|
|
{
|
|
_crCurBackground = _crBackground = cr;
|
|
::SetBkColor(_hdc, cr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetDefaultTextColor (cr)
|
|
*
|
|
* @mfunc
|
|
* Select given foreground color in the _hdc. Used for setting
|
|
* text color in table cells.
|
|
*/
|
|
void CRenderer::SetDefaultTextColor(
|
|
COLORREF cr) //@parm Background color to use
|
|
{
|
|
if(cr == tomAutoColor)
|
|
cr = GetPed()->TxGetForeColor(); // Printer needs work...
|
|
|
|
if(_crTextColor != cr)
|
|
{
|
|
_crCurTextColor = _crTextColor = cr;
|
|
::SetTextColor(_hdc, cr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::SetTextColor (cr)
|
|
*
|
|
* @mfunc
|
|
* Select given text color in the _hdc
|
|
* Used to maintain _crCurTextColor cache
|
|
*/
|
|
void CRenderer::SetTextColor(
|
|
COLORREF cr) //@parm color to set in the dc
|
|
{
|
|
if(cr != _crCurTextColor)
|
|
{
|
|
_crCurTextColor = cr;
|
|
::SetTextColor(_hdc, cr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::GetTextColor(pCF)
|
|
*
|
|
* @mfunc
|
|
* Return text color for pCF. Depends on _bRevAuthor, display tech
|
|
*
|
|
* FUTURE (keithcu) It might be nice to have black or blue selected text be
|
|
* white, but to have all other colors stay their other colors. What do we
|
|
* do if the backcolor is blue??
|
|
*
|
|
* @rdesc
|
|
* text color
|
|
*/
|
|
COLORREF CRenderer::GetTextColor(
|
|
const CCharFormat *pCF) //@parm CCharFormat specifying text color
|
|
{
|
|
if(_fSelected)
|
|
{
|
|
// There are 2 cases where XOR for selection is needed
|
|
// 1) if the background is the same as the selection background
|
|
// 2) if 1.0 window and the background isn't the system default window
|
|
// background color
|
|
|
|
// if this doesn't match the above case just return the cr
|
|
if (!UseXOR(GetPed()->TxGetSysColor(COLOR_HIGHLIGHT)))
|
|
return GetPed()->TxGetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
|
|
// xor the current text color for the selected text color
|
|
return (pCF->_dwEffects & CFE_AUTOCOLOR) ? _crTextColor ^ RGB_WHITE :
|
|
pCF->_crTextColor ^ RGB_WHITE;
|
|
}
|
|
|
|
// The following could be generalized to return a different color for
|
|
// links that have been visited for this text instance (need to define
|
|
// extra CCharFormat::_dwEffects internal flag to ID these links)
|
|
if((pCF->_dwEffects & (CFE_LINK | CFE_LINKPROTECTED)) == CFE_LINK)
|
|
{
|
|
// Blue doesnt show up very well against dark backgrounds.
|
|
// In these situations, use the system selected text color.
|
|
COLORREF crBackground = (pCF->_dwEffects & CFE_AUTOBACKCOLOR)
|
|
? _crBackground : pCF->_crBackColor;
|
|
|
|
if (IsTooSimilar(crBackground, RGB_BLACK) || IsTooSimilar(crBackground, RGB_BLUE))
|
|
{
|
|
COLORREF crHighlightText = GetPed()->TxGetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
if (IsTooSimilar(crBackground, crHighlightText))
|
|
{
|
|
// Background is similar to highlight, use window text color
|
|
return GetPed()->TxGetSysColor(COLOR_WINDOWTEXT);
|
|
}
|
|
else
|
|
{
|
|
return crHighlightText;
|
|
}
|
|
}
|
|
// If not high contrast, fall through
|
|
}
|
|
|
|
BOOL fDisplay = fDisplayDC();
|
|
#ifndef NODRAFTMODE
|
|
// Use draft mode text color only for displays
|
|
if (GetPed()->_fDraftMode && (!_hdc || fDisplay))
|
|
{
|
|
SHORT iFont;
|
|
SHORT yHeight;
|
|
QWORD qwFontSig;
|
|
COLORREF crColor;
|
|
|
|
if (W32->GetDraftModeFontInfo(iFont, yHeight, qwFontSig, crColor))
|
|
return crColor;
|
|
}
|
|
#endif
|
|
|
|
// If we did not return the URL color via draft mode or the high contrast check, do it now
|
|
if((pCF->_dwEffects & (CFE_LINK | CFE_LINKPROTECTED)) == CFE_LINK)
|
|
return RGB_BLUE;
|
|
|
|
if(pCF->_bRevAuthor) // Rev author
|
|
{
|
|
// Limit color of rev authors to 0 through 7.
|
|
return rgcrRevisions[(pCF->_bRevAuthor - 1) & REVMASK];
|
|
}
|
|
|
|
COLORREF cr = (pCF->_dwEffects & CFE_AUTOCOLOR) ? _crTextColor : pCF->_crTextColor;
|
|
COLORREF crTmpTextColor;
|
|
|
|
if(fDisplay && GetTmpTextColor(pCF->_sTmpDisplayAttrIdx, crTmpTextColor))
|
|
cr = crTmpTextColor;
|
|
|
|
if(cr == RGB_WHITE) // Text is white
|
|
{
|
|
COLORREF crBackground = (pCF->_dwEffects & CFE_AUTOBACKCOLOR)
|
|
? _crBackground : pCF->_crBackColor;
|
|
|
|
COLORREF crTmpBackground;
|
|
if(fDisplay && GetTmpBackColor(pCF->_sTmpDisplayAttrIdx, crTmpBackground))
|
|
crBackground = crTmpBackground;
|
|
|
|
if(crBackground != RGB_WHITE)
|
|
{
|
|
// Background color isn't white, so white text is probably
|
|
// visible unless display device is only black/white. So we
|
|
// switch to black text on such devices.
|
|
if (GetDeviceCaps(_hdc, NUMCOLORS) == 2 ||
|
|
GetDeviceCaps(_hdc, TECHNOLOGY) == DT_PLOTTER)
|
|
{
|
|
cr = RGB_BLACK;
|
|
}
|
|
}
|
|
}
|
|
return cr;
|
|
}
|
|
|
|
extern BOOL g_OLSBusy;
|
|
|
|
/*
|
|
* CRenderer::StartLine(&li, fLastLine, &cpSelMin, &cpSelMost, &dup, &dvp)
|
|
*
|
|
* @mfunc
|
|
* Render possible outline symbol and bullet if at start of line
|
|
*
|
|
* @rdesc
|
|
* hdcSave if using offscreen DC
|
|
*/
|
|
HDC CRenderer::StartLine(
|
|
CLine & li, //@parm Line to render
|
|
BOOL fLastLine, //@parm True if last line in layout
|
|
LONG & cpSelMin, //@parm Out parm for current selection cpMin
|
|
LONG & cpSelMost, //@parm Out parm for current selection cpMost
|
|
LONG & dup, //@parm Offset to u
|
|
LONG & dvp) //@parm Offset to v
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::StartLine");
|
|
BOOL fDrawBack = !(GetCF()->_dwEffects & CFE_AUTOBACKCOLOR) && GetPed()->_fExtendBackColor;
|
|
RECTUV rcErase = _rcRender;
|
|
_fEraseOnFirstDraw = FALSE;
|
|
|
|
GetPed()->GetSelRangeForRender(&cpSelMin, &cpSelMost);
|
|
if(cpSelMost != cpSelMin && cpSelMost == GetCp())
|
|
_fSelectedPrev = TRUE;
|
|
|
|
LONG cpMost = GetCp() + _li._cch;
|
|
COLORREF crPrev = 0xFFFFFFFF;
|
|
BOOL fUseSelColors = FALSE;
|
|
|
|
if (cpMost <= cpSelMost && cpMost - 1 >= cpSelMin &&
|
|
_pPF->InTable() && _fRenderSelection)
|
|
{
|
|
CTxtPtr tp(_rpTX);
|
|
tp.SetCp(cpMost);
|
|
if(tp.GetPrevChar() == CELL)
|
|
{
|
|
fUseSelColors = TRUE;
|
|
crPrev = ::SetBkColor(GetDC(), GetPed()->TxGetSysColor(COLOR_HIGHLIGHT));
|
|
}
|
|
}
|
|
SetClipRect();
|
|
|
|
HDC hdcSave = NULL;
|
|
dup = dvp = 0;
|
|
if(li._cch > 0 && li._fUseOffscreenDC)
|
|
{
|
|
// Set up an off-screen DC if we can. Note that if this fails,
|
|
// we just use the regular DC which won't look as nice but
|
|
// will at least display something readable.
|
|
hdcSave = SetupOffscreenDC(dup, dvp, fLastLine);
|
|
if(li._fOffscreenOnce)
|
|
li._fUseOffscreenDC = li._fOffscreenOnce = FALSE;
|
|
}
|
|
|
|
rcErase.top = _ptCur.v;
|
|
rcErase.bottom = min(rcErase.top + _li._dvpHeight, _rcRender.bottom);
|
|
|
|
//If first line, erase to edge of rcRender
|
|
if (rcErase.top <= _rcView.top)
|
|
rcErase.top = _rcRender.top;
|
|
|
|
//If last line, erase to bottom edge of rcRender
|
|
if (fLastLine)
|
|
rcErase.bottom = _rcRender.bottom;
|
|
|
|
if (_fErase && !fDrawBack)
|
|
{
|
|
if(g_OLSBusy && IsSimpleBackground() && !fUseSelColors)
|
|
{
|
|
_fEraseOnFirstDraw = TRUE;
|
|
_rcErase = rcErase;
|
|
}
|
|
else
|
|
EraseTextOut(GetDC(), &rcErase);
|
|
}
|
|
|
|
// Fill line with background color if we are in fExtendBackColor mode
|
|
if (fDrawBack || fUseSelColors)
|
|
{
|
|
// Capture old color so we reset it to what it was when we're finished
|
|
COLORREF crOld = 0;
|
|
if(fDrawBack)
|
|
crOld = ::SetBkColor(GetDC(), GetCF()->_crBackColor);
|
|
EraseTextOut(GetDC(), &_rc);
|
|
|
|
// Reset background color to old color
|
|
if(fDrawBack)
|
|
::SetBkColor(GetDC(), crOld);
|
|
|
|
//Erase the remainder of the background area
|
|
if (_fErase)
|
|
{
|
|
RECTUV rcTemp = rcErase;
|
|
//Erase the top part if necessary
|
|
if (rcErase.top < _rc.top)
|
|
{
|
|
rcTemp.bottom = _rc.top;
|
|
EraseTextOut(GetDC(), &rcTemp);
|
|
}
|
|
|
|
//Erase the left and right parts if necessary
|
|
rcTemp.top = _rc.top;
|
|
rcTemp.bottom = _rc.bottom;
|
|
if (rcErase.left < _rc.left)
|
|
{
|
|
rcTemp.right = _rc.left;
|
|
EraseTextOut(GetDC(), &rcTemp);
|
|
}
|
|
if (rcErase.right > _rc.right)
|
|
{
|
|
rcTemp.left = _rc.right;
|
|
rcTemp.right = rcErase.right;
|
|
EraseTextOut(GetDC(), &rcTemp);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(crPrev != 0xFFFFFFFF)
|
|
::SetBkColor(GetDC(), crPrev);
|
|
|
|
if(IsRich() && _li._fFirstInPara && _pPF)
|
|
{
|
|
if(IsInOutlineView())
|
|
RenderOutlineSymbol();
|
|
|
|
if(_pPF->_wNumbering && !fUseLineServices())
|
|
RenderBullet();
|
|
}
|
|
|
|
// Reset format if there is special background color for previous line.
|
|
// Otherwise, current line with the same format will not re-paint with the
|
|
// special background color
|
|
if (_fBackgroundColor)
|
|
{
|
|
_iFormat = -10; // Reset to invalid format
|
|
|
|
// Assume that there is no special background color for the line
|
|
_fBackgroundColor = FALSE;
|
|
}
|
|
|
|
// Handle setting background color. If the current background
|
|
// color is different than the default, we need to set the background
|
|
// to this because the end of line processing reset the color so
|
|
// that opaquing would work.
|
|
if(_crBackground != _crCurBackground)
|
|
{
|
|
// Tell the window the background color
|
|
::SetBkColor(_hdc, _crCurBackground);
|
|
_fBackgroundColor = TRUE;
|
|
}
|
|
|
|
return hdcSave;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::EraseToBottom()
|
|
*
|
|
* @mfunc
|
|
* Erase from current display position to bottom of render RECT.
|
|
* Used by tables for last line in a display
|
|
*/
|
|
void CRenderer::EraseToBottom()
|
|
{
|
|
if(_ptCur.v < _rcRender.bottom)
|
|
{
|
|
RECTUV rcErase = _rcRender;
|
|
rcErase.top = _ptCur.v;
|
|
EraseTextOut(GetDC(), &rcErase);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderOutlineSymbol()
|
|
*
|
|
* @mfunc
|
|
* Render outline symbol for current paragraph
|
|
*
|
|
* @rdesc
|
|
* TRUE if outline symbol rendered
|
|
*/
|
|
BOOL CRenderer::RenderOutlineSymbol()
|
|
{
|
|
AssertSz(IsInOutlineView(),
|
|
"CRenderer::RenderOutlineSymbol called when not in outline view");
|
|
|
|
HBITMAP hbitmap;
|
|
LONG height;
|
|
LONG width;
|
|
LONG up = _ptCur.u - _li._upStart + LUtoDU(lDefaultTab/2 * _pPF->_bOutlineLevel);
|
|
LONG vp = _ptCur.v;
|
|
|
|
if(!g_hbitmapSubtext && InitializeOutlineBitmaps() != NOERROR)
|
|
return FALSE;
|
|
|
|
HDC hMemDC = CreateCompatibleDC(_hdc); // REVIEW: performance
|
|
|
|
if(!hMemDC)
|
|
return FALSE; //REVIEW: out of memory
|
|
|
|
if(_pPF->_bOutlineLevel & 1) // Subtext
|
|
{
|
|
width = BITMAP_WIDTH_SUBTEXT;
|
|
height = BITMAP_HEIGHT_SUBTEXT;
|
|
hbitmap = g_hbitmapSubtext;
|
|
}
|
|
else // Heading
|
|
{
|
|
width = BITMAP_WIDTH_HEADING;
|
|
height = BITMAP_HEIGHT_HEADING;
|
|
hbitmap = g_hbitmapEmptyHeading;
|
|
|
|
CPFRunPtr rp(*this); // Check next PF for other
|
|
LONG cch = _li._cch; // outline symbols
|
|
|
|
if(_li._cch < rp.GetCchLeft()) // Set cch = count to heading
|
|
{ // EOP
|
|
CTxtPtr tp(_rpTX);
|
|
cch = tp.FindEOP(tomForward);
|
|
}
|
|
rp.Move(cch); // Go to next paragraph
|
|
if(rp.IsCollapsed())
|
|
hbitmap = g_hbitmapCollapsedHeading;
|
|
|
|
else if(_pPF->_bOutlineLevel < rp.GetOutlineLevel())
|
|
hbitmap = g_hbitmapExpandedHeading;
|
|
}
|
|
|
|
if(!hbitmap)
|
|
return FALSE;
|
|
|
|
HBITMAP hbitmapDefault = (HBITMAP)SelectObject(hMemDC, hbitmap);
|
|
|
|
// REVIEW: what if the background color changes? Also, use a TT font
|
|
// for symbols
|
|
LONG dvpSymbol = _pdp->Zoom(height);
|
|
LONG dvp = _li._dvpHeight - _li._dvpDescent - dvpSymbol;
|
|
|
|
if(dvp > 0)
|
|
dvp /= 2;
|
|
else
|
|
dvp = -dvp;
|
|
|
|
POINTUV ptuv = {up, vp + dvp};
|
|
POINT pt;
|
|
_pdp->PointFromPointuv(pt, ptuv);
|
|
StretchBlt(_hdc, pt.x, pt.y, _pdp->Zoom(width), dvpSymbol, hMemDC, 0, 0, width, height, SRCCOPY);
|
|
|
|
SelectObject(hMemDC, hbitmapDefault);
|
|
DeleteDC(hMemDC);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderBullet()
|
|
*
|
|
* @mfunc
|
|
* Render bullet at start of line
|
|
*
|
|
* @rdesc
|
|
* TRUE if this method succeeded
|
|
*/
|
|
BOOL CRenderer::RenderBullet()
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CRenderer::RenderBullet");
|
|
|
|
AssertSz(_pPF->_wNumbering,
|
|
"CRenderer::RenderBullet called for non-bullet");
|
|
|
|
// Width of the bullet character
|
|
LONG dup;
|
|
|
|
// FUTURE: Unicode bullet is L'\x2022' We want to migrate to this and
|
|
// other bullets
|
|
LONG cch;
|
|
CCharFormat CF;
|
|
WCHAR szBullet[CCHMAXNUMTOSTR];
|
|
|
|
CCcs *pccs = GetCcsBullet(&CF);
|
|
|
|
if(!pccs) // Bullet is suppressed because
|
|
return TRUE; // preceding EOP is VT
|
|
|
|
if(_pccs)
|
|
_pccs->Release();
|
|
|
|
_pccs = pccs;
|
|
|
|
// Default to no underline
|
|
_bUnderlineType = CFU_UNDERLINENONE;
|
|
|
|
if(_pPF->IsListNumbered() && CF._dwEffects & CFE_UNDERLINE)
|
|
SetupUnderline(CF._bUnderlineType, CF._bUnderlineColor);
|
|
|
|
SetFontAndColor(&CF);
|
|
|
|
LONG dupLineSave = _dupLine;
|
|
LONG dupOffset = LUtoDU(_pPF->_wNumberingTab ? _pPF->_wNumberingTab : _pPF->_dxOffset);
|
|
LONG upSave = _ptCur.u;
|
|
|
|
// Set-up to render bullet in one chunk
|
|
cch = GetBullet(szBullet, _pccs, &dup);
|
|
dupOffset = max(dupOffset, dup);
|
|
_dupLine = dupOffset;
|
|
if(IsInOutlineView())
|
|
dupOffset = _li._upStart - LUtoDU(lDefaultTab/2 * (_pPF->_bOutlineLevel + 1));
|
|
_ptCur.u -= dupOffset;
|
|
switch(_pPF->_wNumberingStyle & 3)
|
|
{
|
|
case tomAlignCenter:
|
|
dup /= 2; // Fall thru to tomAlignRight
|
|
|
|
case tomAlignRight:
|
|
_ptCur.u -= dup;
|
|
}
|
|
|
|
// Render bullet
|
|
_fLastChunk = TRUE;
|
|
RenderText(szBullet, cch);
|
|
|
|
// Restore render vars to continue with remainder of line.
|
|
_ptCur.u = upSave;
|
|
_dupLine = dupLineSave;
|
|
_li._dup = 0;
|
|
|
|
// This releases the _pccs that we put in for the bullet
|
|
SetNewFont();
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* CRenderer::DrawLine(ptStart, ptEnd)
|
|
*
|
|
* @mfunc
|
|
* Rotate the points passed and then call the OS.
|
|
*/
|
|
void CRenderer::DrawLine(const POINTUV &ptStart, const POINTUV &ptEnd)
|
|
{
|
|
POINT rgpt[2];
|
|
_pdp->PointFromPointuv(rgpt[0], ptStart);
|
|
_pdp->PointFromPointuv(rgpt[1], ptEnd);
|
|
|
|
Polyline(_hdc, rgpt, 2);
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderUnderline(upStart, vpStart, dup, dvp)
|
|
*
|
|
* @mfunc
|
|
* Render underline
|
|
*/
|
|
void CRenderer::RenderUnderline(
|
|
LONG upStart, //@parm Horizontal start of underline
|
|
LONG vpStart, //@parm Vertical start of underline
|
|
LONG dup, //@parm Length of underline
|
|
LONG dvp) //@parm Thickness of underline
|
|
{
|
|
BOOL fUseLS = fUseLineServices();
|
|
COLORREF crUnderline = _crUnderlineClr;
|
|
RECTUV rcT, rcIntersection;
|
|
|
|
rcT.top = vpStart;
|
|
rcT.bottom = vpStart + dvp;
|
|
rcT.left = upStart;
|
|
rcT.right = upStart + dup;
|
|
|
|
if (!IntersectRect((RECT*)&rcIntersection, (RECT*)&_rcRender, (RECT*)&rcT))
|
|
return; // Underline not inside rc, forget it.
|
|
upStart = rcIntersection.left;
|
|
dup = rcIntersection.right - rcIntersection.left;
|
|
vpStart = rcIntersection.top;
|
|
dvp = rcIntersection.bottom - rcIntersection.top;
|
|
|
|
if (crUnderline == tomAutoColor || crUnderline == tomUndefined)
|
|
{
|
|
crUnderline = _crCurTextColor;
|
|
}
|
|
|
|
if (_bUnderlineType != CFU_INVERT &&
|
|
!IN_RANGE(CFU_UNDERLINEDOTTED, _bUnderlineType, CFU_UNDERLINEWAVE) &&
|
|
!IN_RANGE(CFU_UNDERLINEDOUBLEWAVE, _bUnderlineType, CFU_UNDERLINETHICKLONGDASH))
|
|
{
|
|
// Regular single underline case
|
|
// Calculate where to put underline
|
|
rcT.top = vpStart;
|
|
|
|
if (CFU_UNDERLINETHICK == _bUnderlineType)
|
|
{
|
|
if (rcT.top > _rcRender.top + dvp)
|
|
{
|
|
rcT.top -= dvp;
|
|
dvp += dvp;
|
|
}
|
|
}
|
|
|
|
// There are some cases were the following can occur - particularly
|
|
// with bullets on Japanese systems.
|
|
if(!fUseLS && rcT.top >= _ptCur.v + _li._dvpHeight)
|
|
rcT.top = _ptCur.v + _li._dvpHeight - dvp;
|
|
|
|
rcT.bottom = rcT.top + dvp;
|
|
rcT.left = upStart;
|
|
rcT.right = upStart + dup;
|
|
FillRectWithColor(&rcT, crUnderline);
|
|
return;
|
|
}
|
|
|
|
if(_bUnderlineType == CFU_INVERT) // Fake selection.
|
|
{ // NOTE, not really
|
|
rcT.top = _ptCur.v; // how we should invert text!!
|
|
rcT.left = upStart; // check out IME invert.
|
|
rcT.bottom = rcT.top + _li._dvpHeight - _li._dvpDescent + _pccs->_yDescent;
|
|
rcT.right = rcT.left + dup;
|
|
|
|
RECT rc;
|
|
_pdp->RectFromRectuv(rc, rcT);
|
|
InvertRect(_hdc, &rc);
|
|
return;
|
|
}
|
|
|
|
if(IN_RANGE(CFU_UNDERLINEDOTTED, _bUnderlineType, CFU_UNDERLINEWAVE) ||
|
|
IN_RANGE(CFU_UNDERLINEDOUBLEWAVE, _bUnderlineType, CFU_UNDERLINETHICKLONGDASH))
|
|
{
|
|
static const char pen[] = {PS_DOT, PS_DASH, PS_DASHDOT, PS_DASHDOTDOT, PS_SOLID,
|
|
PS_SOLID, PS_SOLID, PS_SOLID, PS_SOLID, PS_DASH, PS_DASH,
|
|
PS_DASHDOT, PS_DASHDOTDOT, PS_DOT, PS_DASHDOT};
|
|
|
|
HPEN hPen = CreatePen(pen[_bUnderlineType - CFU_UNDERLINEDOTTED], 1, crUnderline);
|
|
if(hPen)
|
|
{
|
|
HPEN hPenOld = SelectPen(_hdc, hPen);
|
|
LONG upEnd = upStart + dup;
|
|
POINTUV ptStart, ptEnd;
|
|
|
|
ptStart.u = upStart;
|
|
ptStart.v = vpStart;
|
|
if((_bUnderlineType == CFU_UNDERLINEWAVE) ||
|
|
(_bUnderlineType == CFU_UNDERLINEDOUBLEWAVE) ||
|
|
(_bUnderlineType == CFU_UNDERLINEHEAVYWAVE))
|
|
{
|
|
LONG dv = 1; // Vertical displacement
|
|
LONG u = upStart + 1; // u coordinate
|
|
upEnd++; // Round up rightmost u
|
|
for( ; u < upEnd; dv = -dv, u += 2)
|
|
{
|
|
ptEnd.u = u;
|
|
ptEnd.v = vpStart + dv;
|
|
DrawLine(ptStart, ptEnd);
|
|
ptStart = ptEnd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ptEnd.u = upEnd;
|
|
ptEnd.v = vpStart;
|
|
DrawLine(ptStart, ptEnd);
|
|
}
|
|
|
|
if(hPenOld) // Restore original pen.
|
|
SelectPen(_hdc, hPenOld);
|
|
|
|
DeleteObject(hPen);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CRenderer::RenderStrikeOut(upStart, vpStart, dup, dvp)
|
|
*
|
|
* @mfunc
|
|
* Render strikeout
|
|
*/
|
|
void CRenderer::RenderStrikeOut(
|
|
LONG upStart, //@parm start of strikeout
|
|
LONG vpStart, //@parm start of strikeout
|
|
LONG dup, //@parm Length of strikeout
|
|
LONG dvp) //@parm Thickness of strikeout
|
|
{
|
|
RECTUV rcT, rcIntersection;
|
|
|
|
// Calculate where to put strikeout rectangle
|
|
rcT.top = vpStart;
|
|
rcT.bottom = vpStart + dvp;
|
|
rcT.left = upStart;
|
|
rcT.right = upStart + dup;
|
|
|
|
if (!IntersectRect((RECT*)&rcIntersection, (RECT*)&_rcRender, (RECT*)&rcT))
|
|
return; // Line not inside rc, forget it.
|
|
|
|
FillRectWithColor(&rcIntersection, GetTextColor(GetCF()));
|
|
}
|
|
|
|
/*
|
|
* CRenderer::FillRectWithTextColor(prc, cr)
|
|
*
|
|
* @mfunc
|
|
* Fill input rectangle with current color of text
|
|
*/
|
|
void CRenderer::FillRectWithColor(
|
|
const RECTUV * prc, //@parm Rectangle to fill with color
|
|
COLORREF cr) //@parm Color to use
|
|
{
|
|
// Create a brush with the text color
|
|
HBRUSH hbrush = CreateSolidBrush(_fDisabled ? _crForeDisabled : cr);
|
|
|
|
// Note if the CreateSolidBrush fails we just ignore it since there
|
|
// isn't anything we can do about it anyway.
|
|
if(hbrush)
|
|
{
|
|
// Save old brush
|
|
HBRUSH hbrushOld = (HBRUSH)SelectObject(_hdc, hbrush);
|
|
|
|
// Fill rectangle for underline
|
|
RECT rc;
|
|
_pdp->RectFromRectuv(rc, *prc);
|
|
PatBlt(_hdc, rc.left, rc.top, rc.right - rc.left,
|
|
rc.bottom - rc.top, PATCOPY);
|
|
SelectObject(_hdc, hbrushOld); // Put old brush back
|
|
DeleteObject(hbrush); // Free brush we created.
|
|
}
|
|
}
|
|
|