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