1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 * Copyright (C) 2016 Mopria Alliance, Inc.
4 * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include <sys/types.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <math.h>
23 #include <cups/raster.h>
24
25 #include "lib_pcl.h"
26 #include "wprint_image.h"
27
28 #include "media.h"
29
30 #define TAG "lib_pwg"
31 #define STANDARD_SCALE_FOR_PDF 72.0
32 #define _MI_TO_PIXELS(n, res) ((n)*(res)+500)/1000.0
33 #define _MI_TO_POINTS(n) _MI_TO_PIXELS(n, STANDARD_SCALE_FOR_PDF)
34
35 cups_raster_t *ras_out = NULL;
36 cups_page_header2_t header_pwg;
37
38 /*
39 * Write the PWG header
40 */
_write_header_pwg(int pixel_width,int pixel_height,cups_page_header2_t * h,bool monochrome)41 static void _write_header_pwg(int pixel_width, int pixel_height, cups_page_header2_t *h,
42 bool monochrome) {
43 if (h != NULL) {
44 strcpy(h->MediaClass, "PwgRaster");
45 strcpy(h->MediaColor, "");
46 strcpy(h->MediaType, "");
47 strcpy(h->OutputType, "");
48 h->AdvanceDistance = 0;
49 h->AdvanceMedia = CUPS_ADVANCE_FILE;
50 h->Collate = CUPS_FALSE;
51 h->CutMedia = CUPS_CUT_NONE;
52 h->cupsPageSize[0] = (float) ((pixel_width * STANDARD_SCALE_FOR_PDF) / h->HWResolution[0]);
53 h->cupsPageSize[1] = (float) ((pixel_height * STANDARD_SCALE_FOR_PDF) / h->HWResolution[1]);
54
55 h->ImagingBoundingBox[0] = 0;
56 h->ImagingBoundingBox[1] = 0;
57 h->ImagingBoundingBox[2] = h->cupsPageSize[0];
58 h->ImagingBoundingBox[3] = h->cupsPageSize[1];
59 h->cupsBorderlessScalingFactor = 1.0;
60 h->InsertSheet = CUPS_FALSE;
61 h->Jog = CUPS_JOG_NONE;
62 h->LeadingEdge = CUPS_EDGE_TOP;
63 h->Margins[0] = 0;
64 h->Margins[1] = 0;
65 h->ManualFeed = CUPS_TRUE;
66 h->MediaPosition = 0;
67 h->MediaWeight = 0;
68 h->MirrorPrint = CUPS_FALSE;
69 h->NegativePrint = CUPS_FALSE;
70 h->NumCopies = 1;
71 h->Orientation = CUPS_ORIENT_0;
72 h->PageSize[0] = (int) h->cupsPageSize[0];
73 h->PageSize[1] = (int) h->cupsPageSize[1];
74 h->Separations = CUPS_TRUE;
75 h->TraySwitch = CUPS_TRUE;
76 h->Tumble = CUPS_TRUE;
77 h->cupsWidth = pixel_width;
78 h->cupsHeight = pixel_height;
79 h->cupsBitsPerPixel = (monochrome ? 8 : 24);
80 h->cupsBitsPerColor = 8;
81 h->cupsColorSpace = (monochrome ? CUPS_CSPACE_SW : CUPS_CSPACE_SRGB);
82 h->cupsBytesPerLine = (h->cupsBitsPerPixel * pixel_width + 7) / 8;
83 h->cupsColorOrder = CUPS_ORDER_CHUNKED;
84 h->cupsCompression = 0;
85 h->cupsRowCount = 1;
86 h->cupsRowFeed = 1;
87 h->cupsRowStep = 1;
88 h->cupsNumColors = 0;
89 h->cupsImagingBBox[0] = 0.0;
90 h->cupsImagingBBox[1] = 0.0;
91 h->cupsImagingBBox[2] = 0.0;
92 h->cupsImagingBBox[3] = 0.0;
93
94 strcpy(h->cupsMarkerType, "Marker Type");
95 strcpy(h->cupsRenderingIntent, "Rendering Intent");
96 strcpy(h->cupsPageSizeName, "Letter");
97 }
98 }
99
100 /*
101 * Store the supplied media size into job_info
102 */
_get_pwg_media_size(pcl_job_info_t * job_info,media_size_t media_size,PCLmPageSetup * myPageInfo)103 static void _get_pwg_media_size(pcl_job_info_t *job_info, media_size_t media_size,
104 PCLmPageSetup *myPageInfo) {
105 int i = 0;
106 do {
107 if (myPageInfo == NULL) {
108 continue;
109 }
110
111 for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
112 if (media_size == SupportedMediaSizes[i].media_size) {
113 strncpy(myPageInfo->mediaSizeName, SupportedMediaSizes[i].PCL6Name,
114 sizeof(myPageInfo->mediaSizeName) - 1);
115
116 myPageInfo->mediaWidth = floorf(
117 _MI_TO_POINTS(SupportedMediaSizes[i].WidthInInches));
118 myPageInfo->mediaHeight = floorf(
119 _MI_TO_POINTS(SupportedMediaSizes[i].HeightInInches));
120
121 LOGD(" _get_pwg_media_size(): match found: %d, %s, width=%f, height=%f",
122 media_size, SupportedMediaSizes[i].PCL6Name, myPageInfo->mediaWidth,
123 myPageInfo->mediaHeight);
124 break; // we found a match, so break out of loop
125 }
126 }
127 }
128 while (0);
129
130 if (i == SUPPORTED_MEDIA_SIZE_COUNT) {
131 // media size not found, defaulting to letter
132 LOGD("_get_pwg_media_size(): media size, %d, NOT FOUND, setting to letter", media_size);
133 _get_pwg_media_size(job_info, US_LETTER, myPageInfo);
134 }
135 }
136
137 /*
138 * Write a buffer to the output stream
139 */
_pwg_io_write(void * ctx,unsigned char * buf,size_t bytes)140 static ssize_t _pwg_io_write(void *ctx, unsigned char *buf, size_t bytes) {
141 pcl_job_info_t *pwg_job_info = (pcl_job_info_t *) ctx;
142 _WRITE(pwg_job_info, (const char *) buf, bytes);
143 return bytes;
144 }
145
_start_job(wJob_t job_handle,pcl_job_info_t * job_info,media_size_t media_size,media_type_t media_type,int resolution,duplex_t duplex,duplex_dry_time_t dry_time,color_space_t color_space,media_tray_t media_tray,float top_margin,float left_margin)146 static wJob_t _start_job(wJob_t job_handle, pcl_job_info_t *job_info, media_size_t media_size,
147 media_type_t media_type, int resolution, duplex_t duplex, duplex_dry_time_t dry_time,
148 color_space_t color_space, media_tray_t media_tray, float top_margin,
149 float left_margin) {
150 if (job_info == NULL) {
151 return _WJOBH_NONE;
152 }
153
154 if (job_info->job_handle != _WJOBH_NONE) {
155 if (job_info->wprint_ifc != NULL) {
156 LOGE("_start_job() required cleanup");
157 }
158
159 job_info->job_handle = _WJOBH_NONE;
160 }
161
162 if ((job_info->wprint_ifc == NULL) || (job_info->print_ifc == NULL)) {
163 return _WJOBH_NONE;
164 }
165
166 LOGD("_start_job(), media_size %d, media_type %d, dt %d, %s, media_tray %d", media_size,
167 media_type, dry_time, (duplex == DUPLEX_MODE_NONE) ? "simplex" : "duplex",
168 media_tray);
169 job_info->job_handle = job_handle;
170
171 _START_JOB(job_info, "pwg");
172
173 header_pwg.HWResolution[0] = resolution;
174 header_pwg.HWResolution[1] = resolution;
175
176 job_info->resolution = resolution;
177 job_info->media_size = media_size;
178 job_info->standard_scale = (float) resolution / (float) 72;
179
180 // initialize static variables
181 job_info->pclm_output_buffer = NULL;
182 job_info->seed_row = job_info->pcl_buff = NULL; // unused
183 job_info->pixel_width = job_info->pixel_height = job_info->page_number = job_info->num_rows = 0;
184
185 memset((void *) &job_info->pclm_page_info, 0x0, sizeof(PCLmPageSetup));
186 _get_pwg_media_size(job_info, media_size, &job_info->pclm_page_info);
187
188 if (left_margin < 0.0f || top_margin < 0.0f) {
189 job_info->pclm_page_info.mediaWidthOffset = 0.0f;
190 job_info->pclm_page_info.mediaHeightOffset = 0.0f;
191 } else {
192 job_info->pclm_page_info.mediaWidthOffset = left_margin;
193 job_info->pclm_page_info.mediaHeightOffset = top_margin;
194 }
195
196 header_pwg.cupsMediaType = media_size;
197
198 job_info->pclm_page_info.pageOrigin = top_left; // REVISIT
199 job_info->monochrome = (color_space == COLOR_SPACE_MONO);
200 job_info->pclm_page_info.dstColorSpaceSpefication = deviceRGB;
201 if (color_space == COLOR_SPACE_MONO) {
202 header_pwg.cupsColorSpace = CUPS_CSPACE_SW;
203 job_info->pclm_page_info.dstColorSpaceSpefication = deviceRGB;
204 } else if (color_space == COLOR_SPACE_COLOR) {
205 job_info->pclm_page_info.dstColorSpaceSpefication = deviceRGB;
206 header_pwg.cupsColorSpace = CUPS_CSPACE_SRGB;
207 } else if (color_space == COLOR_SPACE_ADOBE_RGB) {
208 job_info->pclm_page_info.dstColorSpaceSpefication = adobeRGB;
209 header_pwg.cupsColorSpace = CUPS_CSPACE_SRGB;
210 }
211
212 job_info->pclm_page_info.stripHeight = job_info->strip_height;
213 job_info->pclm_page_info.destinationResolution = res600;
214 if (resolution == 300) {
215 job_info->pclm_page_info.destinationResolution = res300;
216 } else if (resolution == 600) {
217 job_info->pclm_page_info.destinationResolution = res600;
218 } else if (resolution == 1200) {
219 job_info->pclm_page_info.destinationResolution = res1200;
220 }
221
222 if (duplex == DUPLEX_MODE_BOOK) {
223 job_info->pclm_page_info.duplexDisposition = duplex_longEdge;
224 header_pwg.Duplex = CUPS_TRUE;
225 } else if (duplex == DUPLEX_MODE_TABLET) {
226 job_info->pclm_page_info.duplexDisposition = duplex_shortEdge;
227 header_pwg.Duplex = CUPS_TRUE;
228 } else {
229 job_info->pclm_page_info.duplexDisposition = simplex;
230 header_pwg.Duplex = CUPS_FALSE;
231 }
232
233 job_info->pclm_page_info.mirrorBackside = false;
234 header_pwg.OutputFaceUp = CUPS_FALSE;
235 header_pwg.cupsBitsPerColor = BITS_PER_CHANNEL;
236 ras_out = cupsRasterOpenIO(_pwg_io_write, (void *) job_info, CUPS_RASTER_WRITE_PWG);
237 return job_info->job_handle;
238 }
239
_start_page(pcl_job_info_t * job_info,int pixel_width,int pixel_height)240 static int _start_page(pcl_job_info_t *job_info, int pixel_width, int pixel_height) {
241 PCLmPageSetup *page_info = &job_info->pclm_page_info;
242 _START_PAGE(job_info, pixel_width, pixel_height);
243
244 page_info->sourceHeight = (float) pixel_height / job_info->standard_scale;
245 page_info->sourceWidth = (float) pixel_width / job_info->standard_scale;
246 LOGI("_start_page(), strip height=%d, image width=%d, image height=%d, scaled width=%f, "
247 "scaled height=%f", page_info->stripHeight, pixel_width, pixel_height,
248 page_info->sourceWidth, page_info->sourceHeight);
249 if (job_info->num_components == 3) {
250 page_info->colorContent = color_content;
251 page_info->srcColorSpaceSpefication = deviceRGB;
252 } else {
253 page_info->colorContent = gray_content;
254 page_info->srcColorSpaceSpefication = grayScale;
255 }
256 page_info->colorContent = color_content;
257 page_info->srcColorSpaceSpefication = deviceRGB;
258
259 // REVISIT: possibly get this value dynamically from device via IPP (ePCL)
260 // however, current ink devices report RLE as the default compression type, which compresses
261 // much worse than JPEG or FLATE
262 page_info->compTypeRequested = compressDCT;
263
264 job_info->scan_line_width = BYTES_PER_PIXEL(pixel_width);
265
266 // Fill up the pwg header
267 _write_header_pwg(pixel_width, pixel_height, &header_pwg, job_info->monochrome);
268
269 LOGI("cupsWidth = %d", header_pwg.cupsWidth);
270 LOGI("cupsHeight = %d", header_pwg.cupsHeight);
271 LOGI("cupsPageWidth = %f", header_pwg.cupsPageSize[0]);
272 LOGI("cupsPageHeight = %f", header_pwg.cupsPageSize[1]);
273 LOGI("cupsBitsPerColor = %d", header_pwg.cupsBitsPerColor);
274 LOGI("cupsBitsPerPixel = %d", header_pwg.cupsBitsPerPixel);
275 LOGI("cupsBytesPerLine = %d", header_pwg.cupsBytesPerLine);
276 LOGI("cupsColorOrder = %d", header_pwg.cupsColorOrder);
277 LOGI("cupsColorSpace = %d", header_pwg.cupsColorSpace);
278
279 cupsRasterWriteHeader2(ras_out, &header_pwg);
280 job_info->page_number++;
281 return job_info->page_number;
282 }
283
_print_swath(pcl_job_info_t * job_info,char * rgb_pixels,int start_row,int num_rows,int bytes_per_row)284 static int _print_swath(pcl_job_info_t *job_info, char *rgb_pixels, int start_row, int num_rows,
285 int bytes_per_row) {
286 int outBuffSize;
287 _PAGE_DATA(job_info, (const unsigned char *) rgb_pixels, (num_rows * bytes_per_row));
288
289 if (job_info->monochrome) {
290 unsigned char *buff = (unsigned char *) rgb_pixels;
291 int nbytes = (num_rows * bytes_per_row);
292 int readIndex, writeIndex;
293 for (readIndex = writeIndex = 0; readIndex < nbytes; readIndex += BYTES_PER_PIXEL(1)) {
294 unsigned char gray = SP_GRAY(buff[readIndex + 0], buff[readIndex + 1],
295 buff[readIndex + 2]);
296 buff[writeIndex++] = gray;
297 }
298 outBuffSize = writeIndex;
299 } else {
300 outBuffSize = num_rows * bytes_per_row;
301 }
302
303 LOGD("_print_swath(): page #%d, buffSize=%d, rows %d - %d (%d rows), bytes per row %d",
304 job_info->page_number, job_info->strip_height * job_info->scan_line_width,
305 start_row, start_row + num_rows - 1, num_rows, bytes_per_row);
306 /* If the inBufferSize is ever used in genPCLm, change the input parameter to pass in
307 * image_info->printable_width*num_components*strip_height. it is currently pixel_width
308 * (from _start_page()) * num_components * strip_height
309 */
310 if (ras_out != NULL) {
311 unsigned result = cupsRasterWritePixels(ras_out, (unsigned char *) rgb_pixels, outBuffSize);
312 LOGD("cupsRasterWritePixels return %d", result);
313 } else {
314 LOGD("cupsRasterWritePixels raster is null");
315 }
316 return OK;
317 }
318
_end_page(pcl_job_info_t * job_info,int page_number)319 static int _end_page(pcl_job_info_t *job_info, int page_number) {
320 if (page_number == -1) {
321 LOGI("lib_pclm: _end_page(): writing blank page");
322 /* PWG Raster's PackBits-like algorithm allows for a maximum of:
323 * 256 repeating rows and is encoded using a single octet containing (count - 1)
324 * 128 repeating color value and is run length encoded using a single octect
325 * containing (count - 1). Therefore we create a small white 128x256 block using:
326 * (255 repeating rows)0xFF, (128 repeating colors)0x7F, (white)0xFF, 0xFF, 0xFF
327 * and when sending monochrome we just send the first 3 bytes instead of all 5
328 */
329 _start_page(job_info, 128, 256);
330 unsigned char blank_data[5] = {0xFF, 0x7F, 0xFF, 0xFF, 0xFF};
331 _pwg_io_write(job_info, blank_data, job_info->monochrome ? 3 : 5);
332 }
333 LOGI("lib_pcwg: _end_page()");
334 _END_PAGE(job_info);
335
336 return OK;
337 }
338
_end_job(pcl_job_info_t * job_info)339 static int _end_job(pcl_job_info_t *job_info) {
340 LOGI("_end_job()");
341 _END_JOB(job_info);
342 cupsRasterClose(ras_out);
343 return OK;
344 }
345
_canCancelMidPage(void)346 static bool _canCancelMidPage(void) {
347 return false;
348 }
349
350 static const ifc_pcl_t _pcl_ifc = {
351 _start_job, _end_job, _start_page, _end_page, _print_swath, _canCancelMidPage
352 };
353
pwg_connect(void)354 ifc_pcl_t *pwg_connect(void) {
355 return ((ifc_pcl_t *) &_pcl_ifc);
356 }