1/*
2 * PS.cpp
3 * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
4 * Copyright 2003 Michael Pfeiffer.
5 * Copyright 2010 Ithamar Adema.
6 */
7
8
9#include "PS.h"
10
11#include <vector>
12#include <stdio.h>
13#include <stdlib.h>
14
15#include <Alert.h>
16#include <Bitmap.h>
17#include <File.h>
18#include <Path.h>
19
20#include "DbgMsg.h"
21#include "FilterIO.h"
22#include "Halftone.h"
23#include "JobData.h"
24#include "PackBits.h"
25#include "PPDParser.h"
26#include "PrinterData.h"
27#include "PSCap.h"
28#include "PSData.h"
29#include "UIDriver.h"
30#include "ValidRect.h"
31
32
33PSDriver::PSDriver(BMessage* message, PrinterData* printerData,
34	const PrinterCap* printerCap)
35	:
36	GraphicsDriver(message, printerData, printerCap),
37	fPrintedPages(0),
38	fCompressionMethod(0),
39	fHalftone(NULL),
40	fFilterIO(NULL)
41{
42}
43
44
45void
46PSDriver::_StartFilterIfNeeded()
47{
48	const PSData* data = dynamic_cast<const PSData*>(GetPrinterData());
49	PPDParser parser(BPath(data->fPPD.String()));
50	if (parser.InitCheck() == B_OK) {
51		BString param = parser.GetParameter("FoomaticRIPCommandLine");
52		char str[3] = "%?";
53		// for now, we don't have any extra substitutions to do...
54		// (will be added once we support configuration options from the PPD)
55		for (str[1] = 'A'; str[1] <= 'Z'; str[1]++)
56			param.ReplaceAll(str, "");
57
58		if (param.Length())
59			fFilterIO = new FilterIO(param);
60
61		if (!fFilterIO || fFilterIO->InitCheck() != B_OK) {
62			delete fFilterIO;
63			fFilterIO = NULL;
64		}
65	}
66}
67
68
69void
70PSDriver::_FlushFilterIfNeeded()
71{
72	if (fFilterIO) {
73		char buffer[1024];
74		ssize_t len;
75
76		while ((len = fFilterIO->Read(buffer, sizeof(buffer))) > 0)
77			WriteSpoolData(buffer, len);
78	}
79}
80
81
82void
83PSDriver::_WritePSString(const char* format, ...)
84{
85	char str[256];
86	va_list	ap;
87	va_start(ap, format);
88	vsprintf(str, format, ap);
89
90	if (fFilterIO)
91		fFilterIO->Write(str, strlen(str));
92	else
93		WriteSpoolData(str, strlen(str));
94
95	va_end(ap);
96}
97
98
99void
100PSDriver::_WritePSData(const void* data, size_t size)
101{
102	if (fFilterIO)
103		fFilterIO->Write(data, size);
104	else
105		WriteSpoolData(data, size);
106}
107
108
109bool
110PSDriver::StartDocument()
111{
112	try {
113		_StartFilterIfNeeded();
114
115		_JobStart();
116		fHalftone = new Halftone(GetJobData()->GetSurfaceType(),
117			GetJobData()->GetGamma(), GetJobData()->GetInkDensity(),
118			GetJobData()->GetDitherType());
119		return true;
120	}
121	catch (TransportException& err) {
122		return false;
123	}
124}
125
126
127bool
128PSDriver::StartPage(int page)
129{
130	page ++;
131	_WritePSString("%%%%Page: %d %d\n", page, page);
132	_WritePSString("gsave\n");
133	_SetupCTM();
134	return true;
135}
136
137
138bool
139PSDriver::EndPage(int)
140{
141	try {
142		fPrintedPages ++;
143		_WritePSString("grestore\n");
144		_WritePSString("showpage\n");
145
146		_FlushFilterIfNeeded();
147
148		return true;
149	}
150	catch (TransportException& err) {
151		return false;
152	}
153}
154
155
156void
157PSDriver::_SetupCTM()
158{
159	const float leftMargin = GetJobData()->GetPrintableRect().left;
160	const float topMargin = GetJobData()->GetPrintableRect().top;
161	if (GetJobData()->GetOrientation() == JobData::kPortrait) {
162		// move origin from bottom left to top left
163		// and set margin
164		_WritePSString("%f %f translate\n", leftMargin,
165			GetJobData()->GetPaperRect().Height()-topMargin);
166	} else {
167		// landscape:
168		// move origin from bottom left to margin top and left
169		// and rotate page contents
170		_WritePSString("%f %f translate\n", topMargin, leftMargin);
171		_WritePSString("90 rotate\n");
172	}
173	// y values increase from top to bottom
174	// units of measure is dpi
175	_WritePSString("72 %d div 72 -%d div scale\n", GetJobData()->GetXres(),
176		GetJobData()->GetYres());
177}
178
179
180bool
181PSDriver::EndDocument(bool)
182{
183	try {
184		if (fHalftone) {
185			delete fHalftone;
186			fHalftone = NULL;
187		}
188		_JobEnd();
189		return true;
190	}
191	catch (TransportException& err) {
192		return false;
193	}
194}
195
196
197static inline uchar
198ToHexDigit(uchar value)
199{
200	if (value <= 9) return '0' + value;
201	else return 'a' + (value - 10);
202}
203
204
205bool
206PSDriver::NextBand(BBitmap* bitmap, BPoint* offset)
207{
208	DBGMSG(("> nextBand\n"));
209
210	try {
211		BRect bounds = bitmap->Bounds();
212
213		RECT rc;
214		rc.left = (int)bounds.left;
215		rc.top = (int)bounds.top;
216		rc.right = (int)bounds.right;
217		rc.bottom = (int)bounds.bottom;
218
219		int height = rc.bottom - rc.top + 1;
220
221		int x = (int)offset->x;
222		int y = (int)offset->y;
223
224		int page_height = GetPageHeight();
225
226		if (y + height > page_height) {
227			height = page_height - y;
228		}
229
230		rc.bottom = height - 1;
231
232		DBGMSG(("height = %d\n", height));
233		DBGMSG(("x = %d\n", x));
234		DBGMSG(("y = %d\n", y));
235
236		if (get_valid_rect(bitmap, &rc)) {
237
238			DBGMSG(("validate rect = %d, %d, %d, %d\n",
239				rc.left, rc.top, rc.right, rc.bottom));
240
241			x = rc.left;
242			y += rc.top;
243
244			bool color = GetJobData()->GetColor() == JobData::kColor;
245			int width = rc.right - rc.left + 1;
246			int widthByte = (width + 7) / 8;
247				// byte boundary
248			int height = rc.bottom - rc.top + 1;
249			int in_size = color ? width : widthByte;
250			int out_size = color ? width * 6: widthByte * 2;
251			int delta = bitmap->BytesPerRow();
252
253			DBGMSG(("width = %d\n", width));
254			DBGMSG(("widthByte = %d\n", widthByte));
255			DBGMSG(("height = %d\n", height));
256			DBGMSG(("out_size = %d\n", out_size));
257			DBGMSG(("delta = %d\n", delta));
258			DBGMSG(("renderobj->Get_pixel_depth() = %d\n",
259				fHalftone->GetPixelDepth()));
260
261			uchar* ptr = static_cast<uchar*>(bitmap->Bits())
262						+ rc.top * delta
263						+ (rc.left * fHalftone->GetPixelDepth()) / 8;
264
265			int compression_method;
266			int compressed_size;
267			const uchar* buffer;
268
269			std::vector<uchar> in_buffer(in_size);
270				// gray values
271			std::vector<uchar> out_buffer(out_size);
272				// gray values in hexadecimal
273
274			DBGMSG(("move\n"));
275
276			int size = color ? width * 3 : in_size;
277			_StartRasterGraphics(x, y, width, height, size);
278
279			for (int i = rc.top; i <= rc.bottom; i++) {
280				if (color) {
281					uchar* out = &out_buffer[0];
282					uchar* in  = ptr;
283					for (int w = width; w > 0; w --) {
284						*out++ = ToHexDigit((in[2]) >> 4);
285						*out++ = ToHexDigit((in[2]) & 15);
286						*out++ = ToHexDigit((in[1]) >> 4);
287						*out++ = ToHexDigit((in[1]) & 15);
288						*out++ = ToHexDigit((in[0]) >> 4);
289						*out++ = ToHexDigit((in[0]) & 15);
290						in += 4;
291					}
292				} else {
293					fHalftone->Dither(&in_buffer[0], ptr, x, y, width);
294
295					uchar* in = &in_buffer[0];
296					uchar* out = &out_buffer[0];
297
298					for (int w = in_size; w > 0; w --, in ++) {
299						*in = ~*in; // invert pixels
300						*out++ = ToHexDigit((*in) >> 4);
301						*out++ = ToHexDigit((*in) & 15);
302					}
303				}
304
305				{
306					compression_method = 0; // uncompressed
307					buffer = &out_buffer[0];
308					compressed_size = out_size;
309				}
310
311				_RasterGraphics(
312					compression_method,
313					buffer,
314					compressed_size);
315
316				ptr  += delta;
317				y++;
318			}
319
320			_EndRasterGraphics();
321
322		} else
323			DBGMSG(("band bitmap is clean.\n"));
324
325		if (y >= page_height) {
326			offset->x = -1.0;
327			offset->y = -1.0;
328		} else
329			offset->y += height;
330
331		DBGMSG(("< nextBand\n"));
332		return true;
333	}
334	catch (TransportException& err) {
335		BAlert* alert = new BAlert("", err.What(), "OK");
336		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
337		alert->Go();
338		return false;
339	}
340}
341
342
343void
344PSDriver::_JobStart()
345{
346	// PostScript header
347	_WritePSString("%%!PS-Adobe-3.0\n");
348	_WritePSString("%%%%LanguageLevel: 1\n");
349	_WritePSString("%%%%Title: %s\n",
350		GetSpoolMetaData()->GetDescription().c_str());
351	_WritePSString("%%%%Creator: %s\n",
352		GetSpoolMetaData()->GetMimeType().c_str());
353	_WritePSString("%%%%CreationDate: %s",
354		GetSpoolMetaData()->GetCreationTime().c_str());
355	_WritePSString("%%%%DocumentMedia: Plain %d %d white 0 ( )\n",
356		GetJobData()->GetPaperRect().IntegerWidth(),
357		GetJobData()->GetPaperRect().IntegerHeight());
358	_WritePSString("%%%%Pages: (atend)\n");
359	_WritePSString("%%%%EndComments\n");
360
361	_WritePSString("%%%%BeginDefaults\n");
362	_WritePSString("%%%%PageMedia: Plain\n");
363	_WritePSString("%%%%EndDefaults\n");
364}
365
366
367void
368PSDriver::_StartRasterGraphics(int x, int y, int width, int height,
369	int widthByte)
370{
371	bool color = GetJobData()->GetColor() == JobData::kColor;
372	fCompressionMethod = -1;
373	_WritePSString("gsave\n");
374	_WritePSString("/s %d string def\n", widthByte);
375	_WritePSString("%d %d translate\n", x, y);
376	_WritePSString("%d %d scale\n", width, height);
377	if (color)
378		_WritePSString("%d %d 8\n", width, height); // 8 bpp
379	else
380		_WritePSString("%d %d 1\n", width, height); // 1 bpp
381	_WritePSString("[%d 0 0 %d 0 0]\n", width, height);
382	_WritePSString("{ currentfile s readhexstring pop }\n");
383	if (color) {
384		_WritePSString("false 3\n"); // single data source, 3 color components
385		_WritePSString("colorimage\n");
386	} else
387		_WritePSString("image\n\n");
388}
389
390
391void
392PSDriver::_EndRasterGraphics()
393{
394	_WritePSString("grestore\n");
395}
396
397
398void
399PSDriver::_RasterGraphics(int compression_method, const uchar* buffer,
400	int size)
401{
402	if (fCompressionMethod != compression_method) {
403		fCompressionMethod = compression_method;
404	}
405	_WritePSData(buffer, size);
406	_WritePSString("\n");
407}
408
409
410void
411PSDriver::_JobEnd()
412{
413	_WritePSString("%%%%Pages: %d\n", fPrintedPages);
414	_WritePSString("%%%%EOF\n");
415
416	_FlushFilterIfNeeded();
417}
418