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
35cups_raster_t *ras_out = NULL;
36cups_page_header2_t header_pwg;
37
38/*
39 * Write the PWG header
40 */
41static 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 */
103static 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 */
140static 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
146static 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
240static 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
284static 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
319/*
320 * Allocate and fill a blank page of PackBits data. Writes size into buffer_size. The buffer
321 * must be free'd by the caller.
322 */
323unsigned char *_generate_blank_data(int pixel_width, int pixel_height, uint8 monochrome, size_t *buffer_size) {
324    if (pixel_width == 0 || pixel_height == 0) return NULL;
325
326    /* PWG Raster's PackBits-like algorithm allows for a maximum of:
327     * 256 repeating rows and is encoded using a single octet containing (count - 1)
328     * 128 repeating color value and is run length encoded using a single octet containing (count - 1)
329     */
330    int rows_full = pixel_height / 256;
331    int columns_full = pixel_width / 128;
332    int row_fraction = ((pixel_height % 256) != 0) ? 1 : 0;
333    int column_fraction = ((pixel_width % 128) != 0) ? 1 : 0;
334    int column_data_size = 1 + (columns_full + column_fraction) * (monochrome ? 2 : 4);
335
336    *buffer_size = (size_t) ((rows_full + row_fraction) * column_data_size);
337    unsigned char *buffer = (unsigned char *) malloc(*buffer_size);
338    if (buffer == NULL) return NULL;
339
340    int i = 0;
341    for (int y = 0; y < rows_full + row_fraction; y++) {
342        // Add row-repeat command
343        if (y < rows_full) {
344            buffer[i++] = 0xFF;
345        } else {
346            buffer[i++] = (unsigned char) ((pixel_height % 256) - 1);
347        }
348
349        for (int x = 0; x < columns_full + column_fraction; x++) {
350            // Add column-repeat command
351            if (x < columns_full) {
352                buffer[i++] = 0x7F;
353            } else {
354                buffer[i++] = (unsigned char) ((pixel_width % 128) - 1);
355            }
356
357            // Pixel color to repeat
358            buffer[i++] = 0xFF;
359            if (!monochrome) {
360                // Add rest of RGB for color output
361                buffer[i++] = 0xFF;
362                buffer[i++] = 0xFF;
363            }
364        }
365    }
366    return buffer;
367}
368
369static int _end_page(pcl_job_info_t *job_info, int page_number) {
370    if (page_number == -1) {
371        LOGD("lib_pclm: _end_page(): writing blank page");
372
373        size_t buffer_size;
374        unsigned char *buffer;
375        _start_page(job_info, header_pwg.cupsWidth, header_pwg.cupsHeight);
376        buffer = _generate_blank_data(header_pwg.cupsWidth, header_pwg.cupsHeight, job_info->monochrome, &buffer_size);
377        if (buffer == NULL) {
378            return ERROR;
379        } else {
380            _pwg_io_write(job_info, buffer, buffer_size);
381            free(buffer);
382        }
383    }
384    LOGI("lib_pcwg: _end_page()");
385    _END_PAGE(job_info);
386
387    return OK;
388}
389
390static int _end_job(pcl_job_info_t *job_info) {
391    LOGI("_end_job()");
392    _END_JOB(job_info);
393    cupsRasterClose(ras_out);
394    return OK;
395}
396
397static bool _canCancelMidPage(void) {
398    return false;
399}
400
401static const ifc_pcl_t _pcl_ifc = {
402        _start_job, _end_job, _start_page, _end_page, _print_swath, _canCancelMidPage
403};
404
405ifc_pcl_t *pwg_connect(void) {
406    return ((ifc_pcl_t *) &_pcl_ifc);
407}