/*
 * PCL5.cpp
 * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
 * Copyright 2003 Michael Pfeiffer.
 */
 
 
#include "PCL5.h"
 
#include <memory>
 
#include <Alert.h>
#include <Bitmap.h>
#include <File.h>
 
#include "DbgMsg.h"
#include "Halftone.h"
#include "JobData.h"
#include "PackBits.h"
#include "PCL5Cap.h"
#include "PrinterData.h"
#include "UIDriver.h"
#include "ValidRect.h"
 
 
PCL5Driver::PCL5Driver(BMessage* message, PrinterData* printerData,
	const PrinterCap* printerCap)
	:
	GraphicsDriver(message, printerData, printerCap),
	fCompressionMethod(0),
	fHalftone(NULL)
{
}
 
 
bool
PCL5Driver::StartDocument()
{
	try {
		_JobStart();
		fHalftone = new Halftone(GetJobData()->GetSurfaceType(),
			GetJobData()->GetGamma(), GetJobData()->GetInkDensity(),
			GetJobData()->GetDitherType());
		return true;
	}
	catch (TransportException& err) {
		return false;
	} 
}
 
 
bool
PCL5Driver::StartPage(int)
{
	return true;
}
 
 
bool
PCL5Driver::EndPage(int)
{
	try {
		WriteSpoolChar('\014');
		return true;
	}
	catch (TransportException& err) {
		return false;
	} 
}
 
 
bool
PCL5Driver::EndDocument(bool)
{
	try {
		if (fHalftone != NULL) {
			delete fHalftone;
			fHalftone = NULL;
		}
		_JobEnd();
		return true;
	}
	catch (TransportException& err) {
		return false;
	} 
}
 
 
bool
PCL5Driver::NextBand(BBitmap* bitmap, BPoint* offset)
{
	DBGMSG(("> nextBand\n"));
 
	try {
		BRect bounds = bitmap->Bounds();
 
		RECT rc;
		rc.left = (int)bounds.left;
		rc.top = (int)bounds.top;
		rc.right = (int)bounds.right;
		rc.bottom = (int)bounds.bottom;
 
		int height = rc.bottom - rc.top + 1;
 
		int x = (int)offset->x;
		int y = (int)offset->y;
 
		int pageHeight = GetPageHeight();
 
		if (y + height > pageHeight)
			height = pageHeight - y;
 
		rc.bottom = height - 1;
 
		DBGMSG(("height = %d\n", height));
		DBGMSG(("x = %d\n", x));
		DBGMSG(("y = %d\n", y));
 
		if (get_valid_rect(bitmap, &rc)) {
 
			DBGMSG(("validate rect = %d, %d, %d, %d\n",
				rc.left, rc.top, rc.right, rc.bottom));
 
			x = rc.left;
			y += rc.top;
 
			int width = rc.right - rc.left + 1;
			int widthByte = (width + 7) / 8;
				// byte boundary
			int height = rc.bottom - rc.top + 1;
			int in_size = widthByte;
			int out_size = (widthByte * 6 + 4) / 5;
			int delta = bitmap->BytesPerRow();
 
			DBGMSG(("width = %d\n", width));
			DBGMSG(("widthByte = %d\n", widthByte));
			DBGMSG(("height = %d\n", height));
			DBGMSG(("in_size = %d\n", in_size));
			DBGMSG(("out_size = %d\n", out_size));
			DBGMSG(("delta = %d\n", delta));
			DBGMSG(("renderobj->Get_pixel_depth() = %d\n", fHalftone->GetPixelDepth()));
 
			uchar* ptr = static_cast<uchar*>(bitmap->Bits())
						+ rc.top * delta
						+ (rc.left * fHalftone->GetPixelDepth()) / 8;
 
			int compressionMethod;
			int compressedSize;
			const uchar* buffer;
 
			uchar* in_buffer  = new uchar[in_size];
			uchar* out_buffer = new uchar[out_size];
 
			auto_ptr<uchar> _in_buffer (in_buffer);
			auto_ptr<uchar> _out_buffer(out_buffer);
 
			DBGMSG(("move\n"));
 
			_Move(x, y);
			_StartRasterGraphics(width, height);
 
			const bool color = GetJobData()->GetColor() == JobData::kColor;
			const int num_planes = color ? 3 : 1;
			
			if (color) {
				fHalftone->SetPlanes(Halftone::kPlaneRGB1);
				fHalftone->SetBlackValue(Halftone::kLowValueMeansBlack);
			}
			
			for (int i = rc.top; i <= rc.bottom; i++) {
			
				for (int plane = 0; plane < num_planes; plane ++) {
										
					fHalftone->Dither(in_buffer, ptr, x, y, width);
							
					compressedSize = pack_bits(out_buffer, in_buffer, in_size);
					
					if (compressedSize + _BytesToEnterCompressionMethod(2)
						< in_size + _BytesToEnterCompressionMethod(0)) {
						compressionMethod = 2; // back bits
						buffer = out_buffer;
					} else {
						compressionMethod = 0; // uncompressed
						buffer = in_buffer;
						compressedSize = in_size;
					}
		
					_RasterGraphics(
						compressionMethod,
						buffer,
						compressedSize,
						plane == num_planes - 1);
				
				}
 
				ptr  += delta;
				y++;
			}
 
			_EndRasterGraphics();
 
		} else
			DBGMSG(("band bitmap is clean.\n"));
 
		if (y >= pageHeight) {
			offset->x = -1.0;
			offset->y = -1.0;
		} else
			offset->y += height;
 
		DBGMSG(("< nextBand\n"));
		return true;
	}
	catch (TransportException& err) {
		BAlert* alert = new BAlert("", err.What(), "OK");
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
		return false;
	} 
}
 
 
void
PCL5Driver::_JobStart()
{
	const bool color = GetJobData()->GetColor() == JobData::kColor;
	// enter PCL5
	WriteSpoolString("\033%%-12345X@PJL ENTER LANGUAGE=PCL\n");
	// reset
	WriteSpoolString("\033E");
	// dpi
	WriteSpoolString("\033*t%dR", GetJobData()->GetXres());
	// unit of measure
	WriteSpoolString("\033&u%dD", GetJobData()->GetXres());
	// page size
	WriteSpoolString("\033&l0A");
	// page orientation
	WriteSpoolString("\033&l0O");
	if (color) {
		// 3 color planes (red, green, blue)
		WriteSpoolString("\033*r3U");
	}
	// raster presentation
	WriteSpoolString("\033*r0F");
	// top maring and perforation skip
	WriteSpoolString("\033&l0e0L");
	// clear horizontal margins
	WriteSpoolString("\0339");
	// number of copies
	// WriteSpoolString("\033&l%ldL", GetJobData()->GetCopies());
}
 
 
void
PCL5Driver::_StartRasterGraphics(int width, int height)
{
	// width
	WriteSpoolString("\033*r%dS", width);
	// height
	WriteSpoolString("\033*r%dT", height);
	// start raster graphics
	WriteSpoolString("\033*r1A");
	fCompressionMethod = -1;
}
 
 
void
PCL5Driver::_EndRasterGraphics()
{
	WriteSpoolString("\033*rB");
}
 
 
void
PCL5Driver::_RasterGraphics(int compressionMethod, const uchar* buffer,
	int size, bool lastPlane)
{
	if (fCompressionMethod != compressionMethod) {
		WriteSpoolString("\033*b%dM", compressionMethod);
		fCompressionMethod = compressionMethod;
	}
	WriteSpoolString("\033*b%d", size);
	if (lastPlane)
		WriteSpoolString("W");
	else
		WriteSpoolString("V");
 
	WriteSpoolData(buffer, size);
}
 
 
void
PCL5Driver::_JobEnd()
{
	WriteSpoolString("\033&l1T");
	WriteSpoolString("\033E");
}
 
 
void
PCL5Driver::_Move(int x, int y)
{
	WriteSpoolString("\033*p%dx%dY", x, y + 75);
}
 
 
int
PCL5Driver::_BytesToEnterCompressionMethod(int compressionMethod)
{
	if (fCompressionMethod == compressionMethod)
		return 0;
	else
		return 5;
}

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.