#include "kclient.hxx"
#include "kappwin.hxx"
#include "kscroll.hxx"
#include "khwndset.hxx"
#include "ksysmets.hxx"
#include "kfont.hxx"
#include "ksoftfont.hxx"
#include "karray.hxx"
#include "ikterm.h"
#include "ikcmd.h"
#include "ikextern.h"

#ifdef CK_HAVE_GDIPLUS
#include <gdiplus.h>
using namespace Gdiplus;
#endif /* CK_HAVE_GDIPLUS */

const char NO_SOFT_FONT = 0;

typedef struct _K_WORK_STORE {
    int               offset;
    int               length;
    int               x;
    int               y;
    cell_video_attr_t attr;
    vt_char_attr_t    effect;
    vt_cell_attr_t    cellAttr;
    char              fontBuffer;
} K_WORK_STORE;

extern UINT keyArray [];    // from kuikey.cxx

extern "C" {
#define K_DNONE      110		/* Screen rollback: down one line */
#define K_UPONE      112		/* Screen rollback: Up one line */

#define DECSTGLT_MONO           0
#define DECSTGLT_ALTERNATE      1
#define DECSTGLT_COLOR          3

extern int tt_cursor;
extern int cursorena[];
extern int tt_cursor_blink;
extern int tt_scrsize[];	/* Scrollback buffer size */
extern int tt_status[];
extern int tt_update;
extern int tt_type;
extern int scrollmode ;
extern int smooth_speed;
extern bool in_smooth_scroll;
extern bool smooth_scroll_upwards;
extern int smooth_scroll_top, smooth_scroll_bottom;
extern int smooth_scroll_left, smooth_scroll_right;
extern vscrn_t vscrn[];
extern int scrollflag[];
extern enum markmodes markmodeflag[] ;
extern BYTE vmode;
extern int win32ScrollUp, win32ScrollDown;
extern int trueblink, trueunderline, trueitalic, truedim, truebold,
        truecrossedout;
extern int decstglt, decatcbm, decatcum;
cell_video_attr_t geterasecolor(int);
int tt_old_update;
extern int tt_sync_output;  /* ckoco3.c */
extern int tt_sync_output_timeout;
extern bool     decscnm;

extern void scrollback( BYTE, int );
extern DWORD VscrnIsDirty( int );

extern int colorpalette; /* ckoco3.c */
extern cell_video_attr_t  colorcursor, colornormal;  /* ckoco3.c */

#ifdef CK_COLORS_DEBUG
extern ULONG RGBTable256[256];
#endif /* CK_COLORS_DEBUG */

#ifndef KUIDIRTY
extern DWORD VscrnClean( int vmode );
#else /* KUIDIRTY */
static int vscrn_dirty[VNUM] = {-1, -1, -1, -1};

DWORD VscrnClean(int vmode) {
	// This value changes each time the vscrn is marked as dirty
	int newval = vscrn[vmode].dirty;

	// If the value is different from the last time we saw it, then the
	// contents of the vscrn has probably changed.
	DWORD dirty = 0;
	if (newval != vscrn_dirty[vmode]) {
		dirty = 1;
	}

	// Make a note of the new value so we can tell if it changes later.
	vscrn_dirty[vmode] = newval;

	return dirty;
}
#endif /* KUIDIRTY */

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
LRESULT CALLBACK KClientWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    // debug(F111,"KClientWndProc()","msg",msg);
    KClient* client = (KClient*) kglob->hwndset->find( hwnd );
    if( client && client->message( hwnd, msg, wParam, lParam ) )
        return client->msgret();

    return CallWindowProc( DefWindowProc, hwnd, msg, wParam, lParam );
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
// In Visual C++ 2002 (7.0) and up for 32bit windows, UINT_PTR is unsigned int
// In Visual C++ 6.0 (and perhaps earlier) UINT_PTR is unsigned long (which
// results in a build error)
#if defined(_MSC_VER) && _MSC_VER < 1300
VOID CALLBACK KTimerProc( HWND hwnd, UINT msg, UINT id, DWORD dwtime )
#else
VOID CALLBACK KTimerProc( HWND hwnd, UINT msg, UINT_PTR id, DWORD dwtime )
#endif
{
    /* Guard against the terminal getting 'stuck' because something turns
     * synchronised output on and leaving it on forever. */
    if (tt_sync_output) {
        if (tt_sync_output_timeout > 0) {
            tt_sync_output_timeout -= tt_update;
        }
        if (tt_sync_output_timeout <= 0) {
            tt_sync_output = FALSE;
            tt_sync_output_timeout = 0;
            VscrnIsDirty(VTERM);
        }
    }

    // debug(F111,"KTimerProc()","msg",msg);
    // debug(F111,"KTimerProc()","id",id);
    // debug(F111,"KTimerProc()","dwtime",dwtime);
    KClient* client = (KClient*) kglob->hwndset->find( hwnd );
    if( client ) {
        if ((scrollmode >= TTS_SMOOTH && in_smooth_scroll) || client->smoothScrolling()) {
            // We're smooth-scrolling.
            client->smoothScroll();
        } else {
            if( ::VscrnClean( vmode /* client->getClientID() */ )
                && (!tt_sync_output || vmode != VTERM))
                client->getDrawInfo();
            else
                client->checkBlink();
        }
    }
#ifndef NOKVERBS
    if ( win32ScrollUp ) {
        putkverb(vmode, F_KVERB | K_UPONE);
    } else if ( win32ScrollDown ) {
        putkverb(vmode, F_KVERB | K_DNONE);
    }
#endif /* NOKVERBS */

    if (client && tt_update != tt_old_update ) {
        // Interval has changed, restart the timer.
        client->startTimer();
    }
}

}   // end of extern "C"

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
KClient::KClient( K_GLOBAL* kg, BYTE cid )
    : KWin( kg )
    , _hdc( 0 )
    , _hdcScreen( 0 )
    , _hdcScratch( 0 )
    , _hdcSScrollBlinkOn( 0 )
    , _hdcSScrollBlinkOff( 0 )
    , maxpHeight( 0 )
    , maxpWidth( 0 )
    , maxHeight( 0 )
    , maxWidth( 0 )
    , _inFocus( FALSE )
    , hrgnPaint( 0 )
    , disabledBrush( 0 )
    , bgBrush( 0 )
    , savebgcolor( 0 )
    , _margin( 2 )      // default margin around display area
    , clientID( cid )
    , saveTermHeight( 0 )
    , saveTermWidth( 0 )
    , saveLineSpacing(1.0)
    , saveHorzIsV(0)
    , font( 0 )
    , processKey( FALSE )
#ifndef CK_COLORS_24BIT
    , prevAttr( cell_video_attr_init_vio_attribute(255) )
#endif /* CK_COLORS_24BIT */
    , prevEffect( uchar(-1) )
    , prevCellAttr( 0 )
    , _xoffset( 0 )
    , _yoffset( 0 )
    , _msgret( 1 )
    , ws_blinking( 0 )
    , cursor_displayed( 0 )
    , screenNormal(FALSE)
    , smoothScrollProgress( 0.0 )
    , smoothScrollTime( 0 )
    , smoothScrollRendering ( FALSE )
{
    InitializeCriticalSection(&csDraw);

#ifdef CK_COLORS_24BIT
#if _MSC_VER < 1800
    prevAttr = cell_video_attr_from_vio_attribute(255);
#else
     prevAttr = cell_video_attr_init_vio_attribute(255);
#endif
#endif /* CK_COLORS_24BIT */
    vert = new KScroll( kg, TRUE, TRUE );
    horz = new KScroll( kg, FALSE, TRUE );

    // structure used to pass data to the paint function
    //
    clientPaint = new K_CLIENT_PAINT;
    memset( clientPaint, '\0', sizeof(K_CLIENT_PAINT) );

    long maxcells = ::getMaxDim();
    workTempSize = allocateClientPaintBuffers(clientPaint, maxcells, &workTemp);
    textBuffer = clientPaint->textBuffer;
    attrBuffer = clientPaint->attrBuffer;
    effectBuffer = clientPaint->effectBuffer;
    lineAttr = clientPaint->lineAttr;
	cellAttrBuffer = clientPaint->cellAttrBuffer;

    workStore = new K_WORK_STORE[ maxcells ];
    memset( workStore, '\0', sizeof(K_WORK_STORE) * maxcells );

    memset( &cursorRect, '\0', sizeof(RECT) );
    cursorCount = 0;

    maxCursorCount = 1100;
    blinkInterval  = 600;

    /* Invalid vscrn ID to always use the current vscrn */
    ikterm = new IKTerm( vmode /* clientID */, clientPaint );
    wc = 0;
    vscrollpos = hscrollpos = 0;

    /* Save initial window size so we can tell when it changes */
    getEndSize(previousWidth, previousHeight);

    ruledLinePen = (HPEN) GetStockObject( WHITE_PEN );
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
KClient::~KClient()
{
    delete font;
    ReleaseDC( hWnd, _hdcScreen );
    DeleteDC( _hdc );
    DeleteDC( _hdcScratch );
    if ( _hdcSScrollBlinkOn != 0 ) DeleteDC( _hdcSScrollBlinkOn );
    if ( _hdcSScrollBlinkOff != 0 ) DeleteDC( _hdcSScrollBlinkOff );
    DeleteObject( compatBitmap );
    DeleteObject( scratchBitmap );
    DeleteObject( scrollBlinkOnBitmap );
    DeleteObject( scrollBlinkOffBitmap );
    DeleteObject( hrgnPaint );
    DeleteObject( disabledBrush );
    DeleteObject( bgBrush );
    DeleteObject (ruledLinePen);

    if( timerID )
        KillTimer( hWnd, timerID );
    delete horz;
    delete vert;

    delete clientPaint;

    delete workStore;
    delete workTemp;

    delete ikterm;

    DeleteCriticalSection(&csDraw);
}

void KClient::stopTimer() {
    if( timerID )
        KillTimer( hWnd, timerID );
    timerID = NULL;
}

void KClient::startTimer() {
    stopTimer();
    tt_old_update = tt_update;
    timerID = SetTimer( hWnd, IDT_CLIENTTIMER, tt_update, KTimerProc );
}

size_t KClient::allocateClientPaintBuffers(K_CLIENT_PAINT* clientPaint,
            long maxcells, uchar **workTempOut) {
    // allocate the space in one chunck so it can be cleared in one call
    //
    int column, row;
    ::getMaxSizes( &column, &row );
    //  workTempSize = (maxcells * sizeof(ushort) * 5) + (row * sizeof(ushort));  // This seems to allocate almost twice as much memory as actually needed!
    size_t workTempSize = MAXSCRNROW * sizeof(ushort); // Line attributes
           workTempSize += maxcells * sizeof(ushort);  // Text
           workTempSize += maxcells * sizeof(vt_char_attr_t);  // Effects
           workTempSize += maxcells * sizeof(cell_video_attr_t);  // Colour
           workTempSize += maxcells * sizeof(vt_cell_attr_t);  // Ruled lines

    uchar* workTemp = new uchar[ workTempSize ];
    memset( workTemp, '\0', workTempSize);

    clientPaint->textBuffer =   (ushort*)            &(workTemp[0]); /* One per cell */
    size_t offset = maxcells * sizeof(ushort);

    clientPaint->attrBuffer =   (cell_video_attr_t*) &(workTemp[ offset ]); /* One per cell */
    offset += maxcells * sizeof(cell_video_attr_t);

    clientPaint->effectBuffer = (vt_char_attr_t*)    &(workTemp[ offset ]); /* One per cell */
    offset += maxcells * sizeof(vt_char_attr_t);

    clientPaint->cellAttrBuffer = (vt_cell_attr_t*)  &(workTemp[ offset ]); /* One per cell */
    offset += maxcells * sizeof(vt_cell_attr_t);

    clientPaint->lineAttr =     (ushort*)            &(workTemp[ offset ]); /* One per line */
    offset += MAXSCRNROW * sizeof(ushort);

    *workTempOut = workTemp;
    return workTempSize;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
Bool KClient::getMaxpDim( int& rw, int& rh )
{ 
    if( maxpWidth == 0 && maxpHeight == 0 )
        return FALSE;

    rw = maxpWidth;
    rh = maxpHeight;
    return TRUE;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::setFont( KFont* f )
{
    if( font == f )
        return;

    if( font )
        delete font;
    font = f;
    font->setFont( _hdc );

    int twid, thi;
    ::getDimensions( vmode /* clientID */, &twid, &thi );

    horz->setValues( font->getFontW(), font->getFontW() * 10 );
    vert->setValues( 1, thi - tt_status[vmode]);

    setInterSpacing( f );
    setDimensions( TRUE );
    softFont.setSize(font->getFontW(), font->getFontSpacedH());

    prevEffect = uchar(-1);     // reset the effect flag
	saveTermHeight = saveTermWidth = 0;
}


/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::fontChanged() {
    softFont.setSize(font->getFontW(), font->getFontSpacedH());
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::setInterSpacing( KFont* f )
{
    int fontw = f->getFontW();
    for( int i = 0; i < MAXNUMCOL; i++ )
        interSpace[i] = fontw;
}

/*------------------------------------------------------------------------
	calculate the appropriate size for this client window...
	includes margin and scrollbars
------------------------------------------------------------------------*/
void KClient::calculateSize( int& w, int& h )
{
    int clientWidth = 0, clientHeight = 0;
    ::getDimensions( vmode /* clientID */, &clientWidth, &clientHeight );

    w = (clientWidth * font->getFontW()) 
        + (vert->isVisible() ? kglob->sysMets->vscrollWidth() : 0) 
        + (2 * kglob->sysMets->edgeWidth()) + margin();

    // determine if the horizontal scrollbar is visible
    //
    int horzh = horz->isVisible() ? kglob->sysMets->hscrollHeight() : 0;

    h = clientHeight * font->getFontSpacedH()
        + horzh
        + (2 * kglob->sysMets->edgeHeight()) + margin();
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::setDimensions( Bool sizeparent )
{
    if( inCreate() )
        return;

    RECT rectp, rectc;
    GetWindowRect(parent->hwnd(), &rectp);
    GetWindowRect( hWnd, &rectc );

    // find the differences in dimensions between the parent and client
    //
    int dw = ( rectp.right - rectp.left ) - ( rectc.right - rectc.left );
    int dh = ( rectp.bottom - rectp.top ) - ( rectc.bottom - rectc.top );

    // add the border around the display area
    //
    calculateSize( maxWidth, maxHeight );

    maxpWidth = maxWidth + dw;
    maxpHeight = maxHeight + dh;

    if( sizeparent ) {
        SetWindowPos( parent->hwnd(), 0, 0, 0, maxpWidth, maxpHeight
            , SWP_NOMOVE | SWP_NOZORDER );

    }
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::getCreateInfo( K_CREATEINFO* info )
{
    info->classname = KWinClassName;
#ifndef CKT_NT35_OR_31
    info->exStyle = WS_EX_CLIENTEDGE;
#endif
    info->style = WS_CHILD | WS_VISIBLE 
        | WS_HSCROLL | WS_VSCROLL | WS_CLIPSIBLINGS;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::createWin( KWin* par )
{
    inCreate( TRUE );

    KWin::createWin( par );

#ifdef _WIN64
    WNDPROC _clientProc = (WNDPROC) SetWindowLongPtr(
            hWnd, GWLP_WNDPROC, (LONG_PTR) KClientWndProc );
#else /* _WIN64 */
    WNDPROC _clientProc = (WNDPROC) SetWindowLong( hWnd, GWL_WNDPROC
                    , (LONG)KClientWndProc );
#endif /* _WIN64 */

    // to make the updates look clean, use a memory DC for the drawing
    // and then BitBlt() it onto the screen DC.
    //
    _hdcScreen = GetDC( hWnd );
    _hdc = CreateCompatibleDC( _hdcScreen );
    compatBitmap = CreateCompatibleBitmap( _hdcScreen
        , kglob->sysMets->screenWidth()
        , kglob->sysMets->screenHeight() );
    _hdcScratch = CreateCompatibleDC( _hdcScreen );
    scratchBitmap = CreateCompatibleBitmap( _hdcScreen
        , kglob->sysMets->screenWidth()
        , kglob->sysMets->screenHeight() );

    SelectObject( _hdc, compatBitmap );
    SelectObject( _hdcScratch, scratchBitmap );

    HBITMAP bitmap = LoadBitmap( hInst, MAKEINTRESOURCE(IDB_BITMAP1) );
    disabledBrush = CreatePatternBrush( bitmap );
    DWORD rgb = cell_video_attr_background_rgb(geterasecolor(vmode));
    bgBrush = CreateSolidBrush( rgb );
    savebgcolor = rgb;

    DeleteObject( bitmap );

    clearPaintRgn();    // clear the paint region

    vert->createWin( this );
    horz->createWin( this );

    vert->setCallback( KWinMethodOf(KClient,vertScroll) );
    horz->setCallback( KWinMethodOf(KClient,horzScroll) );

    // set the default font to terminal (8x12)
    //
    KFont* defaultFont;
    if ( kglob->faceName[0] ) 
        defaultFont = new KFont(kglob->faceName, kglob->fontHeight,
                                 kglob->fontWidth);
    else
        defaultFont = new KFont();

    char * facename = defaultFont->getFaceName();
    // Lucida substs only take place if the font is Lucida Console
    win95lucida = !ckstrcmp(facename,"lucida console",-1,0);
    // HSL substs occur whenever the font does not support HSLs
    win95hsl = ckstrcmp(facename,"everson mono terminal",-1,0);

    setFont( defaultFont );

    int width, height;
    calculateSize( width, height );

    SetWindowPos( hWnd, 0, 0, 0, width, height, SWP_NOZORDER );

    /* This timer determines how frequently we attempt to update the screen */
    /* At some point this was every 10ms but in K95 2.1.3 at least it was fixed
     * to once every 100ms which wasn't very nice. Now the interval is
     * configurable via set terminal screen-update */
    startTimer();

    inCreate( FALSE );
}

/*------------------------------------------------------------------------
    clear the memory DC
------------------------------------------------------------------------*/
void KClient::clearPaintRgn()
{
    if( !hrgnPaint ) {
        hrgnPaint = CreateRectRgn( 0, 0
            , kglob->sysMets->screenWidth()
            , kglob->sysMets->screenHeight() );
    }

    PaintRgn( _hdc, hrgnPaint );
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
long KClient::vertScroll( long val )
{
    if( val > vscrollpos ) {
        do {
            if (!scrollflag[vmode])
                break;
            ::scrollback( vmode /* clientID */, K_DNONE );
            val--;
        } while( val > vscrollpos );
    }
    else if( val >= 0 && val < vscrollpos ) {
        while( val < vscrollpos ) {
            ::scrollback( vmode /* clientID */, K_UPONE );
            val++;
        }
    }

    paint();
    return 0L;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
long KClient::horzScroll( long val )
{
    if( val > hscrollpos ) {
        do {
            if (!scrollflag[vmode])
                break;
            ::dokverb(vmode, K_RTONE );
            val--;
        } while( val > hscrollpos );
    }
    else if( val >= 0 && val < hscrollpos ) {
        while( val < hscrollpos ) {
            ::dokverb(vmode, K_LFONE );
            val++;
        }
    }
    paint();
    return 0L;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::size( int width, int height )
{
    SetWindowPos( hWnd, 0
        , 0, 0
        , width, height
        , SWP_NOMOVE | SWP_NOZORDER );

#if 0
    if( kglob->mouseEffect == TERM_MOUSE_NO_EFFECT ) {
        horz->setRange( maxWidth, width );

        int twid, thi;
        ::getDimensions( vmode /* clientID */, &twid, &thi );

        // determine if the horizontal scrollbar is visible
        //
        int horzh = horz->isVisible() ? kglob->sysMets->hscrollHeight() : 0;

        int truh = height - horzh
            - (2 * kglob->sysMets->edgeHeight()) - margin();
        vert->setRange( thi, truh / font->getFontSpacedH() );
    }
#endif
    paint();
}

/*------------------------------------------------------------------------
    change the dimensions of the terminal
	When doAnyway == FALSE, means the user was dragging the window
	to change the size.  If TRUE, means the system is changing
	the size (in writeMe() ).
------------------------------------------------------------------------*/
void KClient::endSizing( Bool doAnyway )
{
    if( kglob->mouseEffect != TERM_MOUSE_CHANGE_DIMENSION && !doAnyway )
        return;

    if( vmode /* clientID */ == VCS )
        return;
    if( vmode /* clientID */ == VSTATUS)
        return;

    int w, h;
    getEndSize( w, h );

    if (w == previousWidth && h == previousHeight
            && saveTermWidth == w && saveTermHeight == h){

        debug(F100, "endSizing: size not changed - doing nothing", "", 0);
        return;
    }

    previousWidth = w;
    previousHeight = h;

    if (kglob->mouseEffect == TERM_MOUSE_CHANGE_DIMENSION ) {
        kui_setheightwidth(w,h);
    } else if (kglob->mouseEffect == TERM_MOUSE_CHANGE_FONT ) {
        if ( w != saveTermWidth || h != saveTermHeight ) {
            saveTermWidth = saveTermHeight = 0;
        } else {
            kui_setheightwidth(w,h);
        }
    }
    vert->setValues( 1, h - tt_status[vmode]);
    ::VscrnIsDirty(vmode);
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::getEndSize( int& w, int& h )
{
    if ( !font ) {
        h = w = 0;
        return;
    }

    RECT rect;
    GetWindowRect( hWnd, &rect );

    int width = ( rect.right - rect.left )
        - (vert->isVisible() ? kglob->sysMets->vscrollWidth() : 0)
        - 2 * kglob->sysMets->edgeWidth()
//        - 2 * kglob->sysMets->sizeframeWidth()
        - margin();

    // determine if the horizontal scrollbar is visible
    int horzh = horz->isVisible() ? kglob->sysMets->hscrollHeight() : 0;

    int height = ( rect.bottom - rect.top )
        - horzh
        - 2 * kglob->sysMets->edgeHeight()
//        - 2 * kglob->sysMets->sizeframeHeight()
        - margin();

    div_t divt = div( width, font->getFontW());
    w = divt.quot;
    if (divt.rem * 2 > font->getFontW())
        w ++;

    if( w < 20 )        // check for boundary conditions
        w = 20;
    else if( w > MAXSCRNCOL )
        w = MAXSCRNCOL;

    if( w % 2 )         // can't accept odd widths
        w -= 1;

    divt = div( height, font->getFontSpacedH());
    h = divt.quot;
    if (divt.rem * 2 > font->getFontSpacedH())
        h ++;

    if( h < 8 )         // check for boundary conditions
        h = 8;
    else if( h > MAXSCRNROW )
        h = MAXSCRNROW;
}

/*------------------------------------------------------------------------
    paint the window (not an update of data)
------------------------------------------------------------------------*/
Bool KClient::paint()
{
    clearPaintRgn();
    if ((tt_sync_output && vmode == VTERM) || smoothScrolling()) {
        /* in synchronized output mode - keep re-rendering the existing display
         * until we exit synchronized output mode. */
        writeMe();
    } else {
        getDrawInfo();
    }
    return TRUE;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::getDrawInfo()
{
    EnterCriticalSection(&csDraw);
    //debug(F100,"KClient::getDrawInfo()","",0);
    memset( workTemp, '\0', workTempSize );
    BOOL success = ikterm->getDrawInfo();
    LeaveCriticalSection(&csDraw);

    if( success )
        writeMe();
}

void KClient::ToggleCursor( HDC hdc, LPRECT lpRect )
{
    /* Draw the cursor in the scratch hdc rather than directly on the screen
     * like we used to - this makes it blink nicer. No more wiping in and out
     * from top to bottom. */
    BitBlt(_hdcScratch,
           lpRect->left, lpRect->top,
           lpRect->right, lpRect->bottom,
           hdc,
           lpRect->left, lpRect->top,
           SRCCOPY);
    for (int y = lpRect->top; y < lpRect->bottom; y++) {
        for ( int x = lpRect->left ; x < lpRect->right ; x++ ) {
            COLORREF color = GetPixel(_hdcScratch, x, y);
			int cursorbg = cell_video_attr_background_rgb(colorcursor);

            SetPixel(_hdcScratch, x, y, color^cursorbg);
        }
    }
    BitBlt(hdc,
           lpRect->left, lpRect->top,
           lpRect->right, lpRect->bottom,
           _hdcScratch,
           lpRect->left, lpRect->top,
           SRCCOPY);
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::checkBlink()
{
    //debug(F100,"KClient::checkBlink()","",0);

    Bool blinkOn = FALSE;

    if( cursorCount < maxCursorCount ) {
        cursorCount += tt_update;
        if(cursorCount >= blinkInterval)
            blinkOn = TRUE;
    }
    else
        cursorCount = 0;

    /* ws_blinking indicates if the screen currently contains blinking elements
     * besides the cursor.*/
    if (ws_blinking && ((blinkOn && cursorCount == blinkInterval) || (cursorCount == 0)) )
        writeMe();
    else if (smoothScrolling()) /* Skip rendering cursor during smooth-scroll */
        writeMe();

    else if (cursorCount%300 == 0) {
        if (ikterm->getCursorPos() && (_inFocus || (!_inFocus && cursor_displayed)))
        {
            int adjustedH = font->getFontH();
            if( tt_cursor == 2 )        // full
                ;
            else if( tt_cursor == 1 )   // half
                adjustedH /= 2;
            else                        // underline
                adjustedH = kglob->sysMets->borderHeight() + 1;

            cursorRect.left = clientPaint->cursorPt.x * font->getFontW()
                - _xoffset;
            cursorRect.top = clientPaint->cursorPt.y * font->getFontSpacedH()
                + ( font->getFontH() - adjustedH );
            cursorRect.right = cursorRect.left + font->getFontW();
            cursorRect.bottom = cursorRect.top + adjustedH;

            if ( cursorena[vmode] && cursor_on_visible_page(vmode) || markmodeflag[vmode] != notmarking ) {
                if ( !tt_cursor_blink ) {
                    if (!cursor_displayed && _inFocus || cursor_displayed && !_inFocus) {
                        ToggleCursor( hdcScreen(), &cursorRect );
                        cursor_displayed = _inFocus;
                    } 
                } else {
                    ToggleCursor( hdcScreen(), &cursorRect );
                    cursor_displayed = !cursor_displayed;

                }
            } else { /* cursor disabled */
                if (cursor_displayed) {
                    ToggleCursor( hdcScreen(), &cursorRect );
                    cursor_displayed = 0;
                }
            }
        }
    }
}

void KClient::syncSize()
{
    if (!IsZoomed(parent->hwnd()) || kglob->mouseEffect == TERM_MOUSE_NO_EFFECT )
        setDimensions( TRUE );

    if ( kglob->mouseEffect == TERM_MOUSE_CHANGE_FONT ) {
        RECT rect;
        if ( IsZoomed(parent->hwnd()) ) {
            GetWindowRect(GetDesktopWindow(), &rect);
        } else {
            GetWindowRect(parent->hwnd(), &rect);
        }
        parent->sizeFont(&rect, TRUE);
    } 
    if ( !IsZoomed(parent->hwnd()) || kglob->mouseEffect == TERM_MOUSE_NO_EFFECT )
        endSizing( TRUE );	// pass in TRUE to force processing
}

/*------------------------------------------------------------------------
 Handle Smooth Scrolling
------------------------------------------------------------------------*/
/* How Smooth Scroll works (scrollmode >= TTS_SMOOTH):
 *   When a scroll completes, VscrnScrollPage will set in_smooth_scroll = TRUE
 *   and reset the Smooth-Scroll-Finished semaphore. On the next scroll,
 *   VscrnScrollPage will wait on the Smooth-Scroll-Finished and won't start the
 *   scroll until the last one has completed.
 *
 *   If in_smooth_scroll==True and scrollback isn't being viewed
 *   (!scrollflag[vnum]) then IKterm::getDrawInfo() will grab a slightly
 *   enlarged snapshot of the vscreen moving the top back by one line so that
 *   both the old and new top are present.
 *
 *   Over here in KClient during a smooth-scroll the timer will call the
 *   KClient::smoothScroll() rather than KClient::getDrawInfo() or
 *   KClient::checkBlink() every time it fires.
 *
 *   KClient::smoothScroll() increases the scroll progress each time it fires,
 *   then writeMe() will use that progress to offset the rendered bitmap
 *   vertically by some percentage of the line height so that the appropriate
 *   fraction of the old top and new bottom are visible.
 *
 *   When the smooth-scroll progress reaches 100%, KClient::writeMe() will reset
 *   the progress to zero, set in_smooth_scroll to FALSE and post the
 *   Smooth-Scroll-Finished semaphore to allow any pending scroll to proceed and
 *   start the process all over again.
 *
 * Smooth Scrolling only *part* of the Screen:
 *   When the margins are moved away from the page edges, scrolling region still
 *   smooth-scrolls. For the VT420 and earlier this applies only to the top and
 *   while the VT520 will smooth-scroll between the left and right margins too.
 *
 *   When doing a smooth-scroll of only part of the screen the process is a
 *   little different. At the start of the scroll event, VscrnScrollPage saves
 *   the line that is being scrolled away in a temporary location along with the
 *   top and bottom margins that were used for the scroll event.
 *
 *   When render happens, it starts with two rendering passes of the whole
 *   vscreen (one with blinking elements on, and one with them off). These two
 *   passes go to a pair of HDCs for later use rather than to the screen. These
 *   two renders are currently done using renderToDc() which was originally
 *   created for taking screenshots.
 *
 *   Then during the smooth scroll IKTerm only collects data for the scrolling
 *   region with the line saved by VscrnScrollPage slotted in at the top or
 *   bottom depending on scroll direction. writeMe() only renders what IKTerm
 *   hands it, so what gets rendered to _hdc is only scrolling region with the
 *   saved line added on.
 *
 *   Once writeMe() has rendered the scrolling region to _hdc, it then combines
 *   all the pieces. It BitBlts one of the two initial render passes (which one
 *   depending on blink state) to the screen, followed by the smooth scroll
 *   region with an offset for scroll progress.
 */

bool KClient::getSmoothScrollDrawInfo() {
    BOOL success = TRUE;

    DWORD rgb = cell_video_attr_background_rgb(geterasecolor(VTERM));
    HBRUSH bgBrush = CreateSolidBrush( rgb );
    HGDIOBJ tempObj;

    EnterCriticalSection(&csDraw);

    bool tbmm = smooth_scroll_top != -1 && smooth_scroll_bottom != -1;
    bool lrmm = smooth_scroll_left != -1 && smooth_scroll_right != -1;

    if (tbmm || lrmm) {
        // We're only scrolling part of the screen. That complicates matters
        // somewhat. This means we need to render the screen in two stages.

        // For the first stage we need to render the terminal as though
        // we're not doing smooth scrolling. But don't render it to the
        // screen - render it to another HDC off to the side. To do this,
        // we'll use the static rendering functions originally created to
        // render screenshots.
        if (_hdcSScrollBlinkOn == 0) {
            _hdcSScrollBlinkOn = CreateCompatibleDC( _hdcScreen );
            scrollBlinkOnBitmap = CreateCompatibleBitmap( _hdcScreen
                    , kglob->sysMets->screenWidth()
                    , kglob->sysMets->screenHeight() );
            SelectObject(_hdcSScrollBlinkOn, scrollBlinkOnBitmap);
        }
        if (_hdcSScrollBlinkOff == 0) {
            _hdcSScrollBlinkOff = CreateCompatibleDC( _hdcScreen );
            scrollBlinkOffBitmap = CreateCompatibleBitmap( _hdcScreen
                    , kglob->sysMets->screenWidth()
                    , kglob->sysMets->screenHeight() );
            SelectObject(_hdcSScrollBlinkOff, scrollBlinkOffBitmap);
        }

        // Fully erase both DCs. Normally clearPaintRgn() does this for _hdc.
        // If we don't do this, then dragging the window larger while a smooth
        // scroll is ongoing may reveal content from when the window was
        // previously that size
        tempObj = SelectObject(_hdcSScrollBlinkOn, bgBrush);
        PaintRgn(_hdcSScrollBlinkOn, hrgnPaint);
        SelectObject(_hdcSScrollBlinkOn, tempObj);

        tempObj = SelectObject(_hdcSScrollBlinkOff, bgBrush);
        PaintRgn(_hdcSScrollBlinkOff, hrgnPaint);
        SelectObject(_hdcSScrollBlinkOff, tempObj);

        // And we have to render it twice - once with all of the blinking
        // elements ON, and again with them OFF.
        success = success && renderToDc(_hdcSScrollBlinkOn, font, VTERM, margin(), TRUE);
        success = success && renderToDc(_hdcSScrollBlinkOff, font, VTERM, margin(), FALSE);

        // The second stage will then only render the scrolling region, and
        // it will copy all the non-scrolling content out of the pair of HDC
        // we prepared above.
    }

    tempObj = SelectObject(_hdcScratch, bgBrush);
    PaintRgn(_hdcScratch, hrgnPaint);
    SelectObject(_hdcScratch, tempObj);

    memset( workTemp, '\0', workTempSize );
    success = success && ikterm->getSmoothScrollDrawInfo();

    LeaveCriticalSection(&csDraw);

    if (success) {
        smoothScrollRendering = TRUE;
        writeMe();
    }

    DeleteObject( bgBrush );

    return success;
}

bool KClient::smoothScrolling() {
    // We're smooth scrolling if we're on VTERM, and either a smooth
    // scroll was started (in_smooth_scroll), or has not yet been
    // finished (smoothProgress > 0). We have to accept both, otherwise
    // forcing smooth scroll off mid-scroll might not result in the
    // semaphore being raised, etc.
    return vmode == VTERM && (in_smooth_scroll || smoothScrollProgress > 0);
}

void KClient::smoothScroll() {
    smoothScrollTime += tt_update;

    float ms_per_line = 1000.0f / (float)(smooth_speed < 1 ? 6 : smooth_speed);

    // Update progress
    smoothScrollProgress = smoothScrollTime /  ms_per_line;

    // Figure out if we should *show* the smooth scroll.
    bool popupActive = FALSE;
    bool selectionActive = markmodeflag[VTERM] == marking;
    bool viewingScrollback = scrollflag[VTERM];
    bool onVterm = vmode == VTERM;

    if ( ! RequestVscrnMutex( VTERM, 200 ) ) {
        popupActive = vscrn[VTERM].popup != NULL;
        ReleaseVscrnMutex(VTERM);
    }

    // We only render a smooth scroll event if the user is on VTERM, isn't
    // selecting text, isn't viewing scrollback, and doesn't have a popup
    // open. We still compute the smooth scroll though.
    bool smoothRender = !popupActive && !selectionActive && !viewingScrollback
        && onVterm;

    if (smoothRender != smoothScrollRendering || VscrnClean(vmode)) {
        // We're switching between Smooth Scroll rendering and Jump Scroll
        // rendering. Refresh the draw info.
        if (!smoothRender) {
            smoothScrollRendering = smoothRender;
            getDrawInfo();
        } else {
            getSmoothScrollDrawInfo();
        }
    } else if (smoothRender) {
        // Render current smoothScroll progress
        writeMe();
    } else {
        // Same mode as before - just re-render the existing buffer snapshot
        // if needed.
        checkBlink();
    }

    if (smoothScrollProgress >= 1) {
        // Finished the smooth scroll event. Unblock scrolling.
        smoothScrollProgress = 0;
        smoothScrollTime = 0;
        smoothScrollRendering = FALSE;
        in_smooth_scroll = FALSE;

        // Force a refresh of the draw info in case we were rendering a scroll
        // region.
        getDrawInfo();

        PostSmoothScrollFinishedSem();
    }
}

/* Calculates the vertical offset based on current smooth scroll progres */
int KClient::smoothScrollOffset(int lineHeight) const {
    int offset = 0;

    if (!smoothScrollRendering) return offset;

    // Scroll progress determines how much of the old screen top we show
    // and how much of the new screen bottom.
    if (smooth_scroll_upwards) {
        offset = (int)(smoothScrollProgress * lineHeight);
    } else {
        offset = (int)((1.0 - smoothScrollProgress) * lineHeight);
    }

    if (offset > lineHeight) offset = lineHeight;
    if (offset < 0) offset = 0;

    return offset;
}

/*------------------------------------------------------------------------
    update the memory DC with all the drawing and then copy it 
    into the screen DC.  Also update the cursor position.
------------------------------------------------------------------------*/
void KClient::writeMe()
{
    /* TODO: Refactor this into a KTermGdiRenderer or similar.
     *       - Only the actual terminal painting stuff should move
     *          - It should take a k_CLIENT_PAINT and paint whatever it says
     *          - It should be able to render to an arbitrary HDC at given
     *            coordinates so that, eg, a single line could be repainted
     *          - It should replace the existing renderToDc() function, and the
     *            other screenshot stuff should move elsewhere (perhaps a
     *            KTerminalScreenshot class?)
     *          - All the Smooth-Scroll multi-render-and-composite stuff should
     *            be a separate render function that repeatedly calls the
     *            renderer as necessary to get all the bits it needs
     *          - Anything not GDI-specific should be pushed into a parent class
     *            (AbstractKTermRenderer?) to allow for other non-GDI renderer
     *            implementations in the future (GDI+? Direct2D? OS/2 GPI?)
     *       - The stuff for updating status bars should go to another function,
     *         as should the stuff for deciding the visibility of blinking
     *         stuff.
     *       - The K_WORK_STORE stuff could probably be moved to IKTerm and done
     *         at the same time data is being gathered from the terminal buffer.
     *         I'm not sure there is a good reason to loop through all of the
     *         screen data twice.
     *       - IKTerm needs a more flexible API allowing arbitrary regions of
     *         the terminal buffer to be obtained, along with a helper "whole
     *         screen" method. This would allow, eg, screenshotting the entire
     *         scrollback or rendering a single line so that blinking the cursor
     *         doesn't require the whole terminal to be re-rendered.
     */

    int twid, thi, hisv;
    //debug(F100,"KClient::writeMe()","",0);

    // Smooth-scrolling between top and bottom margins
    bool scroll_tbm = smoothScrollRendering
         && smooth_scroll_top != -1 && smooth_scroll_bottom != -1;

    bool scroll_lrm = smoothScrollRendering
         && smooth_scroll_left != -1 && smooth_scroll_right != -1;

    ::getDimensions( vmode /* clientID */, &twid, &thi );
    hisv = horz->isVisible();

    if( thi != saveTermHeight || twid != saveTermWidth || 
        tt_linespacing[vmode] != saveLineSpacing || 
        hisv != saveHorzIsV)
    {
        //if ( deblog ) {
        //    char buf[128];
        //    sprintf(buf,"thi=%d(%d) twid=%d(%d) linespace=%f(%f)",thi,saveTermHeight,
        //             twid,saveTermWidth,tt_linespacing[vmode],saveLineSpacing);
        //    debug(F111,"KClient::writeMe() param change",buf,0);
        //}
        saveTermHeight = thi;
        saveTermWidth = twid;
        saveLineSpacing = tt_linespacing[vmode];
        saveHorzIsV = hisv;

        syncSize();
    }

    // Make sure soft-fonts are ready
    softFont.refresh(twid, thi - tt_status[vmode]);

    int lineHeight = font->getFontSpacedH();

    int w, h, screen_height, screen_thi;
    getSize( w, h );

    // Take backup copies of these, as during smooth-scroll events we'll be
    // rendering one line more than the screen height.
    screen_thi = thi;
    screen_height = h;

    if (smoothScrollRendering) {
        // When rendering a smooth-scroll, we'll render as many lines as IKTerm
        // gives us rather than whatever the screen height is.
        thi = clientPaint->height;
        h = thi * lineHeight;
    }

    // Erase the background with default color
    RECT r;
    r.left = 0;
    r.top = 0;
    r.right = w;
    r.bottom = h;

    DWORD rgb = cell_video_attr_background_rgb(geterasecolor(vmode));
    if ( rgb != savebgcolor ) {
        DeleteObject( bgBrush );
        bgBrush = CreateSolidBrush( rgb );
        savebgcolor = rgb;
    }
    FillRect( hdc(), &r, bgBrush); 

    if (!cell_video_attr_equal(colornormal, normalAttr) ||
            decscnm != screenNormal) {
        normalAttr = colornormal;
        screenNormal = decscnm;
        DeleteObject (ruledLinePen);
        DWORD rgb = decscnm
            ? cell_video_attr_background_rgb(colornormal)
            : cell_video_attr_foreground_rgb(colornormal);
        ruledLinePen = CreatePen(PS_SOLID, 1, rgb);
    }

    // Figure out which soft fonts are valid
    int validSoftFonts[DRCS_BUFFERS];
    for (int j = 1; j <= DRCS_BUFFERS; j++) {
        validSoftFonts[j-1] = softFont.fontValid(j);
    }

    // Then paint the data
    // This code batches up strings with matching attributes so multiple
    // characters can be painted in one go. Soft-fonts are counted as a kind of
    // attribute here, so characters using a one soft-font will not be included
    // with a batch of characters using another soft-font or no soft-font at all
    wc = 0;
    int xpos, i;
    int totlen = clientPaint->len;
    cell_video_attr_t attr = cell_video_attr_init_vio_attribute(255);
    ushort lattr = ushort(-1);
    vt_char_attr_t effect = ushort(-1);
    vt_cell_attr_t cellAttr = 0;
    int lastDrcsBufferId = NO_SOFT_FONT;
    // The following collects up contiguous strings of text that are all on the
    // same line and have the same attributes, allowing the text to be painted
    // in one go rather than a character at a time.
    for( i = 0; i < totlen; i++ )
    {
        int bufferId = DRCS_BUFFER_ID(textBuffer[i]);
        if (bufferId != NO_SOFT_FONT && !validSoftFonts[bufferId-1]) {
            /* This character is associated with a soft-font, but the font
             * doesn't exist. This shouldn't really happen outside of looking at
             * the scrollback after a hard reset, so just replace the character
             * with the backwards question mark in the normal font. */
            bufferId = NO_SOFT_FONT;
            textBuffer[i] = 0x2426; // backwards question mark
        }

        xpos = i % twid;
        if( !xpos || !cell_video_attr_equal(attrBuffer[i], attr)
                || effectBuffer[i] != effect
                || cellAttrBuffer[i] != cellAttr
                || lastDrcsBufferId != bufferId )
        {
            kws = &(workStore[wc]);

            kws->x = xpos * font->getFontW() - _xoffset;
            kws->y = (i / twid) * lineHeight;

            kws->offset = i;
            if( wc )
                workStore[wc-1].length = i - workStore[wc-1].offset;
            attr = kws->attr = attrBuffer[i];
            effect = kws->effect = effectBuffer[i];
            cellAttr = kws->cellAttr = cellAttrBuffer[i];
            lastDrcsBufferId = kws->fontBuffer = bufferId;
            wc++;
        }
    }
    if( wc )
        workStore[wc-1].length = i - workStore[wc-1].offset;

    Bool blinkOn = FALSE;
    Bool blink = FALSE;
    if( cursorCount < maxCursorCount ) {
        cursorCount += tt_update;
        if( cursorCount >= blinkInterval )
            blinkOn = TRUE;
    }
    else
        cursorCount = 0;

    if (cursorCount%300 == 0) {
        if (ikterm->getCursorPos() && (_inFocus || (!_inFocus && cursor_displayed)))
        {
            if ( cursorena[vmode] && cursor_on_visible_page(vmode) || markmodeflag[vmode] != notmarking) {
                if ( !tt_cursor_blink ) {
                    if (!cursor_displayed && _inFocus || cursor_displayed && !_inFocus) {
                        cursor_displayed = _inFocus;
                    } 
                } else {
                    cursor_displayed = !cursor_displayed;

                }
            } else { /* cursor disabled */
                cursor_displayed = 0;
            }
        }
    }

    RECT rect;
    BOOL anyRuledLines = FALSE;
    ws_blinking = 0;
    for( i = 0; i < wc; i++ )
    {
        kws = &(workStore[i]);

        // Determine if this screen contains any blinking data besides the
        // cursor
        if( kws->effect & VT_CHAR_ATTR_BLINK ) {
            ws_blinking = 1;
        }

        if( !cell_video_attr_equal(prevAttr, kws->attr) )
        {
            prevAttr = kws->attr;

            /* These are the default colors used by the console window, set by   */
            /* the SET GUI RGB commands and a few escape sequences               */
			SetBkColor( hdc(), cell_video_attr_background_rgb(prevAttr));
            textColor = cell_video_attr_foreground_rgb(prevAttr);
			SetTextColor( hdc(), textColor);
        }

        // If a soft-font is being used, we can skip all of this as none of
        // these attributes will apply.
        if( prevEffect != kws->effect && kws->fontBuffer == NO_SOFT_FONT )
        {
            prevEffect = kws->effect;
            Bool normal = (prevEffect == VT_CHAR_ATTR_NORMAL) ? TRUE : FALSE;
            Bool bold = truebold && ((prevEffect & VT_CHAR_ATTR_BOLD) ? TRUE : FALSE);
            Bool dim = truedim && ((prevEffect & VT_CHAR_ATTR_DIM) ? TRUE : FALSE);
            Bool underline = trueunderline && ((prevEffect & VT_CHAR_ATTR_UNDERLINE) ? TRUE : FALSE);
            Bool italic = trueitalic && ((prevEffect & VT_CHAR_ATTR_ITALIC) ? TRUE : FALSE);
			Bool crossedOut = truecrossedout && ((prevEffect & VT_CHAR_ATTR_CROSSEDOUT) ? TRUE : FALSE);
            blink = trueblink && ((prevEffect & VT_CHAR_ATTR_BLINK) ? TRUE : FALSE);

            if (decstglt == DECSTGLT_ALTERNATE) {
                // DECSTGLT says we should show attributes as colors. DECATCUM
                // and DECATCBM *may* say we should still do true underline and
                // true blink even while doing these as colors.
                bold = FALSE; dim = FALSE; italic = FALSE;

                underline = decatcum && ((prevEffect & VT_CHAR_ATTR_UNDERLINE) ? TRUE : FALSE);
                blink = decatcbm && ((prevEffect & VT_CHAR_ATTR_BLINK) ? TRUE : FALSE);

                normal = !underline && !blink;
            }

			if (dim) {
				// Cut the colours intensity by dividing each component by 2.
				// We can just quickly do this with a right-shift. Because there
			    // are three separate numbers packed in we need to mask out the
				// high bit of each as part of this so that the low bit of each
				// value to the left is erased.
				SetTextColor( hdc(), (textColor >> 1) & 0x7F7F7F);
			} else {
			    // If not dim, reset the textColor just in case dim is turned
			    // off without the text colour also changing.
			    SetTextColor( hdc(), textColor);
			}

            if( normal )
                getFont()->resetFont( hdc() );
            else if (crossedOut) {
                if( bold && underline && italic )
                    getFont()->setCrossedOutBoldUnderlineItalic( hdc() );
                else if( bold && underline )
                    getFont()->setCrossedOutBoldUnderline( hdc() );
                else if( underline && italic )
                    getFont()->setCrossedOutUnderlineItalic( hdc() );
                else if( bold && italic )
                    getFont()->setCrossedOutBoldItalic( hdc() );
                else if( bold )
                    getFont()->setCrossedOutBold( hdc() );
                else if( underline )
                    getFont()->setCrossedOutUnderline( hdc() );
                else if ( italic )
                    getFont()->setCrossedOutItalic( hdc() );
		    	else
                    getFont()->setCrossedOut( hdc() );
            }
            else if( bold && underline && italic )
                getFont()->setBoldUnderlineItalic( hdc() );
            else if( bold && underline )
                getFont()->setBoldUnderline( hdc() );
            else if( underline && italic )
                getFont()->setUnderlineItalic( hdc() );
            else if( bold && italic )
                getFont()->setBoldItalic( hdc() );
            else if( bold )
                getFont()->setBold( hdc() );
            else if( underline )
                getFont()->setUnderline( hdc() );
            else if ( italic )
                getFont()->setItalic( hdc() );
        }

        if (prevCellAttr != kws->cellAttr ) {
            prevCellAttr = kws->cellAttr;

            anyRuledLines = anyRuledLines ||
                            prevCellAttr & CA_ATTR_LEFT_BORDER ||
                            prevCellAttr & CA_ATTR_TOP_BORDER ||
                            prevCellAttr & CA_ATTR_RIGHT_BORDER ||
                            prevCellAttr & CA_ATTR_BOTTOM_BORDER;
        }

        // Update the rect that covers the area we've gathered common attributes
        // for. All text that belongs in this rect will be painted in one go
        // with the selected attributes.
        SetWorkStoreRect(&rect, kws, font, twid, thi, margin());

        if( !blink || blinkOn ) {
            if (kws->fontBuffer == NO_SOFT_FONT) {
                ExtTextOutW( hdc(), rect.left, rect.top,
                    ETO_CLIPPED | ETO_OPAQUE,
                    &rect,
                    (wchar_t*) &(textBuffer[ kws->offset ]),
                    kws->length,
                    (int*)&interSpace );
            } else {
                softFont.textOut(
                    hdc(),
                    rect.left,
                    rect.top,
                    (wchar_t*) &(textBuffer[ kws->offset ]),
                    kws->length,
                    kws->fontBuffer);
            }
        }
        else {
            ExtTextOut( hdc(), rect.left, rect.top, 
			ETO_CLIPPED | ETO_OPAQUE, 
			&rect, 0, 0, 0 );
        }
    }

    // Stretch the contents of any double-height or double-wide lines to
    // double-height or double-wide size. The EMF output doesn't support doing
    // this.
    for( i = 0; i < thi; i++ )
    {
        lattr = lineAttr[i];
        if( lattr == VT_LINE_ATTR_NORMAL )
            continue;

        Bool dblWide = lattr & VT_LINE_ATTR_DOUBLE_WIDE ? TRUE : FALSE;
        Bool dblHigh = lattr & VT_LINE_ATTR_DOUBLE_HIGH ? TRUE : FALSE;
        Bool upper = lattr & VT_LINE_ATTR_UPPER_HALF ? TRUE : FALSE;
        Bool lower = lattr & VT_LINE_ATTR_LOWER_HALF ? TRUE : FALSE;
    
        int destx = 0;
        int desty = i * lineHeight;
        int destw = twid * font->getFontW();
        int desth = lineHeight;

        int srcx = 0;
        int srcy = upper ? desty : (desty + lineHeight/2);
        int srcw = dblWide ? (destw/2) : destw;
        int srch = dblHigh ? (desth/2) : desth;
        if( dblWide && !dblHigh )
            srcy = desty;

        StretchBlt( _hdcScratch, destx, desty, destw, desth
            , hdc(), srcx, srcy, srcw, srch, SRCCOPY );

        StretchBlt( hdc(), destx, desty, destw, desth
            , _hdcScratch, destx, desty, destw, desth, SRCCOPY );

        if( i == thi - 1 ) {
            desty += lineHeight;
            StretchBlt( _hdcScratch, 0, 0, destw, margin()
                , hdc(), 0, desty, srcw, margin(), SRCCOPY );

            StretchBlt( hdc(), 0, desty, destw, margin()
                , _hdcScratch, 0, 0, destw, margin(), SRCCOPY );
        }
    }

    // Deal with any ruled lines - this has to be done separately after
    // any double-height/double-wide lines as these are not supposed to affect
    // ruled lines.
    if (anyRuledLines) {
        Bool rlLeft = FALSE, rlTop = FALSE, rlRight = FALSE, rlBottom = FALSE;

        for( i = 0; i < wc; i++ ) {
            kws = &(workStore[i]);

            rlLeft = kws->cellAttr & CA_ATTR_LEFT_BORDER;
            rlTop = kws->cellAttr & CA_ATTR_TOP_BORDER;
            rlRight = kws->cellAttr & CA_ATTR_RIGHT_BORDER;
            rlBottom = kws->cellAttr & CA_ATTR_BOTTOM_BORDER;

            if (rlLeft || rlTop || rlRight || rlBottom) {
                RECT rect;
                SetWorkStoreRect(&rect, kws, font, twid, thi, margin());

                // Apply the smooth scroll offset (if there is one) when smooth
                // scrolling between the left and right margins so the lines
                // don't jump around. One line height is added or removed to
                // counteract the offset applied at compositing time.
                if (smoothScrollRendering && scroll_lrm) {
                    int offset;
                    if (smooth_scroll_upwards) {
                        offset = smoothScrollOffset(lineHeight) - lineHeight;
                        rect.top += offset;
                        rect.bottom += offset;
                    } else {
                        offset = smoothScrollOffset(lineHeight);
                        rect.top += offset;
                        rect.bottom += offset;
                    }
                }

                drawRuledLines(hdc(), ruledLinePen, kws->length, font, rect,
                               rlTop, rlBottom, rlLeft, rlRight);
            }
        }
    }

    // Draw the cursor
    if( clientPaint->cursorVisible && cursor_displayed && _inFocus &&
            !smoothScrollRendering) {
        int adjustedH = font->getFontH();
        if( tt_cursor == 2 )        // full
            ;
        else if( tt_cursor == 1 )   // half
            adjustedH /= 2;
        else                        // underline
            adjustedH = kglob->sysMets->borderHeight() + 1;

        cursorRect.left = clientPaint->cursorPt.x * font->getFontW() 
                - _xoffset;
        cursorRect.top = clientPaint->cursorPt.y * font->getFontSpacedH() 
                + ( font->getFontH() - adjustedH );
        cursorRect.right = cursorRect.left + font->getFontW();
        cursorRect.bottom = cursorRect.top + adjustedH;
        ToggleCursor( hdc(), &cursorRect );
        cursor_displayed = 1;
    } else
        cursor_displayed = 0;

    if( vmode /* clientID */ == VTERM && !::isConnected() )
        drawDisabledState( w, screen_height );

#ifdef COMMENT
    if ( IsZoomed(parent->hwnd()) ) {
        int cx, cy, cw, ch;
        ((KAppWin *)parent)->getClientCoord( cx, cy, cw, ch );

        StretchBlt( hdcScreen(), 0, 0, cw, ch, hdc(), 0, 0, w, screen_height, SRCCOPY );
    } else 
#endif /* COMMENT */
    {
        if (smoothScrollRendering) {
            int offset = smoothScrollOffset(lineHeight);

            if (scroll_tbm || scroll_lrm) {
                // We're scrolling only part of the screen. First, copy the
                // non-scrolling bits prepared at the start of the scroll event,
                // then copy the scrolling portion.

                // The bottom coordinate of the terminal area (excluding the status
                // line)
                int terminal_bottom = lineHeight * (screen_thi - (tt_status[vmode]?1:0));

                // In pixels:
                int top;   // Top of the scroll region (0 if top margin not set)
                int height; // Height of the scroll region
                int left;  // Left of the scroll region
                int width; // Width of the scroll region

                if (scroll_tbm) {
                    int bottom; // Bottom of the scroll region

                    top = smooth_scroll_top * lineHeight;
                    bottom = (smooth_scroll_bottom+1) * lineHeight;
                    height = (smooth_scroll_bottom - smooth_scroll_top + 1) * lineHeight;

                    // The top:
                    BitBlt( _hdcScratch,
                        0, 0,    // left, top
                        w, top,  // width, height
                        blinkOn ? _hdcSScrollBlinkOn : _hdcSScrollBlinkOff,
                        0, 0,    // left, top
                        SRCCOPY
                        );

                    // The bottom:
                    BitBlt( _hdcScratch,
                        0, bottom,
                        w,
                        terminal_bottom - bottom +
                            (!tt_status[vmode] ? margin():0),
                        blinkOn ? _hdcSScrollBlinkOn : _hdcSScrollBlinkOff,
                        0, bottom, SRCCOPY );

                } else {
                    top = 0;
                    height = terminal_bottom;
                }

                if (scroll_lrm) {
                    int right;

                    left = smooth_scroll_left * font->getFontW();
                    right = (smooth_scroll_right + 1) * font->getFontW();
                    width = (1+smooth_scroll_right - smooth_scroll_left) * font->getFontW();

                    // The left:
                    BitBlt( _hdcScratch,
                        0, top,                 // x, y
                        left, height,       // width, height
                        blinkOn ? _hdcSScrollBlinkOn : _hdcSScrollBlinkOff,
                        0, top,                 // x, y
                        SRCCOPY
                        );

                    // The right:
                    BitBlt( _hdcScratch,
                        right, top,
                        w - right, height, // width, height
                        blinkOn ? _hdcSScrollBlinkOn : _hdcSScrollBlinkOff,
                        right, top,
                        SRCCOPY
                        );
                } else {
                    left = 0;
                    width = w;
                }

                // Then copy only the scrolling region
                BitBlt( _hdcScratch,
                    left, top,
                    width, height,
                    hdc(),
                    left,
                    offset, SRCCOPY );

                // Now grab the status line
                if (tt_status[vmode]) {
                    BitBlt(_hdcScratch,
                        0, terminal_bottom,
                        w, lineHeight + margin(),  // margin to fill to screen edge
                        _hdcSScrollBlinkOn,
                        0,
                        (screen_thi-1) * lineHeight,  // this includes the status line, so -1
                        SRCCOPY);
                }

                BitBlt( hdcScreen(), 0, 0, w, screen_height,
                    _hdcScratch, 0, 0, SRCCOPY );


            } else { // Smooth-scrolling the entire screen

                int terminal_bottom = lineHeight * (thi - (tt_status[vmode]?1:0));

                // Copy everything except the status line
                BitBlt( _hdcScratch,
                    0, 0, w, terminal_bottom,
                    hdc(),
                    0, offset, SRCCOPY );

                // Now grab the status line
                if (tt_status[vmode]) {
                    BitBlt(_hdcScratch,
                       0, (screen_thi-1) * lineHeight,
                       w, lineHeight + margin(),
                       hdc(),
                       0, (thi-1) * lineHeight,
                       SRCCOPY);
                }

                BitBlt( hdcScreen(), 0, 0, w, screen_height,
                    _hdcScratch, 0, 0, SRCCOPY );
            }
        } else {
           BitBlt( hdcScreen(), 0, 0, w, h, hdc(), 0, 0, SRCCOPY );
        }
    }

    // adjust the vertical scrollbar
    //
    if (!scroll_tbm && !scroll_lrm) {
        int max = (clientPaint->beg == 0) ? clientPaint->end + 1 : clientPaint->page_length;
        vert->setRange( max, thi - (tt_status[vmode]?1:0), FALSE );
        horz->setRange( clientPaint->maxWidth, VscrnGetWidth(vmode) );

        vscrollpos = max - (thi - (tt_status[vmode]?1:0));
        if( scrollflag[vmode /* clientID */] )
        {
            if( clientPaint->scrolltop >= clientPaint->beg ) {
                vscrollpos = clientPaint->scrolltop - clientPaint->beg;
            }
            else if( clientPaint->scrolltop < clientPaint->end ) {
                vscrollpos = tt_scrsize[vmode /* clientID */] - clientPaint->beg
                        + clientPaint->scrolltop;
            }
        }
    }

    // adjust the horizontal scrollbar
    vert->setPos( vscrollpos );
    hscrollpos = VscrnGetScrollHorz(vmode);
    horz->setPos( hscrollpos );
}

void KClient::SetWorkStoreRect(RECT* rect, _K_WORK_STORE* kws, KFont *font,
        int terminalCellsWide, int terminalCellsHigh, int margin) {

    rect->left = kws->x;
    rect->top = kws->y;
    rect->right = rect->left + kws->length * font->getFontW();
    rect->bottom = rect->top + font->getFontSpacedH();

    if( rect->right == terminalCellsWide * font->getFontW() )
        rect->right += margin;

    if( rect->bottom == terminalCellsHigh * font->getFontSpacedH() )
        rect->bottom += margin;
}

// Draws the specified DECterm Ruled Lines for the specified rectangle.
void KClient::drawRuledLines(HDC hdc, HPEN pen, int cells, KFont* font,
            RECT rect, BOOL rlTop, BOOL rlBottom, BOOL rlLeft, BOOL rlRight) {
    if (rlTop || rlBottom || rlLeft || rlRight) {
        HPEN oldPen = (HPEN)SelectObject( hdc, pen );

        // Top ruled line along the extent of this string. Offsets are to
        // account for the offsets on the vertical lines
        if (rlTop) {
            // Line from [rect.left, rect.top] to [rect.right, rect.top]
            MoveToEx(hdc, rect.left , rect.top, NULL);
            LineTo(hdc, rect.right, rect.top);
        }

        // Bottom ruled line along the extent of this string
        if (rlBottom) {
            // Line from [rect.left, rect.bottom] to [rect.right, rect.bottom]
            MoveToEx(hdc, rect.left , rect.bottom-1, NULL);
            LineTo(hdc, rect.right, rect.bottom-1);
        }

        // Vertical ruled lines on the left/right borders of cells.
        if (rlLeft || rlRight) {
            for (int i = 0; i < cells; i++) {
                //int left = rect.left + 1 + i * font->getFontW();
                //int right = left + font->getFontW() -1;

                int left = rect.left + i * font->getFontW();
                int right = left + font->getFontW() - 1;

                if (rlLeft) {
                    // Line on the left cell border. DECterm draws this one inside
                    // the cell causing left and right borders to double up in to
                    // a thicker line
                    MoveToEx(hdc, left, rect.top, NULL);
                    LineTo(hdc, left, rect.bottom);
                }

                if (rlRight) {
                    // Line on the right cell border
                    MoveToEx(hdc, right, rect.top, NULL);
                    LineTo(hdc, right, rect.bottom);
                }
            }
        }

        SelectObject(hdc, oldPen);
    }
}


/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Renders the specified vscrn to a device context for saving.
 *  - Does not render the cursor
 *  - All blinking elements are rendered as visible
 *  - Uses the current font and font size
 * Its based on a copy&paste of writeMe() with blinking and cursor related
 * bits removed and its own copy of IKTerm and the K_CLIENT_PAINT to grab
 * a single snapshot of the state of the vscrn.
 *
 * TODO: Someday writeMe() and this function should be refactored into a
 *       bunch of methods and/or classes to try and maximise code sharing.
 */
BOOL KClient::renderToDc(HDC hdc, KFont *font, int vnum, int margin, bool blinkOn) {
    int twid, thi;
    BOOL success = TRUE;

    // Initialise font
    font->setFont(hdc);

    // Get dimensions of the vscrn
    // Can't use ::getDimensions as that always returns the current vscrn
    twid = VscrnGetWidth(vnum);
    thi = VscrnGetDisplayHeight(vnum);

    // Initialise soft-fonts
    KSoftFont softFont;
    softFont.setSize(font->getFontW(), font->getFontSpacedH());
    softFont.refresh(twid, thi - tt_status[vmode]);

    // Get dimensions
    int w, h;
    h = thi * font->getFontSpacedH();
    w = twid * font->getFontW();

    int validSoftFonts[DRCS_BUFFERS];
    for (int j = 1; j <= DRCS_BUFFERS; j++) {
        validSoftFonts[j-1] = softFont.fontValid(j);
    }

    // Erase the background with default color
    RECT r;
    r.left = 0;
    r.top = 0;
    r.right = w;
    r.bottom = h;

    DWORD rgb = cell_video_attr_background_rgb(geterasecolor(vnum));
    HBRUSH bgBrush = CreateSolidBrush( rgb );
    FillRect( hdc, &r, bgBrush);
    DeleteObject( bgBrush );

    // Grab a snapshot of the terminal screen
    long maxcells = ::getMaxDim();
    K_CLIENT_PAINT clientPaint;
    uchar *workBuf;
    allocateClientPaintBuffers(&clientPaint, maxcells, &workBuf);
    ushort* textBuffer            = clientPaint.textBuffer;
    cell_video_attr_t* attrBuffer = clientPaint.attrBuffer;
    vt_char_attr_t* effectBuffer  = clientPaint.effectBuffer;
    ushort* lineAttr              = clientPaint.lineAttr;
	vt_cell_attr_t* cellAttrBuffer	= clientPaint.cellAttrBuffer;

    K_WORK_STORE *workStore = new K_WORK_STORE[ maxcells ];
    memset( workStore, '\0', sizeof(K_WORK_STORE) * maxcells );

    IKTerm ikterm(vnum, &clientPaint);
    if (!ikterm.getDrawInfo(vnum)) {
        // TODO: Retry a few times in case we just couldn't get the mutex in time.
        delete workBuf;
        delete workStore;
        return FALSE;
    }

    // Collect data up into runs of text with the same attributes
    int xoffset = 0, yoffset = 0;  // Used by scrolling
    int wc = 0;
    int xpos, i;
    int totlen = clientPaint.len;
    _K_WORK_STORE* kws;

    cell_video_attr_t attr = cell_video_attr_init_vio_attribute(255);
    ushort lattr = ushort(-1);
    vt_char_attr_t effect = ushort(-1);
	vt_cell_attr_t cellAttr = 0;
    int lastDrcsBufferId = NO_SOFT_FONT;
    for( i = 0; i < totlen; i++ )
    {
        int bufferId = DRCS_BUFFER_ID(textBuffer[i]);
        if (bufferId != NO_SOFT_FONT && !validSoftFonts[bufferId-1]) {
            /* This character is associated with a soft-font, but the font
             * doesn't exist. This shouldn't really happen outside of looking at
             * the scrollback after a hard reset, so just replace the character
             * with the backwards question mark in the normal font. */
            bufferId = NO_SOFT_FONT;
            textBuffer[i] = 0x2426; // backwards question mark
        }

        xpos = i % twid;
        if( !xpos || !cell_video_attr_equal(attrBuffer[i], attr)
					|| effectBuffer[i] != effect
					|| cellAttrBuffer[i] != cellAttr
                    || lastDrcsBufferId != bufferId )
        {
            kws = &(workStore[wc]);

            kws->x = xpos * font->getFontW() - xoffset;
            kws->y = (i / twid) * font->getFontSpacedH();

            kws->offset = i;
            if( wc )
                workStore[wc-1].length = i - workStore[wc-1].offset;
            attr = kws->attr = attrBuffer[i];
            effect = kws->effect = effectBuffer[i];
			cellAttr = kws->cellAttr = cellAttrBuffer[i];
            lastDrcsBufferId = kws->fontBuffer = bufferId;
            wc++;
        }
    }
    if( wc )
        workStore[wc-1].length = i - workStore[wc-1].offset;

    int interSpace[MAXNUMCOL];
    int fontw = font->getFontW();
    for( i = 0; i < MAXNUMCOL; i++ )
        interSpace[i] = fontw;

    // Output text in runs with matching attributes
    RECT rect;
    BOOL anyRuledLines = FALSE;
    cell_video_attr_t prevAttr = cell_video_attr_init_vio_attribute(255);
    vt_char_attr_t prevEffect = uchar(-1);
    vt_cell_attr_t prevCellAttr = 0;
    COLORREF textColor;
    BOOL blink;
	Bool rlLeft = FALSE, rlTop = FALSE, rlRight = FALSE, rlBottom = FALSE;
    for( i = 0; i < wc; i++ )
    {
        kws = &(workStore[i]);
        if( !cell_video_attr_equal(prevAttr, kws->attr) )
        {
            prevAttr = kws->attr;

            /* These are the default colors used by the console window           */
            /* This needs to be replaced by a class that allows the color values */
            /* to be set by the user and stored somewhere.                       */
            /* The RGBTable is now set via SET GUI RGB commands.                 */
            SetBkColor( hdc, cell_video_attr_background_rgb(prevAttr));
            textColor = cell_video_attr_foreground_rgb(prevAttr);
            SetTextColor( hdc, textColor);
        }

        // If a soft-font is being used, we can skip all of this as none of
        // these attributes will apply.
        if( prevEffect != kws->effect && kws->fontBuffer == NO_SOFT_FONT)
        {
            prevEffect = kws->effect;
            Bool normal = (prevEffect == VT_CHAR_ATTR_NORMAL) ? TRUE : FALSE;
            Bool bold = truebold && ((prevEffect & VT_CHAR_ATTR_BOLD) ? TRUE : FALSE);
            Bool dim = truedim && ((prevEffect & VT_CHAR_ATTR_DIM) ? TRUE : FALSE);
            Bool underline = trueunderline && ((prevEffect & VT_CHAR_ATTR_UNDERLINE) ? TRUE : FALSE);
            Bool italic = trueitalic && ((prevEffect & VT_CHAR_ATTR_ITALIC) ? TRUE : FALSE);
			Bool crossedOut = truecrossedout && ((prevEffect & VT_CHAR_ATTR_CROSSEDOUT) ? TRUE : FALSE);
            blink = trueblink && ((prevEffect & VT_CHAR_ATTR_BLINK) ? TRUE : FALSE);

            if (decstglt == DECSTGLT_ALTERNATE) {
                // DECSTGLT says we should show attributes as colors. DECATCUM
                // and DECATCBM *may* say we should still do true underline and
                // true blink even while doing these as colors.
                bold = FALSE; dim = FALSE; italic = FALSE;

                underline = decatcum && ((prevEffect & VT_CHAR_ATTR_UNDERLINE) ? TRUE : FALSE);
                blink = decatcbm && ((prevEffect & VT_CHAR_ATTR_BLINK) ? TRUE : FALSE);

                normal = !underline && !blink;
            }

            if (dim) {
                // Cut the colours intensity by dividing each component by 2.
                // We can just quickly do this with a right-shift. Because there
                // are three separate numbers packed in we need to mask out the
                // high bit of each as part of this so that the low bit of each
                // value to the left is erased.
                SetTextColor( hdc, (textColor >> 1) & 0x7F7F7F);
            } else {
                // If not dim, reset the textColor just in case dim is turned
                // off without the text colour also changing.
                SetTextColor( hdc, textColor);
            }

            if( normal )
                font->resetFont( hdc );
            else if (crossedOut) {
                if( bold && underline && italic )
                    font->setCrossedOutBoldUnderlineItalic( hdc );
                else if( bold && underline )
                    font->setCrossedOutBoldUnderline( hdc );
                else if( underline && italic )
                    font->setCrossedOutUnderlineItalic( hdc );
                else if( bold && italic )
                    font->setCrossedOutBoldItalic( hdc );
                else if( bold )
                    font->setCrossedOutBold( hdc );
                else if( underline )
                    font->setCrossedOutUnderline( hdc );
                else if ( italic )
                    font->setCrossedOutItalic( hdc );
		    	else
                    font->setCrossedOut( hdc );
            }
            else if( bold && underline && italic )
                font->setBoldUnderlineItalic( hdc );
            else if( bold && underline )
                font->setBoldUnderline( hdc );
            else if( underline && italic )
                font->setUnderlineItalic( hdc );
            else if( bold && italic )
                font->setBoldItalic( hdc );
            else if( bold )
                font->setBold( hdc );
            else if( underline )
                font->setUnderline( hdc );
            else if ( italic )
                font->setItalic( hdc );
        }

        if (prevCellAttr != kws->cellAttr ) {
            prevCellAttr = kws->cellAttr;

            anyRuledLines = anyRuledLines ||
                            prevCellAttr & CA_ATTR_LEFT_BORDER ||
                            prevCellAttr & CA_ATTR_TOP_BORDER ||
                            prevCellAttr & CA_ATTR_RIGHT_BORDER ||
                            prevCellAttr & CA_ATTR_BOTTOM_BORDER;
        }

        // Update the rect that covers the area we've gathered common attributes
        // for. All text that belongs in this rect will be painted in one go
        // with the selected attributes.
        SetWorkStoreRect(&rect, kws, font, twid, thi, margin);

        if (!blink || blinkOn) {
            if (kws->fontBuffer == NO_SOFT_FONT) {
                ExtTextOutW( hdc, rect.left, rect.top,
                             ETO_CLIPPED | ETO_OPAQUE,
                             &rect,
                             (wchar_t*) &(textBuffer[ kws->offset ]),
                             kws->length,
                             (int*)&interSpace );
            } else {
                softFont.textOut(
                        hdc,
                        rect.left,
                        rect.top,
                        (wchar_t*) &(textBuffer[ kws->offset ]),
                        kws->length,
                        kws->fontBuffer);
            }
        } else {
            ExtTextOut( hdc, rect.left, rect.top,
            ETO_CLIPPED | ETO_OPAQUE,
            &rect, 0, 0, 0 );
        }
    }

    // Stretch the contents of any double-height or double-wide lines to
    // double-height or double-wide size. The EMF output doesn't support doing
    // this.
    // TODO: Implement this using a different font size instead, and update the
    //       HELP SAVE content accordingly.
    if ((GetDeviceCaps(hdc, RASTERCAPS) & RC_STRETCHBLT) &&
            GetObjectType(hdc) != OBJ_ENHMETADC) {
        HDC hdcScratch = CreateCompatibleDC( hdc );
        HBITMAP scratchBitmap = CreateCompatibleBitmap( hdc, w, h );
        SelectObject( hdcScratch, scratchBitmap );
        for( i = 0; i < thi; i++ )
        {
            lattr = lineAttr[i];
            if( lattr == VT_LINE_ATTR_NORMAL )
                continue;

            Bool dblWide = lattr & VT_LINE_ATTR_DOUBLE_WIDE ? TRUE : FALSE;
            Bool dblHigh = lattr & VT_LINE_ATTR_DOUBLE_HIGH ? TRUE : FALSE;
            Bool upper = lattr & VT_LINE_ATTR_UPPER_HALF ? TRUE : FALSE;
            Bool lower = lattr & VT_LINE_ATTR_LOWER_HALF ? TRUE : FALSE;

            int destx = 0;
            int desty = i * font->getFontSpacedH();
            int destw = twid * font->getFontW();
            int desth = font->getFontSpacedH();

            int srcx = 0;
            int srcy = upper ? desty : (desty + font->getFontSpacedH()/2);
            int srcw = dblWide ? (destw/2) : destw;
            int srch = dblHigh ? (desth/2) : desth;
            if( dblWide && !dblHigh )
                srcy = desty;

            StretchBlt( hdcScratch, destx, desty, destw, desth
                , hdc, srcx, srcy, srcw, srch, SRCCOPY );

            StretchBlt( hdc, destx, desty, destw, desth
                , hdcScratch, destx, desty, destw, desth, SRCCOPY );

            if( i == thi - 1 ) {
                desty += font->getFontSpacedH();
                StretchBlt( hdcScratch, 0, 0, destw, margin
                    , hdc, 0, desty, srcw, margin, SRCCOPY );

                StretchBlt( hdc, 0, desty, destw, margin
                    , hdcScratch, 0, 0, destw, margin, SRCCOPY );
            }
        }
        DeleteDC( hdcScratch );
        DeleteObject( scratchBitmap );
    }

    // Deal with any ruled lines - this has to be done separately after
    // any double-height/double-wide lines as these are not supposed to affect
    // ruled lines.
    if (anyRuledLines) {
        Bool rlLeft = FALSE, rlTop = FALSE, rlRight = FALSE, rlBottom = FALSE;
        DWORD rgb = decscnm
            ? cell_video_attr_background_rgb(colornormal)
            : cell_video_attr_foreground_rgb(colornormal);
        HPEN ruledLinePen = CreatePen(PS_SOLID, 1, rgb);

        for( i = 0; i < wc; i++ ) {
            kws = &(workStore[i]);

            rlLeft = kws->cellAttr & CA_ATTR_LEFT_BORDER;
            rlTop = kws->cellAttr & CA_ATTR_TOP_BORDER;
            rlRight = kws->cellAttr & CA_ATTR_RIGHT_BORDER;
            rlBottom = kws->cellAttr & CA_ATTR_BOTTOM_BORDER;

            if (rlLeft || rlTop || rlRight || rlBottom) {
                RECT rect;
                SetWorkStoreRect(&rect, kws, font, twid, thi, margin);

                drawRuledLines(hdc, ruledLinePen, kws->length, font, rect,
                                rlTop, rlBottom, rlLeft, rlRight);
            }
        }
        DeleteObject(ruledLinePen);
    }

    success = GdiFlush();

    delete workBuf;
    delete workStore;

    return success;
}

#ifdef CK_SAVE_TO_IMAGE
/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Renders the the specified vscrn to a device-independent bitmap (DIB)
 * suitable for saving. Returns an HBITMAP, and the pixel data via
 * outPixels. If an error occurs, NULL is returned.
 */
HBITMAP KClient::renderToBitmap(int vnum, DWORD **outPixels) {
    KFont font(this->font->getLogFont());

    HDC hDC = GetDC(NULL);
    HDC hMDC = CreateCompatibleDC(hDC);

    font.setFont(hMDC); // Required for getFontW/getFontSpacedH to work

    int h = VscrnGetDisplayHeight(vnum) * font.getFontSpacedH();
    int w = VscrnGetWidth(vnum) * font.getFontW();

    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = w;
    bmi.bmiHeader.biHeight = h;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;

    DWORD *pixels;
    HBITMAP hbmp = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, (void**)&pixels, NULL, NULL);
    *outPixels = pixels;

    ReleaseDC(NULL, hDC);

    HBITMAP hOldBmp = (HBITMAP)SelectObject(hMDC, hbmp);
    DeleteObject(hOldBmp);

    BOOL success = renderToDc(hMDC, &font, vnum, margin());

    DeleteObject(hMDC);

    if (!success) {
        DeleteObject(hbmp);
        hbmp = NULL;
    }

    return hbmp;
}
#endif /* CK_SAVE_TO_IMAGE */

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Renders the specified vscrn to an EMF file with the specified filename.
 * If an error occurs, FALSE is returned. Double Height/Double Wide lines are
 * rendered as Single Height/Single Wide due to limitations in GDIs EMF
 * recorder. */
BOOL KClient::renderToEmfFile(int vnum, char* filename) {

    HDC hdcEMF = CreateEnhMetaFile(NULL, filename, NULL,
        vnum == VTERM ? "Kermit-95\0Terminal Screen\0"
            : vnum == VCMD ?  "Kermit-95\0Command Screen\0"
                : "Kermit-95\0Other Screen\0");

    KFont font(this->font->getLogFont());

    BOOL success = renderToDc(hdcEMF, &font, vnum, margin());

    HENHMETAFILE hemf = CloseEnhMetaFile(hdcEMF);
    DeleteEnhMetaFile(hemf);

    return success;
}

#ifdef CK_SAVE_TO_IMAGE
#ifdef CK_HAVE_GDIPLUS
/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Gets the Class Identifier for the specified GDI+ Image Encoder. This
 * comes from Microsoft sample code:
 *   https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-retrieving-the-class-identifier-for-an-encoder-use
 */
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT  num = 0;          // number of image encoders
    UINT  size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize(&num, &size);
    if(size == 0)
        return -1;  // Failure

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo == NULL)
        return -1;  // Failure

    GetImageEncoders(num, size, pImageCodecInfo);

    for(UINT j = 0; j < num; ++j)
    {
        if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;  // Success
        }
    }

    free(pImageCodecInfo);
    return -1;  // Failure
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Saves an HBITMAP to an image file using GDI+ */
BOOL KClient::saveBitmap(HBITMAP hbmp, const char* filename, const wchar_t* format) {

    // GDI+ wants the filename as a wide string.
    size_t newsize = strlen(filename) + 1;
    wchar_t* wcfilename = new wchar_t[newsize];
    size_t convertedChars = 0;
    mbstowcs_s(&convertedChars, wcfilename, newsize, filename, _TRUNCATE);

    BOOL success = TRUE;
    Status  stat;

    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    // Convert to a GDI+ bitmap
    Bitmap * bmp = Bitmap::FromHBITMAP(hbmp, NULL);

    // Get the CLSID of the PNG encoder.
    CLSID   encoderClsid;
    if (GetEncoderClsid(format, &encoderClsid)) {
        // Save to PNG file
        stat = bmp->Save(wcfilename, &encoderClsid, NULL);

        if(stat != Ok) {
            printf("Failed to save image: stat = %d\n", stat);
            success = FALSE;
        }
    } else {
        printf("Failed to save image: could not get encoder\n");
        success = FALSE;
    }

    delete wcfilename;
    delete bmp;
    GdiplusShutdown(gdiplusToken);

    return success;
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Renders to an image file using GDI+
 *    vnum        The vscrn to render
 *    filename    File to save to
 *    format      Image format, eg image/png
 * If an error occurs, FALSE is returned.
 */
BOOL KClient::renderToImageFile(int vnum, char* filename, const wchar_t* format) {
    BOOL success = TRUE;

    // Render vscrn to a bitmap
    DWORD *pixels;
    HBITMAP hbmp = renderToBitmap(vnum, &pixels);

    success = saveBitmap(hbmp, filename, format);

    DeleteObject(hbmp);

    return success;
}

BOOL KClient::renderToPngFile(int vnum, char* filename) {

    return renderToImageFile(vnum, filename, L"image/png");
}

BOOL KClient::renderToGifFile(int vnum, char* filename) {
    return renderToImageFile(vnum, filename, L"image/gif");
}

BOOL KClient::saveFontBuffer(int buffer_number, const char* filename,
    const wchar_t *format) {

    BOOL stretched = FALSE;

    if (buffer_number < 1 ) return FALSE;
    if (buffer_number > DRCS_BUFFERS) {
        buffer_number -= DRCS_BUFFERS;
        stretched = TRUE;
    }
    if (buffer_number > DRCS_BUFFERS) return FALSE;

    HBITMAP font = softFont.getFontBitmap(buffer_number, stretched);

    if (font == NULL) return FALSE;
    return saveBitmap(font, filename, format);
}

#endif /* CK_HAVE_GDIPLUS */

/* GDI+ supposedly has a bitmap encoder which would be much less code than
 * all of the stuff below, but I couldn't get it to work in my brief attempt.
 * The code below would have still been required for when GDI+ isn't available
 * anyhow. */


/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Creates a suitable BITMAPINFO struct for the specified HBITMAP. The
 * code comes from a Microsoft sample:
 *   https://learn.microsoft.com/en-us/windows/win32/gdi/storing-an-image
 */
PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)
{
    BITMAP bmp;
    PBITMAPINFO pbmi;
    WORD    cClrBits;

    // Retrieve the bitmap color format, width, and height.
    if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp)) {
        return NULL;
    }

    // Convert the color format to a count of bits.
    cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
    if (cClrBits == 1)
        cClrBits = 1;
    else if (cClrBits <= 4)
        cClrBits = 4;
    else if (cClrBits <= 8)
        cClrBits = 8;
    else if (cClrBits <= 16)
        cClrBits = 16;
    else if (cClrBits <= 24)
        cClrBits = 24;
    else cClrBits = 32;

    // Allocate memory for the BITMAPINFO structure. (This structure
    // contains a BITMAPINFOHEADER structure and an array of RGBQUAD
    // data structures.)

     if (cClrBits < 24)
         pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
                    sizeof(BITMAPINFOHEADER) +
                    sizeof(RGBQUAD) * (1<< cClrBits));

     // There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel

     else
         pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
                    sizeof(BITMAPINFOHEADER));

    // Initialize the fields in the BITMAPINFO structure.

    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = bmp.bmWidth;
    pbmi->bmiHeader.biHeight = bmp.bmHeight;
    pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
    pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
    if (cClrBits < 24)
        pbmi->bmiHeader.biClrUsed = (1<<cClrBits);

    // If the bitmap is not compressed, set the BI_RGB flag.
    pbmi->bmiHeader.biCompression = BI_RGB;

    // Compute the number of bytes in the array of color
    // indices and store the result in biSizeImage.
    // The width must be DWORD aligned unless the bitmap is RLE
    // compressed.
    pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
                                  * pbmi->bmiHeader.biHeight;
    // Set biClrImportant to 0, indicating that all of the
    // device colors are important.
    pbmi->bmiHeader.biClrImportant = 0;
    return pbmi;
 }

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
/* Renders the specified vscrn to a Windows device-independent bitmap
 * (.bmp or .dib) file with the specified name. If an error occurs, FALSE
 * is returned. */
BOOL KClient::renderToBmpFile(int vnum, char* filename) {
    DWORD *pixels;
    HBITMAP hbmp = renderToBitmap(vnum, &pixels);

    if (hbmp == NULL) {
        printf("Failed to render bitmap\n");
        return FALSE;
    }

    PBITMAPINFO pbi = NULL;
    PBITMAPINFOHEADER pbih = NULL;     // bitmap info-header
    DWORD dwTotal = 0;                 // total count of bytes
    DWORD cb = 0;                      // incremental count of bytes
    DWORD dwTmp = 0;
    int ret = 0;

    pbi = CreateBitmapInfoStruct(NULL, hbmp);
    if(pbi == NULL) {
        printf("Failed to create bitmap\n");
        DeleteObject(hbmp);
        return FALSE;
    }
    pbih = (PBITMAPINFOHEADER) pbi;

    HANDLE hf = CreateFile(filename,
                           GENERIC_READ | GENERIC_WRITE,
                           (DWORD) 0,
                           NULL,
                           CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL,
                           (HANDLE) NULL);
    if (hf == INVALID_HANDLE_VALUE) {
        printf("Failed to create output file %s\n", filename);
        free(pbi);
        DeleteObject(hbmp);
        return FALSE;
    }

    BITMAPFILEHEADER hdr;
    hdr.bfType = 0x4d42;        // 0x42 = "B" 0x4d = "M"
    // Compute the size of the entire file.
    hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
                 pbih->biSize + pbih->biClrUsed
                 * sizeof(RGBQUAD) + pbih->biSizeImage);
    hdr.bfReserved1 = 0;
    hdr.bfReserved2 = 0;

    // Compute the offset to the array of color indices.
    hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
                    pbih->biSize + pbih->biClrUsed
                    * sizeof (RGBQUAD);

    // Copy the BITMAPFILEHEADER into the .BMP file.
    if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
        (LPDWORD) &dwTmp,  NULL)) {
        printf("Error writing to output file %s\n", filename);
        CloseHandle(hf);
        free(pbi);
        DeleteObject(hbmp);
        return FALSE;
    }

    // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
    if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
                  + pbih->biClrUsed * sizeof (RGBQUAD),
                  (LPDWORD) &dwTmp, ( NULL))) {
        printf("Error writing to output file %s\n", filename);
        CloseHandle(hf);
        free(pbi);
        DeleteObject(hbmp);
        return FALSE;
    }

    // Copy the array of color indices into the .BMP file.
    dwTotal = cb = pbih->biSizeImage;

    if (!WriteFile(hf, (LPSTR) pixels, (int) cb, (LPDWORD) &dwTmp,NULL)) {
        printf("Error writing to output file %s\n", filename);
        CloseHandle(hf);
        free(pbi);
        DeleteObject(hbmp);
        return FALSE;
    }

    CloseHandle(hf);
    free(pbi);
    DeleteObject(hbmp);

    return TRUE;
}
#endif /* CK_SAVE_TO_IMAGE */

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
void KClient::drawDisabledState( int w, int h )
{
    RECT rect;
    rect.top = rect.left = 0;
    rect.right = w;
    rect.bottom = h;
    FillRect( _hdcScratch, &rect, disabledBrush );
    BitBlt( hdc(), 0, 0, w, h, _hdcScratch, 0, 0, SRCPAINT );
}

/*------------------------------------------------------------------------
------------------------------------------------------------------------*/
Bool KClient::message( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    Bool done = FALSE;
    _msgret = 1;
    //debug(F111,"KClient::message","msg",msg);
    switch( msg )
    {
    case WM_PAINT:
        {
            //debug(F111,"KClient::message","WM_PAINT",msg);
            PAINTSTRUCT ps;
            if (BeginPaint( hwnd, &ps ))
                paint();
            EndPaint( hwnd, &ps );
            done = TRUE;
            break;
        }

    case WM_ERASEBKGND:
        //debug(F111,"KClient::message","WM_ERASEBKGND",msg);
        done = TRUE;
        break;
        
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
        debug(F111,"KClient::message","WM_{LRM}BUTTONDOWN (fall-through)",msg);
        takeFocus();
        // NOTE: FALL THROUGH !!!
        //

    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDBLCLK:
    case WM_MOUSEMOVE:
        {
            debug(F111,"KClient::message","WM_{LRM}BUTTON{UP,DBLCLK},MOUSEMOVE",msg);
            short bitx = (short) LOWORD(lParam);
            short bity = (short) HIWORD(lParam);
            int x = bitx > 0 ? bitx / font->getFontW() : 0;
            int y = bity > 0 ? bity / font->getFontSpacedH() : 0;
            ikterm->mouseEvent( hwnd, msg, wParam, x, y );
            break;
        }

#ifdef WM_MOUSEWHEEL
    case WM_MOUSEWHEEL:
        {
            POINT pt;
            pt.x = LOWORD(lParam);
            pt.y = HIWORD(lParam);
            ScreenToClient(hwnd, &pt);

            debug(F111,"KClient::message","WM_MOUSEWHEEL",msg);
            int x = pt.x > 0 ? pt.x / font->getFontW() : 0;
            int y = pt.y > 0 ? pt.y / font->getFontSpacedH() : 0;
            ikterm->mouseEvent( hwnd, msg, wParam, x, y );
            break;
        }
#endif

#ifdef COMMENT
    /* This is the obvious, simple and easy way to do it and this does work. But
     * I decided to instead pretend scrolling up one notch is one click of the
     * 4th mouse button, and scrolling down one notch is one click of the 5th
     * mouse button, plus some UI changes to treat buttons 4 and 5 specially
     * so you can't configure the click type (click, doubleclick, drag) for
     * them. This way scroll events can be mapped just like any button click.
     * Leaving this here in case the complicated way turns out to be broken
     * horribly.
     * */
    case WM_MOUSEWHEEL: {
          int zDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
          debug(F111, "WM_MOUSEWHEEL", "zDelta", zDelta);
          if (zDelta > 0) {
            do {
                //if (!scrollflag[vmode]) break;
                ::scrollback( vmode /* clientID */, K_UPONE );
                zDelta--;
            } while (zDelta > 0);
          } else {
            do {
              if (!scrollflag[vmode]) break;
              ::scrollback( vmode /* clientID */, K_DNONE );
              zDelta++;
            } while (zDelta < 0);
          }
          paint();
          done=1;
          break;
      }
#endif

    case WM_SETFOCUS:
        //debug(F111,"KClient::message","WM_SETFOCUS",msg);
        _inFocus = TRUE;
        ::dokverb(vmode, K_FOCUS_IN );
        break;

    case WM_KILLFOCUS:
        //debug(F111,"KClient::message","WM_KILLFOCUS",msg);
        _inFocus = FALSE;
        ::dokverb(vmode, K_FOCUS_OUT );
        break;

    case WM_VSCROLL:
        //debug(F111,"KClient::message","WM_VSCROLL",msg);
        done = vert->message( hwnd, msg, wParam, lParam );
        break;

    case WM_HSCROLL:
        //debug(F111,"KClient::message","WM_HSCROLL",msg);
        done = horz->message( hwnd, msg, wParam, lParam );
        break;

#ifdef COMMENT
    case WM_SYSKEYDOWN:     // needed for VK_F10
        //debug(F111,"KClient::message","WM_SYSKEYDOWN",msg);
        {
            // check the static array for VK_ key
            // VK_SCROLL is the last key in the array
            //
            if( wParam <= VK_SCROLL && keyArray[ wParam ] ) {
                done = ikterm->virtkeyEvent( wParam, lParam, TRUE );
                done = TRUE;
                //if( done )
                _msgret = 0;
                processKey = FALSE;
            }
            else
                processKey = TRUE;
            break;
            }

    case WM_KEYDOWN:
        //debug(F111,"KClient::message","WM_KEYDOWN",msg);
        {
            // check the static array for VK_ key
            // VK_SCROLL is the last key in the array
            //
            if( wParam <= VK_SCROLL && keyArray[ wParam ] ) {
                done = ikterm->virtkeyEvent( wParam, lParam, TRUE );
                done = TRUE;
                //if( done )
                _msgret = 0;
                processKey = FALSE;
            }
            else
                processKey = TRUE;
            break;
        }

    case WM_SYSDEADCHAR:
    case WM_DEADCHAR:
    case WM_SYSCHAR:
    case WM_CHAR:
        //debug(F111,"KClient::message","WM_{[SYS],[DEAD]}CHAR",msg);
        {
            if( processKey ) {
                done = ikterm->keyboardEvent( wParam, lParam, TRUE );
                done = TRUE;
                //if( done )
                _msgret = 0;
                // processKey = 0;
            }
            break;
        }       

    case WM_SYSKEYUP:     // needed for VK_F10
    case WM_KEYUP:
        //debug(F111,"KClient::message","WM_[SYS]KEYUP",msg);
        {
            if( wParam <= VK_SCROLL && keyArray[ wParam ] )
                done = ikterm->virtkeyEvent( wParam, lParam, FALSE );
            else if ( processKey )
                done = ikterm->keyboardEvent( wParam, lParam, FALSE );
            done = TRUE;
            break;
        }
#else /* COMMENT */
    case WM_SYSKEYDOWN:     // needed for VK_F10
        //debug(F111,"KClient::message","WM_SYSKEYDOWN",msg);
        done = ikterm->newKeyboardEvent( (int)wParam, (long)lParam, TRUE, TRUE );
        break;
    case WM_KEYDOWN:
        //debug(F111,"KClient::message","WM_KEYDOWN",msg);
        done = ikterm->newKeyboardEvent( (int)wParam, (long)lParam, TRUE, FALSE );
        break;

    case WM_SYSKEYUP:     // needed for VK_F10
        //debug(F111,"KClient::message","WM_SYSKEYUP",msg);
        done = ikterm->newKeyboardEvent( (int)wParam, (long)lParam, FALSE, TRUE );
        break;
    case WM_KEYUP:
        //debug(F111,"KClient::message","WM_KEYUP",msg);
        done = ikterm->newKeyboardEvent( (int)wParam, (long)lParam, FALSE, FALSE );
        break;
#endif /* COMMENT */
    }
    return done;
}
