/* * Copyright 2010, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Michael Pfeiffer */ #include "GPJob.h" #include // 72 DPI static const int32 kGutenprintUnit = 72; class CoordinateSystem { public: CoordinateSystem() : fXDPI(0), fYDPI(0) { } void SetDPI(int32 x, int32 y) { fXDPI = x; fYDPI = y; } void ToGutenprint(int32 fromX, int32 fromY, double& toX, double& toY) { toX = fromX * kGutenprintUnit / fXDPI; toY = fromY * kGutenprintUnit / fYDPI; } void ToGutenprintCeiling(int32 fromX, int32 fromY, double& toX, double& toY) { toX = (fromX * kGutenprintUnit + fXDPI - 1) / fXDPI; toY = (fromY * kGutenprintUnit + fYDPI - 1) / fYDPI; } void FromGutenprint(double fromX, double fromY, int32& toX, int32& toY) { toX = (int32)(fromX * fXDPI / kGutenprintUnit); toY = (int32)(fromY * fYDPI / kGutenprintUnit); } void FromGutenprintCeiling(double fromX, double fromY, int32& toX, int32& toY) { toX = (int32)((fromX * fXDPI + kGutenprintUnit - 1) / kGutenprintUnit); toY = (int32)((fromY * fYDPI + kGutenprintUnit - 1) / kGutenprintUnit); } void SizeFromGutenprint(double fromWidth, double fromHeight, int32& toWidth, int32& toHeight) { toWidth = (int32)(fromWidth * fXDPI / kGutenprintUnit); toHeight = (int32)(fromHeight * fYDPI / kGutenprintUnit); } void RoundUpToWholeInches(int32& width, int32& height) { width = ((width + kGutenprintUnit - 1) / kGutenprintUnit) * kGutenprintUnit; height = ((height + kGutenprintUnit - 1) / kGutenprintUnit) * kGutenprintUnit; } private: double fXDPI; double fYDPI; }; GPJob::GPJob() : fApplicationName(), fOutputStream(NULL), fConfiguration(NULL), fHasPages(false), fVariables(NULL), fPrinter(NULL), fBands(NULL), fCachedBand(NULL), fStatus(B_OK) { fImage.init = ImageInit; fImage.reset = ImageReset; fImage.width = ImageWidth; fImage.height = ImageHeight; fImage.get_row = ImageGetRow; fImage.get_appname = ImageGetAppname; fImage.conclude = ImageConclude; fImage.rep = this; } GPJob::~GPJob() { } void GPJob::SetApplicationName(const BString& applicationName) { fApplicationName = applicationName; } void GPJob::SetConfiguration(GPJobConfiguration* configuration) { fConfiguration = configuration; } void GPJob::SetOutputStream(OutputStream* outputStream) { fOutputStream = outputStream; } status_t GPJob::Begin() { fStatus = B_OK; stp_init(); fPrinter = stp_get_printer_by_driver(fConfiguration->fDriver); if (fPrinter == NULL) { fprintf(stderr, "GPJob Begin: driver %s not found!\n", fConfiguration->fDriver.String()); return B_ERROR; } fVariables = stp_vars_create(); if (fVariables == NULL) { fprintf(stderr, "GPJob Begin: out of memory\n"); return B_NO_MEMORY; } stp_set_printer_defaults(fVariables, fPrinter); stp_set_outfunc(fVariables, OutputFunction); stp_set_errfunc(fVariables, ErrorFunction); stp_set_outdata(fVariables, this); stp_set_errdata(fVariables, this); stp_set_string_parameter(fVariables, "PageSize", fConfiguration->fPageSize); if (fConfiguration->fResolution != "") stp_set_string_parameter(fVariables, "Resolution", fConfiguration->fResolution); stp_set_string_parameter(fVariables, "InputSlot", fConfiguration->fInputSlot); stp_set_string_parameter(fVariables, "PrintingMode", fConfiguration->fPrintingMode); { map::iterator it = fConfiguration->fStringSettings. begin(); for (; it != fConfiguration->fStringSettings.end(); it ++) { stp_set_string_parameter(fVariables, it->first.c_str(), it->second.c_str()); } } { map::iterator it = fConfiguration->fBooleanSettings. begin(); for (; it != fConfiguration->fBooleanSettings.end(); it ++) { stp_set_boolean_parameter(fVariables, it->first.c_str(), it->second); } } { map::iterator it = fConfiguration->fIntSettings. begin(); for (; it != fConfiguration->fIntSettings.end(); it ++) { stp_set_int_parameter(fVariables, it->first.c_str(), it->second); } } { map::iterator it = fConfiguration->fDimensionSettings. begin(); for (; it != fConfiguration->fDimensionSettings.end(); it ++) { stp_set_dimension_parameter(fVariables, it->first.c_str(), it->second); } } { map::iterator it = fConfiguration->fDoubleSettings. begin(); for (; it != fConfiguration->fDoubleSettings.end(); it ++) { stp_set_float_parameter(fVariables, it->first.c_str(), it->second); } } stp_set_string_parameter(fVariables, "InputImageType", "RGB"); stp_set_string_parameter(fVariables, "ChannelBitDepth", "8"); stp_set_float_parameter(fVariables, "Density", 1.0f); stp_set_string_parameter(fVariables, "JobMode", "Job"); stp_set_printer_defaults_soft(fVariables, fPrinter); return B_OK; } void GPJob::End() { if (fVariables == NULL) return; if (fHasPages) stp_end_job(fVariables, &fImage); stp_vars_destroy(fVariables); fVariables = NULL; } status_t GPJob::PrintPage(list& bands) { if (fStatus != B_OK) return fStatus; fBands = &bands; fCachedBand = NULL; Rectangle imageableArea; stp_get_imageable_area(fVariables, &imageableArea.left, &imageableArea.right, &imageableArea.bottom, &imageableArea.top); fprintf(stderr, "GPJob imageable area left %f, top %f, right %f, " "bottom %f\n", imageableArea.left, imageableArea.top, imageableArea.right, imageableArea.bottom); fprintf(stderr, "GPJob width %f %s, height %f %s\n", imageableArea.Width(), std::fmod(imageableArea.Width(), 72.) == 0.0 ? "whole inches" : "not whole inches", imageableArea.Height(), std::fmod(imageableArea.Height(), 72.) == 0.0 ? "whole inches" : "not whole inches" ); CoordinateSystem coordinateSystem; coordinateSystem.SetDPI(fConfiguration->fXDPI, fConfiguration->fYDPI); { // GPBand offset is relative to imageable area left, top // but it has to be absolute to left, top of page int32 offsetX; int32 offsetY; coordinateSystem.FromGutenprintCeiling(imageableArea.left, imageableArea.top, offsetX, offsetY); BPoint offset(offsetX, offsetY); list::iterator it = fBands->begin(); for (; it != fBands->end(); it++) { (*it)->fWhere += offset; } } fPrintRect = GetPrintRectangle(bands); { int left = (int)fPrintRect.left; int top = (int)fPrintRect.top; int width = fPrintRect.Width() + 1; int height = fPrintRect.Height() + 1; fprintf(stderr, "GPJob bitmap bands frame left %d, top %d, width %d, " "height %d\n", left, top, width, height); } // calculate the position and size of the image to be printed on the page // unit: 1/72 Inches // constraints: the image must be inside the imageable area double left; double top; coordinateSystem.ToGutenprint(fPrintRect.left, fPrintRect.top, left, top); if (left < imageableArea.left) left = imageableArea.left; if (top < imageableArea.top) top = imageableArea.top; double right; double bottom; coordinateSystem.ToGutenprintCeiling(fPrintRect.right, fPrintRect.bottom, right, bottom); if (right > imageableArea.right) right = imageableArea.right; if (bottom > imageableArea.bottom) bottom = imageableArea.bottom; double width = right - left; double height = bottom - top; // because of rounding and clipping in the previous step, // now the image frame has to be synchronized coordinateSystem.FromGutenprint(left, top, fPrintRect.left, fPrintRect.top); int32 printRectWidth; int32 printRectHeight; coordinateSystem.SizeFromGutenprint(width, height, printRectWidth, printRectHeight); fPrintRect.right = fPrintRect.left + printRectWidth - 1; fPrintRect.bottom = fPrintRect.top + printRectHeight - 1; { int left = fPrintRect.left; int top = fPrintRect.top; int width = fPrintRect.Width() + 1; int height = fPrintRect.Height() + 1; fprintf(stderr, "GPJob image dimensions left %d, top %d, width %d, " "height %d\n", left, top, width, height); } fprintf(stderr, "GPJob image dimensions in 1/72 Inches: " "left %d, top %d, right %d, bottom %d\n", (int)left, (int)top, (int)right, (int)bottom); stp_set_width(fVariables, right - left); stp_set_height(fVariables, bottom - top); stp_set_left(fVariables, left); stp_set_top(fVariables, top); stp_merge_printvars(fVariables, stp_printer_get_defaults(fPrinter)); if (!stp_verify(fVariables)) { fprintf(stderr, "GPJob PrintPage: invalid variables\n"); return B_ERROR; } if (!fHasPages) { fHasPages = true; stp_start_job(fVariables, &fImage); } stp_print(fVariables, &fImage); return fStatus; } void GPJob::GetErrorMessage(BString& message) { message = fErrorMessage; } RectInt32 GPJob::GetPrintRectangle(list& bands) { list::iterator it = bands.begin(); if (it == bands.end()) return BRect(0, 0, 0, 0); GPBand* first = *it; BRect rect = first->GetBoundingRectangle(); for (it ++; it != bands.end(); it ++) { GPBand* band = *it; rect = rect | band->GetBoundingRectangle(); } return rect; } void GPJob::Init() { } void GPJob::Reset() { } int GPJob::Width() { return fPrintRect.Width() + 1; } int GPJob::Height() { return fPrintRect.Height() + 1; } stp_image_status_t GPJob::GetRow(unsigned char* data, size_t size, int row) { if (fStatus != B_OK) return STP_IMAGE_STATUS_ABORT; // row is relative to left, top of image // convert it to absolute y coordinate value int line = fPrintRect.top + row; FillWhite(data, size); GPBand* band = FindBand(line); if (band != NULL) FillRow(band, data, size, line); return STP_IMAGE_STATUS_OK; } GPBand* GPJob::FindBand(int line) { if (fCachedBand != NULL && fCachedBand->ContainsLine(line)) return fCachedBand; list::iterator it = fBands->begin(); for (; it != fBands->end(); it ++) { GPBand* band = *it; if (band->ContainsLine(line)) { fCachedBand = band; return band; } } fCachedBand = NULL; return NULL; } void GPJob::FillRow(GPBand* band, unsigned char* data, size_t size, int line) { int imageTop = line - static_cast(band->fWhere.y - band->fValidRect.top); int imageLeft = static_cast(band->fValidRect.left); const int sourceBytesPerRow = band->fBitmap.BytesPerRow(); const int kSourceBytesPerPixel = 4; // BGRA const unsigned char* source = static_cast(band->fBitmap.Bits()) + imageTop * sourceBytesPerRow + imageLeft * kSourceBytesPerPixel; int dataLeft = static_cast(band->fWhere.x - fPrintRect.left); int sourcePixelsToSkip = 0; if (dataLeft < 0) { sourcePixelsToSkip = -dataLeft; dataLeft = 0; } int width = band->fValidRect.IntegerWidth() + 1 - sourcePixelsToSkip; source += sourcePixelsToSkip * kSourceBytesPerPixel; if (width <= 0) return; const int kTargetBytesPerPixel = 3; // RGB unsigned char* target = &data[dataLeft * kTargetBytesPerPixel]; int maxWidth = size / kTargetBytesPerPixel - dataLeft; if (width > maxWidth) width = maxWidth; ASSERT(0 <= imageTop && imageTop <= band->fValidRect.IntegerHeight()); ASSERT((dataLeft + width) * kTargetBytesPerPixel <= size); for (int i = 0; i < width; i ++) { target[0] = source[2]; target[1] = source[1]; target[2] = source[0]; target += kTargetBytesPerPixel; source += kSourceBytesPerPixel; } } void GPJob::FillWhite(unsigned char* data, size_t size) { for (size_t i = 0; i < size; i ++) data[i] = 0xff; } const char* GPJob::GetAppname() { return fApplicationName.String(); } void GPJob::Conclude() { // nothing to do } void GPJob::Write(const char* data, size_t size) { try { fOutputStream->Write(data, size); } catch (TransportException& e) { fStatus = B_IO_ERROR; } } void GPJob::ReportError(const char* data, size_t size) { if (fStatus == B_OK) fStatus = B_ERROR; fErrorMessage.Append(data, size); } void GPJob::ImageInit(stp_image_t* image) { GPJob* job = static_cast(image->rep); job->Init(); } void GPJob::ImageReset(stp_image_t* image) { GPJob* job = static_cast(image->rep); job->Reset(); } int GPJob::ImageWidth(stp_image_t* image) { GPJob* job = static_cast(image->rep); return job->Width(); } int GPJob::ImageHeight(stp_image_t *image) { GPJob* job = static_cast(image->rep); return job->Height(); } stp_image_status_t GPJob::ImageGetRow(stp_image_t* image, unsigned char* data, size_t size, int row) { GPJob* job = static_cast(image->rep); return job->GetRow(data, size, row); } const char* GPJob::ImageGetAppname(stp_image_t* image) { GPJob* job = static_cast(image->rep); return job->GetAppname(); } void GPJob::ImageConclude(stp_image_t *image) { GPJob* job = static_cast(image->rep); job->Conclude(); } void GPJob::OutputFunction(void *cookie, const char *data, size_t size) { GPJob* job = static_cast(cookie); job->Write(data, size); } void GPJob::ErrorFunction(void *cookie, const char *data, size_t size) { GPJob* job = static_cast(cookie); job->ReportError(data, size); }