root/src/add-ons/print/drivers/postscript/PS.cpp
/*
 * PS.cpp
 * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
 * Copyright 2003 Michael Pfeiffer.
 * Copyright 2010 Ithamar Adema.
 */


#include "PS.h"

#include <vector>
#include <stdio.h>
#include <stdlib.h>

#include <Alert.h>
#include <Bitmap.h>
#include <File.h>
#include <Path.h>

#include "DbgMsg.h"
#include "FilterIO.h"
#include "Halftone.h"
#include "JobData.h"
#include "PackBits.h"
#include "PPDParser.h"
#include "PrinterData.h"
#include "PSCap.h"
#include "PSData.h"
#include "UIDriver.h"
#include "ValidRect.h"


PSDriver::PSDriver(BMessage* message, PrinterData* printerData,
        const PrinterCap* printerCap)
        :
        GraphicsDriver(message, printerData, printerCap),
        fPrintedPages(0),
        fCompressionMethod(0),
        fHalftone(NULL),
        fFilterIO(NULL)
{
}


void
PSDriver::_StartFilterIfNeeded()
{
        const PSData* data = dynamic_cast<const PSData*>(GetPrinterData());
        PPDParser parser(BPath(data->fPPD.String()));
        if (parser.InitCheck() == B_OK) {
                BString param = parser.GetParameter("FoomaticRIPCommandLine");
                char str[3] = "%?";
                // for now, we don't have any extra substitutions to do...
                // (will be added once we support configuration options from the PPD)
                for (str[1] = 'A'; str[1] <= 'Z'; str[1]++)
                        param.ReplaceAll(str, "");

                if (param.Length())
                        fFilterIO = new FilterIO(param);

                if (!fFilterIO || fFilterIO->InitCheck() != B_OK) {
                        delete fFilterIO;
                        fFilterIO = NULL;
                }
        }
}


void
PSDriver::_FlushFilterIfNeeded()
{
        if (fFilterIO) {
                char buffer[1024];
                ssize_t len;

                while ((len = fFilterIO->Read(buffer, sizeof(buffer))) > 0)
                        WriteSpoolData(buffer, len);
        }
}


void
PSDriver::_WritePSString(const char* format, ...)
{
        char str[256];
        va_list ap;
        va_start(ap, format);
        vsprintf(str, format, ap);

        if (fFilterIO)
                fFilterIO->Write(str, strlen(str));
        else
                WriteSpoolData(str, strlen(str));

        va_end(ap);
}


void
PSDriver::_WritePSData(const void* data, size_t size)
{
        if (fFilterIO)
                fFilterIO->Write(data, size);
        else
                WriteSpoolData(data, size);
}


bool
PSDriver::StartDocument()
{
        try {
                _StartFilterIfNeeded();

                _JobStart();
                fHalftone = new Halftone(GetJobData()->GetSurfaceType(),
                        GetJobData()->GetGamma(), GetJobData()->GetInkDensity(),
                        GetJobData()->GetDitherType());
                return true;
        }
        catch (TransportException& err) {
                return false;
        }
}


bool
PSDriver::StartPage(int page)
{
        page ++;
        _WritePSString("%%%%Page: %d %d\n", page, page);
        _WritePSString("gsave\n");
        _SetupCTM();
        return true;
}


bool
PSDriver::EndPage(int)
{
        try {
                fPrintedPages ++;
                _WritePSString("grestore\n");
                _WritePSString("showpage\n");

                _FlushFilterIfNeeded();

                return true;
        }
        catch (TransportException& err) {
                return false;
        }
}


void
PSDriver::_SetupCTM()
{
        const float leftMargin = GetJobData()->GetPrintableRect().left;
        const float topMargin = GetJobData()->GetPrintableRect().top;
        if (GetJobData()->GetOrientation() == JobData::kPortrait) {
                // move origin from bottom left to top left
                // and set margin
                _WritePSString("%f %f translate\n", leftMargin,
                        GetJobData()->GetPaperRect().Height()-topMargin);
        } else {
                // landscape:
                // move origin from bottom left to margin top and left
                // and rotate page contents
                _WritePSString("%f %f translate\n", topMargin, leftMargin);
                _WritePSString("90 rotate\n");
        }
        // y values increase from top to bottom
        // units of measure is dpi
        _WritePSString("72 %d div 72 -%d div scale\n", GetJobData()->GetXres(),
                GetJobData()->GetYres());
}


bool
PSDriver::EndDocument(bool)
{
        try {
                if (fHalftone) {
                        delete fHalftone;
                        fHalftone = NULL;
                }
                _JobEnd();
                return true;
        }
        catch (TransportException& err) {
                return false;
        }
}


static inline uchar
ToHexDigit(uchar value)
{
        if (value <= 9) return '0' + value;
        else return 'a' + (value - 10);
}


bool
PSDriver::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 page_height = GetPageHeight();

                if (y + height > page_height) {
                        height = page_height - 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;

                        bool color = GetJobData()->GetColor() == JobData::kColor;
                        int width = rc.right - rc.left + 1;
                        int widthByte = (width + 7) / 8;
                                // byte boundary
                        int height = rc.bottom - rc.top + 1;
                        int in_size = color ? width : widthByte;
                        int out_size = color ? width * 6: widthByte * 2;
                        int delta = bitmap->BytesPerRow();

                        DBGMSG(("width = %d\n", width));
                        DBGMSG(("widthByte = %d\n", widthByte));
                        DBGMSG(("height = %d\n", height));
                        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 compression_method;
                        int compressed_size;
                        const uchar* buffer;

                        std::vector<uchar> in_buffer(in_size);
                                // gray values
                        std::vector<uchar> out_buffer(out_size);
                                // gray values in hexadecimal

                        DBGMSG(("move\n"));

                        int size = color ? width * 3 : in_size;
                        _StartRasterGraphics(x, y, width, height, size);

                        for (int i = rc.top; i <= rc.bottom; i++) {
                                if (color) {
                                        uchar* out = &out_buffer[0];
                                        uchar* in  = ptr;
                                        for (int w = width; w > 0; w --) {
                                                *out++ = ToHexDigit((in[2]) >> 4);
                                                *out++ = ToHexDigit((in[2]) & 15);
                                                *out++ = ToHexDigit((in[1]) >> 4);
                                                *out++ = ToHexDigit((in[1]) & 15);
                                                *out++ = ToHexDigit((in[0]) >> 4);
                                                *out++ = ToHexDigit((in[0]) & 15);
                                                in += 4;
                                        }
                                } else {
                                        fHalftone->Dither(&in_buffer[0], ptr, x, y, width);

                                        uchar* in = &in_buffer[0];
                                        uchar* out = &out_buffer[0];

                                        for (int w = in_size; w > 0; w --, in ++) {
                                                *in = ~*in; // invert pixels
                                                *out++ = ToHexDigit((*in) >> 4);
                                                *out++ = ToHexDigit((*in) & 15);
                                        }
                                }

                                {
                                        compression_method = 0; // uncompressed
                                        buffer = &out_buffer[0];
                                        compressed_size = out_size;
                                }

                                _RasterGraphics(
                                        compression_method,
                                        buffer,
                                        compressed_size);

                                ptr  += delta;
                                y++;
                        }

                        _EndRasterGraphics();

                } else
                        DBGMSG(("band bitmap is clean.\n"));

                if (y >= page_height) {
                        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
PSDriver::_JobStart()
{
        // PostScript header
        _WritePSString("%%!PS-Adobe-3.0\n");
        _WritePSString("%%%%LanguageLevel: 1\n");
        _WritePSString("%%%%Title: %s\n",
                GetSpoolMetaData()->GetDescription().c_str());
        _WritePSString("%%%%Creator: %s\n",
                GetSpoolMetaData()->GetMimeType().c_str());
        _WritePSString("%%%%CreationDate: %s",
                GetSpoolMetaData()->GetCreationTime().c_str());
        _WritePSString("%%%%DocumentMedia: Plain %d %d white 0 ( )\n",
                GetJobData()->GetPaperRect().IntegerWidth(),
                GetJobData()->GetPaperRect().IntegerHeight());
        _WritePSString("%%%%Pages: (atend)\n");
        _WritePSString("%%%%EndComments\n");

        _WritePSString("%%%%BeginDefaults\n");
        _WritePSString("%%%%PageMedia: Plain\n");
        _WritePSString("%%%%EndDefaults\n");
}


void
PSDriver::_StartRasterGraphics(int x, int y, int width, int height,
        int widthByte)
{
        bool color = GetJobData()->GetColor() == JobData::kColor;
        fCompressionMethod = -1;
        _WritePSString("gsave\n");
        _WritePSString("/s %d string def\n", widthByte);
        _WritePSString("%d %d translate\n", x, y);
        _WritePSString("%d %d scale\n", width, height);
        if (color)
                _WritePSString("%d %d 8\n", width, height); // 8 bpp
        else
                _WritePSString("%d %d 1\n", width, height); // 1 bpp
        _WritePSString("[%d 0 0 %d 0 0]\n", width, height);
        _WritePSString("{ currentfile s readhexstring pop }\n");
        if (color) {
                _WritePSString("false 3\n"); // single data source, 3 color components
                _WritePSString("colorimage\n");
        } else
                _WritePSString("image\n\n");
}


void
PSDriver::_EndRasterGraphics()
{
        _WritePSString("grestore\n");
}


void
PSDriver::_RasterGraphics(int compression_method, const uchar* buffer,
        int size)
{
        if (fCompressionMethod != compression_method) {
                fCompressionMethod = compression_method;
        }
        _WritePSData(buffer, size);
        _WritePSString("\n");
}


void
PSDriver::_JobEnd()
{
        _WritePSString("%%%%Pages: %d\n", fPrintedPages);
        _WritePSString("%%%%EOF\n");

        _FlushFilterIfNeeded();
}