root/src/add-ons/print/drivers/pcl5/PCL5.cpp
/*
 * PCL5.cpp
 * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
 * Copyright 2003 Michael Pfeiffer.
 */


#include "PCL5.h"

#include <vector>

#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;

                        std::vector<uchar> in_buffer(in_size);
                        std::vector<uchar> out_buffer(out_size);

                        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[0], ptr, x, y, width);

                                        compressedSize = pack_bits(&out_buffer[0], &in_buffer[0], in_size);

                                        if (compressedSize + _BytesToEnterCompressionMethod(2)
                                                < in_size + _BytesToEnterCompressionMethod(0)) {
                                                compressionMethod = 2; // back bits
                                                buffer = &out_buffer[0];
                                        } else {
                                                compressionMethod = 0; // uncompressed
                                                buffer = &in_buffer[0];
                                                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;
}