Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

913 lines
24 KiB

/************************* Module Header *************************************
* page.c
* Functions associated with page concept, such as new frame,
* startdoc, abortdoc etc.
*
* HISTORY:
* 14:25 on Mon 21 Jan 1991 -by- Lindsay Harris [lindsayh]
* Created it, based partly on windows control.c
*
*
* Copyright (C) 1991 - 1993 Microsoft Corporation
*
****************************************************************************/
#include <stddef.h>
#include <windows.h>
#include <winddi.h>
#include "libproto.h"
#include "win30def.h"
#include "udmindrv.h"
#include "udpfm.h"
#include "uddevice.h"
#include "udresrc.h"
#include "pdev.h"
#include "udresid.h"
#include "udrender.h"
#include "fnenabl.h"
#include "stretch.h" /* Palette data */
#include "rasdd.h"
#include "udfnprot.h"
/*
* The following is the default order for sending initialising commands
* to a printer. Typically this data is supplied by a minidriver, but if
* it follows this order, the data may be omitted.
*/
static const short pDefInitOrder[] =
{
PC_ORD_PAPER_SOURCE,
PC_ORD_PAPER_DEST,
PC_ORD_PAPER_SIZE,
PC_ORD_RESOLUTION,
PC_ORD_TEXTQUALITY,
0
};
static const short pDefInitOrientationOnly[] =
{
PC_ORD_ORIENTATION,
0
};
/*
* Local function prototypes.
*/
BOOL bSendInitialisation( PDEV *, BOOL );
BOOL bRenderBM( PDEV *, SURFOBJ * );
BOOL bRenderJNL( PDEV *, SURFOBJ * );
void vEndPage( PDEV * );
/*************************** Function Header *******************************
* DrvStartDoc
* Function called to process START_DOC transaction. That is, the
* start of a document. Thus it is necessary to send the initialisation
* data to the printer, as well as initialise internal data.
*
* RETURNS:
* Nothing
*
* HISTORY:
* 16:07 on Sat 26 Jun 1993 -by- Lindsay Harris [lindsayh]
* Changed to set the RestartPDEV flag; initialisation done StartPage.
*
* 13:31 on Fri 13 Sep 1991 -by- Lindsay Harris [lindsayh]
* Changed yet again - engine now calls StartDoc/StartPage instead
*
* 13:51 on Fri 01 Mar 1991 -by- Lindsay Harris [lindsayh]
* Changed name & parameters to match DDI spec, ver 0.9
*
* 15:02 on Mon 21 Jan 1991 -by- Lindsay Harris [lindsayh]
* Created it, based on control.c from Window's unidrv.
*
***************************************************************************/
BOOL
DrvStartDoc( pso, pwszDocName, dwJobId )
SURFOBJ *pso; /* The surface of interest */
PWSTR pwszDocName; /* Document name */
DWORD dwJobId; /* The Spool Job Identification Number */
{
/*
* Simply set the RestartPDEV bit in the UD_PDEV, then this
* will force complete initialisation during DrvStartPage().
*/
UNREFERENCED_PARAMETER( pwszDocName );
UNREFERENCED_PARAMETER( dwJobId );
((UD_PDEV *)((PDEV *)(pso->dhpdev))->pUDPDev)->fMode |= PF_RESTART_PG;
((UD_PDEV *)((PDEV *)(pso->dhpdev))->pUDPDev)->fMode |= PF_DOCSTARTED;
return TRUE;
}
/************************** Function Header *********************************
* DrvStartPage
* Called at the start of each new page. So send the start a new page
* command, and adjust any red tape that is related.
*
* RETURNS:
* Nothing
*
* HISTORY:
* 16:16 on Sat 26 Jun 1993 -by- Lindsay Harris [lindsayh]
* Call bSendInitialisation to do the initialising - twice!
*
* 13:44 on Fri 13 Sep 1991 -by- Lindsay Harris [lindsayh]
* Created it, from DrvStartPage of old DDI spec.
*
*****************************************************************************/
BOOL
DrvStartPage( pso )
SURFOBJ *pso; /* The surface of interest! */
{
PDEV *pPDev;
UD_PDEV *pUDPDev; /* UNIDRV's pdev */
RECTL rcPage; /* Page size: for erasing the surface */
pPDev = (PDEV *)pso->dhpdev;
pUDPDev = pPDev->pUDPDev;
if( pUDPDev->fMode & PF_RESTART_PG )
{
/*
* There has been a DrvRestartPDev() call, so we need to call
* bSendInitialisation to put the printer into the desired mode.
*/
if( !bSendInitialisation( pPDev, TRUE ) )
return FALSE;
}
/*
* Most important step is to set the background colour throughout
* the bitmap.
*/
rcPage.top = 0;
rcPage.left = 0;
rcPage.bottom = (pUDPDev->bBanding) ? pUDPDev->szlBand.cy : pUDPDev->szlPage.cy;
rcPage.right = (pUDPDev->bBanding) ? pUDPDev->szlBand.cx : pUDPDev->szlPage.cx;
EngEraseSurface( pso, &rcPage,
((PAL_DATA *)(pPDev->pPalData))->iWhiteIndex );
/*
* If this is NOT a page printer, we need to initialise the position
* sorting functions, so that we print the page unidirectionally.
*/
if( (pUDPDev->fMDGeneral & MD_SERIAL) && pUDPDev->cFonts )
{
if( !bCreatePS( pPDev ) )
{
#if DBG
DbgPrint( "Rasdd!DrvStartPage: Cannot create text sorting areas\n" );
#endif
return FALSE;
}
}
bSendInitialisation( pPDev, FALSE ); /* BEGIN_PAGE and beyond */
/*
* Also set the clip region to the entire page. The top entry
* is especially important for DrvTextOut, where the value is added
* as required. If not set to 0 here, then no output appears after
* the first page!. Set the values only if they are not set. They may
* have been set in DrvStartBanding.
*/
if (!pUDPDev->bBanding)
{
pUDPDev->rcClipRgn.top = 0;
pUDPDev->rcClipRgn.left = 0;
pUDPDev->rcClipRgn.right = pUDPDev->szlPage.cx;
pUDPDev->rcClipRgn.bottom = pUDPDev->szlPage.cy;
}
/*
* Also set the current position to some illegal position, so that
* we make no assumptions about where we are.
*/
XMoveto( pUDPDev, -1, MV_UPDATE );
YMoveto( pUDPDev, -1, MV_UPDATE );
if( pUDPDev->fMode & PF_SEIKO )
{
/*
* The Seiko printer requires sending the initialisation data
* at the start of each page. So set the restart pdev bit,
* so that next time we come in here, we send the data. Doing
* it this way means we do not do it twice for the first page.
*/
pUDPDev->fMode |= PF_RESTART_PG;
}
return TRUE;
}
/***************************** Function Header *****************************
* bSendInitialisation
* Function to send the initialisation data to the printer. Called
* during DrvStartPage, so that we send the right stuff before
* any data is sent.
*
* RETURNS:
* TRUE/FALSE, FALSE being failure to send data/Abort.
*
* HISTORY:
* Monday November 29 1993 -by- Norman Hendley [normanh]
* Enabled setting of page lengths for custom papersizes
* Enabled Page Protection command
*
* 15:53 on Sat 26 Jun 1993 -by- Lindsay Harris [lindsayh]
* Moved from DrvStartDoc() to support SETCOPYCOUNT escape.
*
***************************************************************************/
BOOL
bSendInitialisation( pPDev, bBeginPage )
PDEV *pPDev; /* The key to all our data */
BOOL bBeginPage; /* Set if only process BEGIN_PAGE and later */
{
int iCmd;
const short *psID;
DATAHDR *pdh;
UD_PDEV *pUDPDev;
/*
* Send the job initialisation data to the printer. The commands required
* and the sequence in which they should be sent is included in the
* minidriver data.
*/
pUDPDev = pPDev->pUDPDev;
pdh = pUDPDev->pdh;
if( pUDPDev->orgwStartDocCmdOrder == (OCD)NOT_USED )
{
/* No data, so use the default initialisation list. */
psID = pDefInitOrder;
}
else if( (pUDPDev->fMode & PF_RESET_NOINIT_PG) )
{
/* If the flag PF_RESET_NOINIT_PG is set,initialization data which will
* cause page to be ejected will not be sent. This Flag is set when
* DrvRestPDEV is called and the condition requires that page shouldn't
* be ejected (like in Duplex portrait and landscape case).
*/
psID = pDefInitOrientationOnly;
/* We want only to do this when needed. */
pUDPDev->fMode &= ~PF_RESET_NOINIT_PG;
}
else
{
/* Minidriver has one, so use its values. */
psID = (short *)((LPSTR)pdh + pdh->loHeap +
pUDPDev->orgwStartDocCmdOrder);
}
/*
* If this is not the begin page initialisation, then we need to
* send only the latter part of the initialisation sequence.
*/
if( !bBeginPage )
{
/* Skip until we find PC_ORD_BEGINPAGE */
while( *psID && *psID != PC_ORD_BEGINPAGE )
++psID;
}
for( ; *psID != 0; psID++ )
{
iCmd = -1; /* Default nothing doing */
switch( *psID )
{
case PC_ORD_ORIENTATION:
if( pUDPDev->iOrient != DMORIENT_LANDSCAPE )
iCmd = CMD_PC_PORTRAIT; /* Portrait unless landscape */
else
iCmd = CMD_PC_LANDSCAPE;
break;
case PC_ORD_BEGINDOC:
iCmd = CMD_PC_BEGIN_DOC;
break;
case PC_ORD_MULT_COPIES:
WriteChannel( pUDPDev, CMD_PC_MULT_COPIES, pUDPDev->sCopies );
break;
case PC_ORD_DUPLEX:
if( pUDPDev->fMDGeneral & MD_DUPLEX )
{
if( pUDPDev->sDuplex != DMDUP_SIMPLEX )
iCmd = CMD_PC_DUPLEX_ON;
else
iCmd = CMD_PC_DUPLEX_OFF;
}
break;
case PC_ORD_DUPLEX_TYPE:
if( pUDPDev->sDuplex == DMDUP_VERTICAL )
iCmd = CMD_PC_DUPLEX_VERT;
else
if( pUDPDev->sDuplex == DMDUP_HORIZONTAL )
iCmd = CMD_PC_DUPLEX_HORZ;
break;
case PC_ORD_TEXTQUALITY:
iCmd = CMD_TEXTQUALITY;
break;
case PC_ORD_PAPER_SOURCE:
iCmd = CMD_PAPERSOURCE;
break;
case PC_ORD_PAPER_SIZE:
WriteChannel( pUDPDev, CMD_PAPERSIZE,
( ( (pUDPDev->iOrient == DMORIENT_LANDSCAPE ) ?
pUDPDev->pfPaper.ptPhys.x :pUDPDev->pfPaper.ptPhys.y )
* pUDPDev->Resolution.ptTextScale.y),
( ( (pUDPDev->iOrient == DMORIENT_LANDSCAPE ) ?
pUDPDev->pfPaper.ptPhys.y :pUDPDev->pfPaper.ptPhys.x )
* pUDPDev->Resolution.ptTextScale.x) );
break;
case PC_ORD_PAPER_DEST:
iCmd = CMD_PAPERDEST;
break;
case PC_ORD_PAPER_QUALITY:
iCmd = CMD_PAPERQUALITY;
break;
case PC_ORD_RESOLUTION:
iCmd = CMD_RES_SELECTRES;
break;
case PC_ORD_SETCOLORMODE:
if( pUDPDev->Resolution.fDump & RES_DM_COLOR )
{
iCmd = CMD_DC_GC_SETCOLORMODE;
/*
* sandram added code for Color LaserJet
*/
if ((pUDPDev->fMode & PF_24BPP) || (pUDPDev->fMode & PF_8BPP))
if (pUDPDev->sColor == DMCOLOR_MONOCHROME)
WriteChannel (pUDPDev, CMD_DC_PC_MONOCHROMEMODE);
}
break;
case PC_ORD_PAGEPROTECT:
iCmd = CMD_PAGEPROTECT;
break;
// added for WDL release by Derry Durand [derryd] - July 1995
case PC_ORD_PRINTDENSITY:
iCmd = CMD_PRINTDENSITY;
break;
case PC_ORD_IMAGECONTROL:
iCmd = CMD_IMAGECONTROL;
break;
case PC_ORD_BEGINPAGE:
if( bBeginPage )
return TRUE; /* All done in here */
iCmd = CMD_PC_BEGIN_PAGE;
break;
default: /* In case it's something we don't know */
iCmd = -1;
break;
}
if( iCmd >= 0 )
WriteChannel( pUDPDev, iCmd );
}
/*
* If there is a palette to download, now is the time to do it.
*/
// sandram - added code for Color LaserJet
if (pUDPDev->fColorFormat & DC_SEND_PALETTE)
{
// should be Color LaserJet
if (pUDPDev->fMode & PF_8BPP)
v8BPPLoadPal ( pPDev );
else if (pUDPDev->fMode & PF_SEIKO)
vSeikoLoadPal( pPDev );
}
/* We want only to do this when needed. */
pUDPDev->fMode &= ~PF_RESTART_PG;
return TRUE;
}
/***************************** Function Header *****************************
* DrvSendPage
* Called when the user has completed the drawing for this page.
* This function corresponds to DEVESC_NEWPAGE.
*
* RETURNS:
* Nothing
*
* HISTORY:
* 13:17 on Fri 01 Mar 1991 -by- Lindsay Harris [lindsayh]
* Wrote it, possibly subject to change.
*
***************************************************************************/
BOOL
DrvSendPage( pso )
SURFOBJ *pso;
{
/*
* Not a great deal to do - we basically get the surface details
* and call the output functions. If this is a surface, then nothing
* needs to be done, but journal files require more complex
* processing.
*/
BOOL bRet; /* Return value */
PDEV *pPDev; /* Access to all that is important */
bRet = FALSE;
pPDev = (PDEV *) pso->dhpdev;
#if DBG
if( pPDev->ulID != PDEV_ID )
{
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
#endif
switch( pso->iType )
{
case STYPE_BITMAP: /* Engine managed bitmap */
bRenderBM( pPDev, pso );
vEndPage( pPDev );
bRet = TRUE;
break;
#if DBG
case STYPE_DEVICE: /* Device surface - should not happen */
DbgPrint( "Rasdd!DrvSendPage: STYPE_DEVICE should not happen\n" );
break;
default:
DbgPrint( "Rasdd!DrvSendPage: UNKNOWN SURFACE TYPE\n" );
break;
#endif
}
return bRet;
}
/***************************** Function Header *****************************
* DrvNextBand
* Called to tell the driver to realize the contents of the band
* which has been drawn to the passed in surface and then return
* the origin of the next band.
*
* RETURNS:
* TRUE is successful FALSE otherwise.
*
* HISTORY:
* 13:17 on Mond 09 Jan 1995 -by- Gerrit van Wingerden [gerritv]
* Wrote it.
*
***************************************************************************/
BOOL
DrvNextBand( SURFOBJ *pso, POINTL *pptl )
{
UD_PDEV *pUDPDev;
BOOL bMore;
SIZEL sizl;
PDEV *pPDev; /* Access to all that is important */
pPDev = (PDEV *) pso->dhpdev;
pUDPDev = pPDev->pUDPDev;
sizl = pUDPDev->szlBand;
if( pPDev->pvRenderData == NULL )
return FALSE;
if( !bRender( pPDev, pPDev->pvRenderDataTmp, pso->sizlBitmap, pso->pvBits ) )
{
if ( ((RENDER *)(pPDev->pvRenderDataTmp))->plrWhite )
DRVFREE(((RENDER *)(pPDev->pvRenderDataTmp))->plrWhite);
((RENDER *)(pPDev->pvRenderData))->plrWhite =
((RENDER *)(pPDev->pvRenderDataTmp))->plrWhite = NULL;
return(FALSE);
}
if ( ((RENDER *)(pPDev->pvRenderDataTmp))->plrWhite )
DRVFREE(((RENDER *)(pPDev->pvRenderDataTmp))->plrWhite);
((RENDER *)(pPDev->pvRenderData))->plrWhite =
((RENDER *)(pPDev->pvRenderDataTmp))->plrWhite = NULL;
{
RECTL rcPage;
rcPage.top = 0;
rcPage.left = 0;
rcPage.bottom = (pUDPDev->bBanding) ? pUDPDev->szlBand.cy : pUDPDev->szlPage.cy;
rcPage.right = (pUDPDev->bBanding) ? pUDPDev->szlBand.cx : pUDPDev->szlPage.cx;
EngEraseSurface( pso, &rcPage,
((PAL_DATA *)(pPDev->pPalData))->iWhiteIndex );
}
switch( pUDPDev->iBandDirection )
{
case SW_DOWN: /* Moving down the page */
pUDPDev->rcClipRgn.top += sizl.cy;
pUDPDev->rcClipRgn.bottom += sizl.cy;
/* Make sure we do not run off the bottom */
bMore = pUDPDev->rcClipRgn.top < pUDPDev->szlPage.cy;
if( pUDPDev->rcClipRgn.bottom > pUDPDev->szlPage.cy )
{
/* Less to do, so the partial band size */
pUDPDev->rcClipRgn.bottom = pUDPDev->szlPage.cy;
sizl.cy = pUDPDev->szlPage.cy - pUDPDev->rcClipRgn.top;
}
break;
case SW_RTOL: /* LaserJet style: right to left */
pUDPDev->rcClipRgn.left -= sizl.cx;
pUDPDev->rcClipRgn.right -= sizl.cx;
bMore = pUDPDev->rcClipRgn.right > 0;
if( pUDPDev->rcClipRgn.left < 0 )
{
/* Final band, so reduce the size */
pUDPDev->rcClipRgn.left = 0;
sizl.cx = pUDPDev->rcClipRgn.right;
}
break;
case SW_LTOR: /* Dot matrix: left to right */
pUDPDev->rcClipRgn.left += sizl.cx;
pUDPDev->rcClipRgn.right += sizl.cx;
bMore = pUDPDev->rcClipRgn.left < pUDPDev->szlPage.cx;
if( pUDPDev->rcClipRgn.right > pUDPDev->szlPage.cx )
{
/* Final band - limit the size */
pUDPDev->rcClipRgn.right = pUDPDev->szlPage.cx;
sizl.cx = pUDPDev->szlPage.cx - pUDPDev->rcClipRgn.left;
}
break;
default:
#if DBG
DbgPrint("rasdd!NextBand bogus banding direction\n");
#endif
return(FALSE);
}
if( bMore )
{
pptl->x = pUDPDev->rcClipRgn.left;
pptl->y = pUDPDev->rcClipRgn.top;
#if PRINT_INFO
DbgPrint("rasdd!DrvNextBand: Next band %d %d\n", pptl->x, pptl->y );
#endif
}
else
{
BOOL bRet = bRenderPageEnd( pPDev );
vEndPage( pPDev );
pptl->x = pptl->y = -1;
return(bRet);
}
return(TRUE);
}
/***************************** Function Header *****************************
* DrvStartBanding
* Called to tell the driver to prepare for banding and return the
* origin of the first band.
*
* RETURNS:
* TRUE is successful FALSE otherwise.
*
* HISTORY:
* 13:17 on Mond 09 Jan 1995 -by- Gerrit van Wingerden [gerritv]
* Wrote it.
*
***************************************************************************/
BOOL
DrvStartBanding( pso, pptl )
SURFOBJ *pso;
POINTL *pptl;
{
PDEV *pPDev; /* Access to all that is important */
SIZEL sizl;
UD_PDEV *pUDPDev; /* Unidrive'e PDEV */
pPDev = (PDEV *) pso->dhpdev;
#if PRINT_INFO
DbgPrint( "rasdd: doing DrvStartBanding\n" );
#endif
pUDPDev = pPDev->pUDPDev; /* For our convenience */
sizl = pUDPDev->szlBand; /* Size of existing bitmap */
if( pPDev->pvRenderData == NULL )
return FALSE; /* Should not happen, nasty if it does */
if( !bRenderStartPage( pPDev ) )
return FALSE;
/* reset the render data for this band */
*(RENDER *)(pPDev->pvRenderDataTmp) = *(RENDER *)(pPDev->pvRenderData);
if( pUDPDev->fMode & PF_ROTATE )
{
/* We do the rotation, but can be one of two ways! */
pUDPDev->rcClipRgn.top = 0;
pUDPDev->rcClipRgn.bottom = pUDPDev->szlPage.cy;
if( pUDPDev->fMode & PF_CCW_ROTATE )
{
/* LaserJet style rotation */
pUDPDev->rcClipRgn.left = pUDPDev->szlPage.cx - sizl.cx;
pUDPDev->rcClipRgn.right = pUDPDev->szlPage.cx;
pUDPDev->iBandDirection = SW_RTOL;
}
else
{
/* Dot matrix style rotation */
pUDPDev->rcClipRgn.left = 0;
pUDPDev->rcClipRgn.right = sizl.cx;
pUDPDev->iBandDirection = SW_LTOR;
}
}
else
{
/* Go as is! */
pUDPDev->rcClipRgn.top = 0;
pUDPDev->rcClipRgn.left = 0;
pUDPDev->rcClipRgn.right = sizl.cx;
pUDPDev->rcClipRgn.bottom = sizl.cy;
pUDPDev->iBandDirection = SW_DOWN;
}
pptl->x = pUDPDev->rcClipRgn.left;
pptl->y = pUDPDev->rcClipRgn.top;
return(TRUE);
}
/************************ Function Header ***********************************
* bRenderBM
* Render an engine managed surface. This means we do little, as the
* surface is passed to us, and basically we pass it on to the usual
* rendering code.
*
* RETURNS:
* TRUE/FALSE, whatever is returned from bRender()
*
* HISTROY:
* 14:40 on Mon 01 Jun 1992 -by- Lindsay Harris [lindsayh]
* First incarnation, now that journalling is complete.
*
****************************************************************************/
BOOL
bRenderBM( pPDev, pso )
PDEV *pPDev; /* Access to everything */
SURFOBJ *pso; /* The surface: we want bitmap pointer */
{
BOOL bRet; /* Return code */
RENDER RenderData; /* Rendering data passed to bRender() */
if( pPDev->pvRenderData == NULL )
return FALSE;
RenderData = *(RENDER *)(pPDev->pvRenderData);
bRet = FALSE;
if( bRenderStartPage( pPDev ) )
{
bRet = bRender( pPDev, &RenderData, pso->sizlBitmap, pso->pvBits );
((RENDER *)(pPDev->pvRenderData))->plrWhite = RenderData.plrWhite;
}
bRenderPageEnd( pPDev );
return bRet;
}
/***************************** Function Header *****************************
* vEndPage
* Called when a page has been rendered. Mainly used to complete the
* page printing, either by form feeding it from the printer,
* or using graphics commands to move to the bottom of page.
*
* RETURNS:
* Nothing
*
* HISTORY:
* 15:35 on Mon 07 Oct 1991 -by- Lindsay Harris [lindsayh]
* Insane logic about when to use ptRes.x vs ptRes.y
*
* 15:05 on Mon 21 Jan 1991 -by- Lindsay Harris [lindsayh]
* Based on Window's UNIDRV code.
*
**************************************************************************/
void
vEndPage( pPDev )
PDEV *pPDev;
{
UD_PDEV *lpdv;
lpdv = pPDev->pUDPDev;
/*
* Eject the page or move to the bottom, as appropriate. If
* available, use the form feed character, else use graphics mode.
*/
if( lpdv->fMode & PF_USE_FF )
WriteChannel( lpdv, CMD_CM_FF );
else
{
/*
* The pfPaper.ptRes data has the X, Y coordinates swapped if
* this page is printed in landscape. SO, if we are in landscape,
* we need to unswap them.
*/
int yEnd; /* Last scan line on page */
yEnd = lpdv->iOrient == DMORIENT_LANDSCAPE ?
lpdv->pfPaper.ptRes.x : lpdv->pfPaper.ptRes.y;
YMoveto( lpdv, yEnd, MV_GRAPHICS );
}
WriteChannel( lpdv, CMD_PC_ENDPAGE );
FlushSpoolBuf( lpdv );
if( pPDev->pPSHeader )
vFreePS( pPDev ); /* Done with this page */
return;
}
/************************ Function Header ***********************************
* DrvEndDoc
* The end of the document - simply flush the buffer!
*
* RETURNS:
* TRUE
*
* HISTORY:
* 13:48 on Fri 13 Sep 1991 -by- Lindsay Harris [lindsayh]
* Created for new DDI spec + spooler flushing problems.
*
*****************************************************************************/
BOOL
DrvEndDoc( pso, fl )
SURFOBJ *pso;
FLONG fl;
{
/*
* Grab hold of UNIDRV's pdev structure and flush the buffer!
*/
UD_PDEV *lpdv;
lpdv = ((PDEV *)(pso->dhpdev))->pUDPDev;
if( fl & ED_ABORTDOC )
vEndPage( (PDEV *)pso->dhpdev );
/* Send the END DOC type commands too! */
WriteChannel( lpdv, CMD_PC_ENDDOC );
FlushSpoolBuf( lpdv );
/* Clear the PF_DOCSTARTED flag. */
lpdv->fMode &= ~PF_DOCSTARTED;
vFreeDL( (PDEV *)pso->dhpdev );
return TRUE;
}