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 
32 cups_raster_t *ras_out = NULL;
33 cups_page_header2_t header_pwg;
34 
35 /*
36  * Write the PWG header
37  */
_write_header_pwg(int pixel_width,int pixel_height,cups_page_header2_t * h,bool monochrome)38 static void _write_header_pwg(int pixel_width, int pixel_height, cups_page_header2_t *h,
39         bool monochrome) {
40     if (h != NULL) {
41         strcpy(h->MediaClass, "PwgRaster");
42         strcpy(h->MediaColor, "");
43         strcpy(h->MediaType, "");
44         strcpy(h->OutputType, "");
45         h->AdvanceDistance = 0;
46         h->AdvanceMedia = CUPS_ADVANCE_FILE;
47         h->Collate = CUPS_FALSE;
48         h->CutMedia = CUPS_CUT_NONE;
49         h->cupsPageSize[0] = (float) ((pixel_width * STANDARD_SCALE_FOR_PDF) / h->HWResolution[0]);
50         h->cupsPageSize[1] = (float) ((pixel_height * STANDARD_SCALE_FOR_PDF) / h->HWResolution[1]);
51 
52         h->ImagingBoundingBox[0] = 0;
53         h->ImagingBoundingBox[1] = 0;
54         h->ImagingBoundingBox[2] = h->cupsPageSize[0];
55         h->ImagingBoundingBox[3] = h->cupsPageSize[1];
56         h->cupsBorderlessScalingFactor = 1.0;
57         h->InsertSheet = CUPS_FALSE;
58         h->Jog = CUPS_JOG_NONE;
59         h->LeadingEdge = CUPS_EDGE_TOP;
60         h->Margins[0] = 0;
61         h->Margins[1] = 0;
62         h->ManualFeed = CUPS_TRUE;
63         h->MediaPosition = 0;
64         h->MediaWeight = 0;
65         h->MirrorPrint = CUPS_FALSE;
66         h->NegativePrint = CUPS_FALSE;
67         h->NumCopies = 1;
68         h->Orientation = CUPS_ORIENT_0;
69         h->PageSize[0] = (int) h->cupsPageSize[0];
70         h->PageSize[1] = (int) h->cupsPageSize[1];
71         h->Separations = CUPS_TRUE;
72         h->TraySwitch = CUPS_TRUE;
73         h->cupsWidth = pixel_width;
74         h->cupsHeight = pixel_height;
75         h->cupsBitsPerPixel = (monochrome ? 8 : 24);
76         h->cupsBitsPerColor = 8;
77         h->cupsColorSpace = (monochrome ? CUPS_CSPACE_SW : CUPS_CSPACE_SRGB);
78         h->cupsBytesPerLine = (h->cupsBitsPerPixel * pixel_width + 7) / 8;
79         h->cupsColorOrder = CUPS_ORDER_CHUNKED;
80         h->cupsCompression = 0;
81         h->cupsRowCount = 1;
82         h->cupsRowFeed = 1;
83         h->cupsRowStep = 1;
84         h->cupsNumColors = 0;
85         h->cupsImagingBBox[0] = 0.0;
86         h->cupsImagingBBox[1] = 0.0;
87         h->cupsImagingBBox[2] = 0.0;
88         h->cupsImagingBBox[3] = 0.0;
89 
90         strcpy(h->cupsMarkerType, "Marker Type");
91         strcpy(h->cupsRenderingIntent, "Rendering Intent");
92         strcpy(h->cupsPageSizeName, "Letter");
93     }
94 }
95 
96 /*
97  * Store the supplied media size into job_info
98  */
_get_pwg_media_size(pcl_job_info_t * job_info,media_size_t media_size,PCLmPageSetup * myPageInfo)99 static void _get_pwg_media_size(pcl_job_info_t *job_info, media_size_t media_size,
100         PCLmPageSetup *myPageInfo) {
101     int i = 0;
102     do {
103         if (myPageInfo == NULL) {
104             continue;
105         }
106 
107         for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
108             if (media_size == SupportedMediaSizes[i].media_size) {
109                 strncpy(myPageInfo->mediaSizeName, SupportedMediaSizes[i].PCL6Name,
110                         sizeof(myPageInfo->mediaSizeName) - 1);
111 
112                 myPageInfo->mediaWidth = floorf(
113                         _MI_TO_POINTS(SupportedMediaSizes[i].WidthInInches));
114                 myPageInfo->mediaHeight = floorf(
115                         _MI_TO_POINTS(SupportedMediaSizes[i].HeightInInches));
116 
117                 LOGD("  _get_pwg_media_size(): match found: %d, %s, width=%f, height=%f",
118                         media_size, SupportedMediaSizes[i].PCL6Name, myPageInfo->mediaWidth,
119                         myPageInfo->mediaHeight);
120                 break;  // we found a match, so break out of loop
121             }
122         }
123     }
124     while (0);
125 
126     if (i == SUPPORTED_MEDIA_SIZE_COUNT) {
127         // media size not found, defaulting to letter
128         LOGD("_get_pwg_media_size(): media size, %d, NOT FOUND, setting to letter", media_size);
129         _get_pwg_media_size(job_info, US_LETTER, myPageInfo);
130     }
131 }
132 
133 /*
134  * Write a buffer to the output stream
135  */
_pwg_io_write(void * ctx,unsigned char * buf,size_t bytes)136 static ssize_t _pwg_io_write(void *ctx, unsigned char *buf, size_t bytes) {
137     pcl_job_info_t *pwg_job_info = (pcl_job_info_t *) ctx;
138     _WRITE(pwg_job_info, (const char *) buf, bytes);
139     return bytes;
140 }
141 
_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)142 static wJob_t _start_job(wJob_t job_handle, pcl_job_info_t *job_info, media_size_t media_size,
143         media_type_t media_type, int resolution, duplex_t duplex, duplex_dry_time_t dry_time,
144         color_space_t color_space, media_tray_t media_tray, float top_margin,
145         float left_margin) {
146     if (job_info == NULL) {
147         return _WJOBH_NONE;
148     }
149 
150     if (job_info->job_handle != _WJOBH_NONE) {
151         if (job_info->wprint_ifc != NULL) {
152             LOGE("_start_job() required cleanup");
153         }
154 
155         job_info->job_handle = _WJOBH_NONE;
156     }
157 
158     if ((job_info->wprint_ifc == NULL) || (job_info->print_ifc == NULL)) {
159         return _WJOBH_NONE;
160     }
161 
162     LOGD("_start_job(), media_size %d, media_type %d, dt %d, %s, media_tray %d", media_size,
163             media_type, dry_time, (duplex == DUPLEX_MODE_NONE) ? "simplex" : "duplex",
164             media_tray);
165     job_info->job_handle = job_handle;
166 
167     _START_JOB(job_info, "pwg");
168 
169     header_pwg.HWResolution[0] = resolution;
170     header_pwg.HWResolution[1] = resolution;
171 
172     job_info->resolution = resolution;
173     job_info->media_size = media_size;
174     job_info->standard_scale = (float) resolution / (float) 72;
175 
176     //  initialize static variables
177     job_info->pclm_output_buffer = NULL;
178     job_info->seed_row = job_info->pcl_buff = NULL;    // unused
179     job_info->pixel_width = job_info->pixel_height = job_info->page_number = job_info->num_rows = 0;
180 
181     memset((void *) &job_info->pclm_page_info, 0x0, sizeof(PCLmPageSetup));
182     _get_pwg_media_size(job_info, media_size, &job_info->pclm_page_info);
183 
184     if (left_margin < 0.0f || top_margin < 0.0f) {
185         job_info->pclm_page_info.mediaWidthOffset = 0.0f;
186         job_info->pclm_page_info.mediaHeightOffset = 0.0f;
187     } else {
188         job_info->pclm_page_info.mediaWidthOffset = left_margin;
189         job_info->pclm_page_info.mediaHeightOffset = top_margin;
190     }
191 
192     header_pwg.cupsMediaType = media_size;
193 
194     job_info->pclm_page_info.pageOrigin = top_left;    // REVISIT
195     job_info->monochrome = (color_space == COLOR_SPACE_MONO);
196     job_info->pclm_page_info.dstColorSpaceSpefication = deviceRGB;
197     if (color_space == COLOR_SPACE_MONO) {
198         header_pwg.cupsColorSpace = CUPS_CSPACE_SW;
199         job_info->pclm_page_info.dstColorSpaceSpefication = deviceRGB;
200     } else if (color_space == COLOR_SPACE_COLOR) {
201         job_info->pclm_page_info.dstColorSpaceSpefication = deviceRGB;
202         header_pwg.cupsColorSpace = CUPS_CSPACE_SRGB;
203     } else if (color_space == COLOR_SPACE_ADOBE_RGB) {
204         job_info->pclm_page_info.dstColorSpaceSpefication = adobeRGB;
205         header_pwg.cupsColorSpace = CUPS_CSPACE_SRGB;
206     }
207 
208     job_info->pclm_page_info.stripHeight = job_info->strip_height;
209     job_info->pclm_page_info.destinationResolution = res600;
210     if (resolution == 300) {
211         job_info->pclm_page_info.destinationResolution = res300;
212     } else if (resolution == 600) {
213         job_info->pclm_page_info.destinationResolution = res600;
214     } else if (resolution == 1200) {
215         job_info->pclm_page_info.destinationResolution = res1200;
216     }
217 
218     if (duplex == DUPLEX_MODE_BOOK) {
219         job_info->pclm_page_info.duplexDisposition = duplex_longEdge;
220         header_pwg.Duplex = CUPS_TRUE;
221         header_pwg.Tumble = CUPS_FALSE;
222     } else if (duplex == DUPLEX_MODE_TABLET) {
223         job_info->pclm_page_info.duplexDisposition = duplex_shortEdge;
224         header_pwg.Duplex = CUPS_TRUE;
225         header_pwg.Tumble = CUPS_TRUE;
226     } else {
227         job_info->pclm_page_info.duplexDisposition = simplex;
228         header_pwg.Duplex = CUPS_FALSE;
229         header_pwg.Tumble = CUPS_FALSE;
230     }
231 
232     job_info->pclm_page_info.mirrorBackside = false;
233     header_pwg.OutputFaceUp = CUPS_FALSE;
234     header_pwg.cupsBitsPerColor = BITS_PER_CHANNEL;
235     ras_out = cupsRasterOpenIO(_pwg_io_write, (void *) job_info, CUPS_RASTER_WRITE_PWG);
236     return job_info->job_handle;
237 }
238 
_start_page(pcl_job_info_t * job_info,int pixel_width,int pixel_height)239 static int _start_page(pcl_job_info_t *job_info, int pixel_width, int pixel_height) {
240     PCLmPageSetup *page_info = &job_info->pclm_page_info;
241     _START_PAGE(job_info, pixel_width, pixel_height);
242 
243     page_info->sourceHeight = (float) pixel_height / job_info->standard_scale;
244     page_info->sourceWidth = (float) pixel_width / job_info->standard_scale;
245     LOGI("_start_page(), strip height=%d, image width=%d, image height=%d, scaled width=%f, "
246             "scaled height=%f", page_info->stripHeight, pixel_width, pixel_height,
247             page_info->sourceWidth, page_info->sourceHeight);
248     if (job_info->num_components == 3) {
249         page_info->colorContent = color_content;
250         page_info->srcColorSpaceSpefication = deviceRGB;
251     } else {
252         page_info->colorContent = gray_content;
253         page_info->srcColorSpaceSpefication = grayScale;
254     }
255     page_info->colorContent = color_content;
256     page_info->srcColorSpaceSpefication = deviceRGB;
257 
258     // REVISIT: possibly get this value dynamically from device via IPP (ePCL)
259     // however, current ink devices report RLE as the default compression type, which compresses
260     // much worse than JPEG or FLATE
261     page_info->compTypeRequested = compressDCT;
262 
263     job_info->scan_line_width = BYTES_PER_PIXEL(pixel_width);
264 
265     // Fill up the pwg header
266     _write_header_pwg(pixel_width, pixel_height, &header_pwg, job_info->monochrome);
267 
268     LOGI("cupsWidth = %d", header_pwg.cupsWidth);
269     LOGI("cupsHeight = %d", header_pwg.cupsHeight);
270     LOGI("cupsPageWidth = %f", header_pwg.cupsPageSize[0]);
271     LOGI("cupsPageHeight = %f", header_pwg.cupsPageSize[1]);
272     LOGI("cupsBitsPerColor = %d", header_pwg.cupsBitsPerColor);
273     LOGI("cupsBitsPerPixel = %d", header_pwg.cupsBitsPerPixel);
274     LOGI("cupsBytesPerLine = %d", header_pwg.cupsBytesPerLine);
275     LOGI("cupsColorOrder = %d", header_pwg.cupsColorOrder);
276     LOGI("cupsColorSpace = %d", header_pwg.cupsColorSpace);
277 
278     cupsRasterWriteHeader2(ras_out, &header_pwg);
279     job_info->page_number++;
280     return job_info->page_number;
281 }
282 
_print_swath(pcl_job_info_t * job_info,char * rgb_pixels,int start_row,int num_rows,int bytes_per_row)283 static int _print_swath(pcl_job_info_t *job_info, char *rgb_pixels, int start_row, int num_rows,
284         int bytes_per_row) {
285     int outBuffSize;
286     _PAGE_DATA(job_info, (const unsigned char *) rgb_pixels, (num_rows * bytes_per_row));
287 
288     if (job_info->monochrome) {
289         unsigned char *buff = (unsigned char *) rgb_pixels;
290         int nbytes = (num_rows * bytes_per_row);
291         int readIndex, writeIndex;
292         for (readIndex = writeIndex = 0; readIndex < nbytes; readIndex += BYTES_PER_PIXEL(1)) {
293             unsigned char gray = SP_GRAY(buff[readIndex + 0], buff[readIndex + 1],
294                     buff[readIndex + 2]);
295             buff[writeIndex++] = gray;
296         }
297         outBuffSize = writeIndex;
298     } else {
299         outBuffSize = num_rows * bytes_per_row;
300     }
301 
302     LOGD("_print_swath(): page #%d, buffSize=%d, rows %d - %d (%d rows), bytes per row %d",
303             job_info->page_number, job_info->strip_height * job_info->scan_line_width,
304             start_row, start_row + num_rows - 1, num_rows, bytes_per_row);
305     /* If the inBufferSize is ever used in genPCLm, change the input parameter to pass in
306      * image_info->printable_width*num_components*strip_height. it is currently pixel_width
307      * (from _start_page()) * num_components * strip_height
308      */
309     if (ras_out != NULL) {
310         unsigned result = cupsRasterWritePixels(ras_out, (unsigned char *) rgb_pixels, outBuffSize);
311         LOGD("cupsRasterWritePixels return %d", result);
312     } else {
313         LOGD("cupsRasterWritePixels raster is null");
314     }
315     return OK;
316 }
317 
318 /*
319  * Allocate and fill a blank page of PackBits data. Writes size into buffer_size. The buffer
320  * must be free'd by the caller.
321  */
_generate_blank_data(int pixel_width,int pixel_height,uint8 monochrome,size_t * buffer_size)322 unsigned char *_generate_blank_data(int pixel_width, int pixel_height, uint8 monochrome, size_t *buffer_size) {
323     if (pixel_width == 0 || pixel_height == 0) return NULL;
324 
325     /* PWG Raster's PackBits-like algorithm allows for a maximum of:
326      * 256 repeating rows and is encoded using a single octet containing (count - 1)
327      * 128 repeating color value and is run length encoded using a single octet containing (count - 1)
328      */
329     int rows_full = pixel_height / 256;
330     int columns_full = pixel_width / 128;
331     int row_fraction = ((pixel_height % 256) != 0) ? 1 : 0;
332     int column_fraction = ((pixel_width % 128) != 0) ? 1 : 0;
333     int column_data_size = 1 + (columns_full + column_fraction) * (monochrome ? 2 : 4);
334 
335     *buffer_size = (size_t) ((rows_full + row_fraction) * column_data_size);
336     unsigned char *buffer = (unsigned char *) malloc(*buffer_size);
337     if (buffer == NULL) return NULL;
338 
339     int i = 0;
340     for (int y = 0; y < rows_full + row_fraction; y++) {
341         // Add row-repeat command
342         if (y < rows_full) {
343             buffer[i++] = 0xFF;
344         } else {
345             buffer[i++] = (unsigned char) ((pixel_height % 256) - 1);
346         }
347 
348         for (int x = 0; x < columns_full + column_fraction; x++) {
349             // Add column-repeat command
350             if (x < columns_full) {
351                 buffer[i++] = 0x7F;
352             } else {
353                 buffer[i++] = (unsigned char) ((pixel_width % 128) - 1);
354             }
355 
356             // Pixel color to repeat
357             buffer[i++] = 0xFF;
358             if (!monochrome) {
359                 // Add rest of RGB for color output
360                 buffer[i++] = 0xFF;
361                 buffer[i++] = 0xFF;
362             }
363         }
364     }
365     return buffer;
366 }
367 
_end_page(pcl_job_info_t * job_info,int page_number)368 static int _end_page(pcl_job_info_t *job_info, int page_number) {
369     if (page_number == -1) {
370         LOGD("lib_pclm: _end_page(): writing blank page");
371 
372         size_t buffer_size;
373         unsigned char *buffer;
374         _start_page(job_info, header_pwg.cupsWidth, header_pwg.cupsHeight);
375         buffer = _generate_blank_data(header_pwg.cupsWidth, header_pwg.cupsHeight, job_info->monochrome, &buffer_size);
376         if (buffer == NULL) {
377             return ERROR;
378         } else {
379             _pwg_io_write(job_info, buffer, buffer_size);
380             free(buffer);
381         }
382     }
383     LOGI("lib_pcwg: _end_page()");
384     _END_PAGE(job_info);
385 
386     return OK;
387 }
388 
_end_job(pcl_job_info_t * job_info)389 static int _end_job(pcl_job_info_t *job_info) {
390     LOGI("_end_job()");
391     _END_JOB(job_info);
392     cupsRasterClose(ras_out);
393     return OK;
394 }
395 
_canCancelMidPage(void)396 static bool _canCancelMidPage(void) {
397     return false;
398 }
399 
400 static const ifc_pcl_t _pcl_ifc = {
401         _start_job, _end_job, _start_page, _end_page, _print_swath, _canCancelMidPage
402 };
403 
pwg_connect(void)404 ifc_pcl_t *pwg_connect(void) {
405     return ((ifc_pcl_t *) &_pcl_ifc);
406 }