1/*
2* Copyright 2010, Haiku. All rights reserved.
3* Distributed under the terms of the MIT License.
4*
5* Authors:
6*		Michael Pfeiffer
7*/
8#include "GPJob.h"
9
10#include <Debug.h>
11
12
13// 72 DPI
14static const int32 kGutenprintUnit = 72;
15
16class CoordinateSystem
17{
18public:
19	CoordinateSystem()
20	:
21	fXDPI(0),
22	fYDPI(0)
23	{
24	}
25
26
27	void SetDPI(int32 x, int32 y) {
28		fXDPI = x;
29		fYDPI = y;
30	}
31
32
33	void ToGutenprint(int32 fromX, int32 fromY, double& toX, double& toY) {
34		toX = fromX * kGutenprintUnit / fXDPI;
35		toY = fromY * kGutenprintUnit / fYDPI;
36	}
37
38
39	void ToGutenprintCeiling(int32 fromX, int32 fromY, double& toX,
40		double& toY) {
41		toX = (fromX * kGutenprintUnit + fXDPI - 1) / fXDPI;
42		toY = (fromY * kGutenprintUnit + fYDPI - 1) / fYDPI;
43	}
44
45
46	void FromGutenprint(double fromX, double fromY, int32& toX, int32& toY) {
47		toX = (int32)(fromX * fXDPI / kGutenprintUnit);
48		toY = (int32)(fromY * fYDPI / kGutenprintUnit);
49	}
50
51	void FromGutenprintCeiling(double fromX, double fromY, int32& toX,
52		int32& toY) {
53		toX = (int32)((fromX * fXDPI + kGutenprintUnit - 1) / kGutenprintUnit);
54		toY = (int32)((fromY * fYDPI + kGutenprintUnit - 1) / kGutenprintUnit);
55	}
56
57	void SizeFromGutenprint(double fromWidth, double fromHeight,
58		int32& toWidth, int32& toHeight) {
59		toWidth = (int32)(fromWidth * fXDPI / kGutenprintUnit);
60		toHeight = (int32)(fromHeight * fYDPI / kGutenprintUnit);
61	}
62
63	void RoundUpToWholeInches(int32& width, int32& height) {
64		width = ((width + kGutenprintUnit - 1) / kGutenprintUnit)
65			* kGutenprintUnit;
66		height = ((height + kGutenprintUnit - 1) / kGutenprintUnit)
67			* kGutenprintUnit;
68	}
69
70private:
71	double fXDPI;
72	double fYDPI;
73};
74
75
76GPJob::GPJob()
77	:
78	fApplicationName(),
79	fOutputStream(NULL),
80	fConfiguration(NULL),
81	fHasPages(false),
82	fVariables(NULL),
83	fPrinter(NULL),
84	fBands(NULL),
85	fCachedBand(NULL),
86	fStatus(B_OK)
87{
88	fImage.init = ImageInit;
89	fImage.reset = ImageReset;
90	fImage.width = ImageWidth;
91	fImage.height = ImageHeight;
92	fImage.get_row = ImageGetRow;
93	fImage.get_appname = ImageGetAppname;
94	fImage.conclude = ImageConclude;
95	fImage.rep = this;
96}
97
98
99GPJob::~GPJob()
100{
101}
102
103
104void
105GPJob::SetApplicationName(const BString& applicationName)
106{
107	fApplicationName = applicationName;
108}
109
110
111void
112GPJob::SetConfiguration(GPJobConfiguration* configuration)
113{
114	fConfiguration = configuration;
115}
116
117
118void
119GPJob::SetOutputStream(OutputStream* outputStream)
120{
121	fOutputStream = outputStream;
122}
123
124
125status_t
126GPJob::Begin()
127{
128	fStatus = B_OK;
129
130	stp_init();
131
132	fPrinter = stp_get_printer_by_driver(fConfiguration->fDriver);
133	if (fPrinter == NULL) {
134		fprintf(stderr, "GPJob Begin: driver %s not found!\n",
135			fConfiguration->fDriver.String());
136		return B_ERROR;
137	}
138
139	fVariables = stp_vars_create();
140	if (fVariables == NULL) {
141		fprintf(stderr, "GPJob Begin: out of memory\n");
142		return B_NO_MEMORY;
143	}
144	stp_set_printer_defaults(fVariables, fPrinter);
145
146	stp_set_outfunc(fVariables, OutputFunction);
147	stp_set_errfunc(fVariables, ErrorFunction);
148	stp_set_outdata(fVariables, this);
149	stp_set_errdata(fVariables, this);
150
151	stp_set_string_parameter(fVariables, "PageSize",
152		fConfiguration->fPageSize);
153
154	if (fConfiguration->fResolution != "")
155		stp_set_string_parameter(fVariables, "Resolution",
156			fConfiguration->fResolution);
157
158	stp_set_string_parameter(fVariables, "InputSlot",
159		fConfiguration->fInputSlot);
160
161	stp_set_string_parameter(fVariables, "PrintingMode",
162		fConfiguration->fPrintingMode);
163
164	{
165		map<string, string>::iterator it = fConfiguration->fStringSettings.
166			begin();
167		for (; it != fConfiguration->fStringSettings.end(); it ++) {
168			stp_set_string_parameter(fVariables, it->first.c_str(),
169				it->second.c_str());
170		}
171	}
172
173	{
174		map<string, bool>::iterator it = fConfiguration->fBooleanSettings.
175			begin();
176		for (; it != fConfiguration->fBooleanSettings.end(); it ++) {
177			stp_set_boolean_parameter(fVariables, it->first.c_str(),
178				it->second);
179		}
180	}
181
182	{
183		map<string, int32>::iterator it = fConfiguration->fIntSettings.
184			begin();
185		for (; it != fConfiguration->fIntSettings.end(); it ++) {
186			stp_set_int_parameter(fVariables, it->first.c_str(),
187				it->second);
188		}
189	}
190
191	{
192		map<string, int32>::iterator it = fConfiguration->fDimensionSettings.
193			begin();
194		for (; it != fConfiguration->fDimensionSettings.end(); it ++) {
195			stp_set_dimension_parameter(fVariables, it->first.c_str(),
196				it->second);
197		}
198	}
199
200	{
201		map<string, double>::iterator it = fConfiguration->fDoubleSettings.
202			begin();
203		for (; it != fConfiguration->fDoubleSettings.end(); it ++) {
204			stp_set_float_parameter(fVariables, it->first.c_str(),
205				it->second);
206		}
207	}
208
209	stp_set_string_parameter(fVariables, "InputImageType",
210		"RGB");
211	stp_set_string_parameter(fVariables, "ChannelBitDepth",
212		"8");
213	stp_set_float_parameter(fVariables, "Density",
214		1.0f);
215	stp_set_string_parameter(fVariables, "JobMode", "Job");
216
217	stp_set_printer_defaults_soft(fVariables, fPrinter);
218
219	return B_OK;
220}
221
222
223void
224GPJob::End()
225{
226	if (fVariables == NULL)
227		return;
228
229	if (fHasPages)
230		stp_end_job(fVariables, &fImage);
231
232	stp_vars_destroy(fVariables);
233	fVariables = NULL;
234}
235
236status_t
237GPJob::PrintPage(list<GPBand*>& bands) {
238	if (fStatus != B_OK)
239		return fStatus;
240
241	fBands = &bands;
242	fCachedBand = NULL;
243
244	Rectangle<stp_dimension_t> imageableArea;
245	stp_get_imageable_area(fVariables, &imageableArea.left,
246		&imageableArea.right, &imageableArea.bottom, &imageableArea.top);
247	fprintf(stderr, "GPJob imageable area left %f, top %f, right %f, "
248		"bottom %f\n",
249		imageableArea.left, imageableArea.top, imageableArea.right,
250		imageableArea.bottom);
251	fprintf(stderr, "GPJob width %f %s, height %f %s\n",
252		imageableArea.Width(),
253		std::fmod(imageableArea.Width(), 72.) == 0.0 ? "whole inches" : "not whole inches",
254		imageableArea.Height(),
255		std::fmod(imageableArea.Height(), 72.) == 0.0 ? "whole inches" : "not whole inches"
256		);
257
258	CoordinateSystem coordinateSystem;
259	coordinateSystem.SetDPI(fConfiguration->fXDPI, fConfiguration->fYDPI);
260	{
261		// GPBand offset is relative to imageable area left, top
262		// but it has to be absolute to left, top of page
263		int32 offsetX;
264		int32 offsetY;
265		coordinateSystem.FromGutenprintCeiling(imageableArea.left,
266			imageableArea.top, offsetX, offsetY);
267
268		BPoint offset(offsetX, offsetY);
269		list<GPBand*>::iterator it = fBands->begin();
270		for (; it != fBands->end(); it++) {
271			(*it)->fWhere += offset;
272		}
273	}
274
275	fPrintRect = GetPrintRectangle(bands);
276
277	{
278		int left = (int)fPrintRect.left;
279		int top = (int)fPrintRect.top;
280		int width = fPrintRect.Width() + 1;
281		int height = fPrintRect.Height() + 1;
282
283		fprintf(stderr, "GPJob bitmap bands frame left %d, top %d, width %d, "
284			"height %d\n",
285			left, top, width, height);
286	}
287
288	// calculate the position and size of the image to be printed on the page
289	// unit: 1/72 Inches
290	// constraints: the image must be inside the imageable area
291	double left;
292	double top;
293	coordinateSystem.ToGutenprint(fPrintRect.left, fPrintRect.top, left, top);
294	if (left < imageableArea.left)
295		left = imageableArea.left;
296	if (top < imageableArea.top)
297		top = imageableArea.top;
298
299	double right;
300	double bottom;
301	coordinateSystem.ToGutenprintCeiling(fPrintRect.right, fPrintRect.bottom,
302		right, bottom);
303	if (right > imageableArea.right)
304		right = imageableArea.right;
305	if (bottom > imageableArea.bottom)
306		bottom = imageableArea.bottom;
307
308	double width = right - left;
309	double height = bottom - top;
310
311	// because of rounding and clipping in the previous step,
312	// now the image frame has to be synchronized
313	coordinateSystem.FromGutenprint(left, top, fPrintRect.left, fPrintRect.top);
314	int32 printRectWidth;
315	int32 printRectHeight;
316	coordinateSystem.SizeFromGutenprint(width, height, printRectWidth,
317		printRectHeight);
318	fPrintRect.right = fPrintRect.left + printRectWidth - 1;
319	fPrintRect.bottom = fPrintRect.top + printRectHeight - 1;
320	{
321		int left = fPrintRect.left;
322		int top = fPrintRect.top;
323		int width = fPrintRect.Width() + 1;
324		int height = fPrintRect.Height() + 1;
325
326		fprintf(stderr, "GPJob image dimensions left %d, top %d, width %d, "
327			"height %d\n",
328			left, top, width, height);
329	}
330
331	fprintf(stderr, "GPJob image dimensions in 1/72 Inches: "
332		"left %d, top %d, right %d, bottom %d\n",
333		(int)left, (int)top, (int)right, (int)bottom);
334
335	stp_set_width(fVariables, right - left);
336	stp_set_height(fVariables, bottom - top);
337	stp_set_left(fVariables, left);
338	stp_set_top(fVariables, top);
339
340	stp_merge_printvars(fVariables, stp_printer_get_defaults(fPrinter));
341
342	if (!stp_verify(fVariables)) {
343		fprintf(stderr, "GPJob PrintPage: invalid variables\n");
344		return B_ERROR;
345	}
346
347	if (!fHasPages) {
348		fHasPages = true;
349		stp_start_job(fVariables, &fImage);
350	}
351
352	stp_print(fVariables, &fImage);
353
354	return fStatus;
355}
356
357
358void
359GPJob::GetErrorMessage(BString& message)
360{
361	message = fErrorMessage;
362}
363
364
365RectInt32
366GPJob::GetPrintRectangle(list<GPBand*>& bands)
367{
368	list<GPBand*>::iterator it = bands.begin();
369	if (it == bands.end())
370		return BRect(0, 0, 0, 0);
371
372	GPBand* first = *it;
373	BRect rect = first->GetBoundingRectangle();
374	for (it ++; it != bands.end(); it ++) {
375		GPBand* band = *it;
376		rect = rect | band->GetBoundingRectangle();
377	}
378	return rect;
379}
380
381
382void
383GPJob::Init()
384{
385
386}
387
388
389void
390GPJob::Reset()
391{
392
393}
394
395
396int
397GPJob::Width()
398{
399	return fPrintRect.Width() + 1;
400}
401
402
403int
404GPJob::Height()
405{
406	return fPrintRect.Height() + 1;
407}
408
409
410stp_image_status_t
411GPJob::GetRow(unsigned char* data, size_t size, int row)
412{
413	if (fStatus != B_OK)
414		return STP_IMAGE_STATUS_ABORT;
415
416	// row is relative to left, top of image
417	// convert it to absolute y coordinate value
418	int line = fPrintRect.top + row;
419
420	FillWhite(data, size);
421
422	GPBand* band = FindBand(line);
423	if (band != NULL)
424		FillRow(band, data, size, line);
425
426	return STP_IMAGE_STATUS_OK;
427}
428
429
430GPBand*
431GPJob::FindBand(int line)
432{
433	if (fCachedBand != NULL && fCachedBand->ContainsLine(line))
434		return fCachedBand;
435
436	list<GPBand*>::iterator it = fBands->begin();
437	for (; it != fBands->end(); it ++) {
438		GPBand* band = *it;
439		if (band->ContainsLine(line)) {
440			fCachedBand = band;
441			return band;
442		}
443	}
444
445	fCachedBand = NULL;
446	return NULL;
447}
448
449
450void
451GPJob::FillRow(GPBand* band, unsigned char* data, size_t size, int line)
452{
453	int imageTop = line - static_cast<int>(band->fWhere.y -
454		band->fValidRect.top);
455	int imageLeft = static_cast<int>(band->fValidRect.left);
456
457	const int sourceBytesPerRow = band->fBitmap.BytesPerRow();
458	const int kSourceBytesPerPixel = 4; // BGRA
459	const unsigned char* source =
460		static_cast<unsigned char*>(band->fBitmap.Bits())
461		+ imageTop * sourceBytesPerRow
462		+ imageLeft * kSourceBytesPerPixel;
463
464	int dataLeft = static_cast<int>(band->fWhere.x - fPrintRect.left);
465	int sourcePixelsToSkip = 0;
466	if (dataLeft < 0) {
467		sourcePixelsToSkip = -dataLeft;
468		dataLeft = 0;
469	}
470	int width = band->fValidRect.IntegerWidth() + 1 - sourcePixelsToSkip;
471	source += sourcePixelsToSkip * kSourceBytesPerPixel;
472	if (width <= 0)
473		return;
474
475	const int kTargetBytesPerPixel = 3; // RGB
476	unsigned char* target = &data[dataLeft * kTargetBytesPerPixel];
477	int maxWidth = size / kTargetBytesPerPixel - dataLeft;
478	if (width > maxWidth)
479		width = maxWidth;
480
481	ASSERT(0 <= imageTop && imageTop <= band->fValidRect.IntegerHeight());
482	ASSERT((dataLeft + width) * kTargetBytesPerPixel <= size);
483
484	for (int i = 0; i < width; i ++) {
485		target[0] = source[2];
486		target[1] = source[1];
487		target[2] = source[0];
488		target += kTargetBytesPerPixel;
489		source += kSourceBytesPerPixel;
490	}
491}
492
493
494void
495GPJob::FillWhite(unsigned char* data, size_t size)
496{
497	for (size_t i = 0; i < size; i ++)
498		data[i] = 0xff;
499}
500
501
502const char*
503GPJob::GetAppname()
504{
505	return fApplicationName.String();
506}
507
508
509void
510GPJob::Conclude()
511{
512	// nothing to do
513}
514
515
516void
517GPJob::Write(const char* data, size_t size)
518{
519	try {
520		fOutputStream->Write(data, size);
521	} catch (TransportException& e) {
522		fStatus = B_IO_ERROR;
523	}
524}
525
526
527void
528GPJob::ReportError(const char* data, size_t size)
529{
530	if (fStatus == B_OK)
531		fStatus = B_ERROR;
532	fErrorMessage.Append(data, size);
533}
534
535
536void
537GPJob::ImageInit(stp_image_t* image)
538{
539	GPJob* job = static_cast<GPJob*>(image->rep);
540	job->Init();
541}
542
543
544void
545GPJob::ImageReset(stp_image_t* image)
546{
547	GPJob* job = static_cast<GPJob*>(image->rep);
548	job->Reset();
549}
550
551
552int
553GPJob::ImageWidth(stp_image_t* image)
554{
555	GPJob* job = static_cast<GPJob*>(image->rep);
556	return job->Width();
557}
558
559
560int
561GPJob::ImageHeight(stp_image_t *image)
562{
563	GPJob* job = static_cast<GPJob*>(image->rep);
564	return job->Height();
565}
566
567
568stp_image_status_t
569GPJob::ImageGetRow(stp_image_t* image, unsigned char* data, size_t size,
570	int row)
571{
572	GPJob* job = static_cast<GPJob*>(image->rep);
573	return job->GetRow(data, size, row);
574}
575
576
577const char*
578GPJob::ImageGetAppname(stp_image_t* image)
579{
580	GPJob* job = static_cast<GPJob*>(image->rep);
581	return job->GetAppname();
582}
583
584
585void
586GPJob::ImageConclude(stp_image_t *image)
587{
588	GPJob* job = static_cast<GPJob*>(image->rep);
589	job->Conclude();
590}
591
592
593void
594GPJob::OutputFunction(void *cookie, const char *data, size_t size)
595{
596	GPJob* job = static_cast<GPJob*>(cookie);
597	job->Write(data, size);
598}
599
600
601void
602GPJob::ErrorFunction(void *cookie, const char *data, size_t size)
603{
604	GPJob* job = static_cast<GPJob*>(cookie);
605	job->ReportError(data, size);
606}
607
608