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 <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include "ifc_print_job.h"
23 #include "lib_pcl.h"
24 #include "wprint_image.h"
25 
26 #ifndef _GNU_SOURCE
27 #define _GNU_SOURCE
28 #endif
29 #ifndef __USE_UNIX98
30 #define __USE_UNIX98
31 #endif
32 
33 #include <pthread.h>
34 #include <semaphore.h>
35 #include <string.h>
36 
37 #define MAX_SEND_BUFFS (BUFFERED_ROWS / STRIPE_HEIGHT)
38 
39 #define TAG "plugin_pcl"
40 
41 typedef enum {
42     MSG_START_JOB,
43     MSG_START_PAGE,
44     MSG_SEND,
45     MSG_END_JOB,
46     MSG_END_PAGE,
47 } msg_id_t;
48 
49 typedef struct {
50     msg_id_t id;
51 
52     union {
53         struct {
54             float extra_margin;
55             int width;
56             int height;
57         } start_page;
58         struct {
59             char *buffer;
60             int start_row;
61             int num_rows;
62             int bytes_per_row;
63         } send;
64         struct {
65             int page;
66             char *buffers[MAX_SEND_BUFFS];
67             int count;
68         } end_page;
69     } param;
70 } msgQ_msg_t;
71 
72 typedef struct {
73     wJob_t job_handle;
74     msg_q_id msgQ;
75     pthread_t send_tid;
76     pcl_job_info_t job_info;
77     wprint_job_params_t *job_params;
78     sem_t buffs_sem;
79     ifc_pcl_t *pcl_ifc;
80 } plugin_data_t;
81 
82 static const char *_mime_types[] = {
83         MIME_TYPE_PDF,
84         NULL};
85 
86 static const char *_print_formats[] = {
87         PRINT_FORMAT_PCLM,
88         PRINT_FORMAT_PWG,
89         PRINT_FORMAT_PDF,
90         NULL};
91 
_get_mime_types(void)92 static const char **_get_mime_types(void) {
93     return _mime_types;
94 }
95 
_get_print_formats(void)96 static const char **_get_print_formats(void) {
97     return _print_formats;
98 }
99 
_cleanup_plugin_data(plugin_data_t * priv)100 static void _cleanup_plugin_data(plugin_data_t *priv) {
101     if (priv != NULL) {
102         if (priv->msgQ != MSG_Q_INVALID_ID) {
103             priv->job_info.wprint_ifc->msgQDelete(priv->msgQ);
104         }
105         sem_destroy(&priv->buffs_sem);
106         free(priv);
107     }
108 }
109 
110 /*
111  * Waits to receive message from the msgQ. Handles messages and sends commands to handle jobs
112  */
_send_thread(void * param)113 static void *_send_thread(void *param) {
114     msgQ_msg_t msg;
115     plugin_data_t *priv = (plugin_data_t *) param;
116 
117     while (priv->job_info.wprint_ifc->msgQReceive(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t),
118             WAIT_FOREVER) == OK) {
119         if (msg.id == MSG_START_JOB) {
120             priv->pcl_ifc->start_job(priv->job_handle, &priv->job_info,
121                     priv->job_params->media_size, priv->job_params->media_type,
122                     priv->job_params->pixel_units, priv->job_params->duplex,
123                     priv->job_params->dry_time, priv->job_params->color_space,
124                     priv->job_params->media_tray, priv->job_params->page_top_margin,
125                     priv->job_params->page_left_margin);
126         } else if (msg.id == MSG_START_PAGE) {
127             priv->pcl_ifc->start_page(&priv->job_info, msg.param.start_page.width,
128                     msg.param.start_page.height);
129         } else if (msg.id == MSG_SEND) {
130             if (!priv->pcl_ifc->canCancelMidPage() || !priv->job_params->cancelled) {
131                 priv->pcl_ifc->print_swath(&priv->job_info, msg.param.send.buffer,
132                         msg.param.send.start_row, msg.param.send.num_rows,
133                         msg.param.send.bytes_per_row);
134             }
135             sem_post(&priv->buffs_sem);
136         } else if (msg.id == MSG_END_PAGE) {
137             int i;
138             priv->pcl_ifc->end_page(&priv->job_info, msg.param.end_page.page);
139             for (i = 0; i < msg.param.end_page.count; i++) {
140                 if (msg.param.end_page.buffers[i] != NULL) {
141                     free(msg.param.end_page.buffers[i]);
142                 }
143             }
144         } else if (msg.id == MSG_END_JOB) {
145             priv->pcl_ifc->end_job(&priv->job_info);
146             break;
147         }
148     }
149     return NULL;
150 }
151 
152 /*
153  * Starts pcl thread
154  */
_start_thread(plugin_data_t * param)155 static status_t _start_thread(plugin_data_t *param) {
156     sigset_t allsig, oldsig;
157     status_t result;
158 
159     if (param == NULL) {
160         return ERROR;
161     }
162 
163     param->send_tid = pthread_self();
164 
165     result = OK;
166     sigfillset(&allsig);
167 #if CHECK_PTHREAD_SIGMASK_STATUS
168     result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
169 #else // CHECK_PTHREAD_SIGMASK_STATUS
170     pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
171 #endif // CHECK_PTHREAD_SIGMASK_STATUS
172     if (result == OK) {
173         result = (status_t) pthread_create(&(param->send_tid), 0, _send_thread, (void *) param);
174         if ((result == ERROR) && (param->send_tid != pthread_self())) {
175 #if USE_PTHREAD_CANCEL
176             pthread_cancel(param->send_tid);
177 #else // else USE_PTHREAD_CANCEL
178             pthread_kill(param->send_tid, SIGKILL);
179 #endif // USE_PTHREAD_CANCEL
180             param->send_tid = pthread_self();
181         }
182     }
183 
184     if (result == OK) {
185         sched_yield();
186 #if CHECK_PTHREAD_SIGMASK_STATUS
187         result = pthread_sigmask(SIG_SETMASK, &oldsig, 0);
188 #else // CHECK_PTHREAD_SIGMASK_STATUS
189         pthread_sigmask(SIG_SETMASK, &oldsig, 0);
190 #endif // CHECK_PTHREAD_SIGMASK_STATUS
191     }
192     return result;
193 }
194 
195 /*
196  * Stops pcl thread
197  */
_stop_thread(plugin_data_t * priv)198 static status_t _stop_thread(plugin_data_t *priv) {
199     status_t result = ERROR;
200     if (priv == NULL) {
201         return result;
202     }
203     if (!pthread_equal(priv->send_tid, pthread_self())) {
204         msgQ_msg_t msg;
205         msg.id = MSG_END_JOB;
206 
207         priv->job_info.wprint_ifc->msgQSend(
208                 priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT, MSG_Q_FIFO);
209         pthread_join(priv->send_tid, 0);
210         priv->send_tid = pthread_self();
211         result = OK;
212     }
213     _cleanup_plugin_data(priv);
214     return result;
215 }
216 
_start_job(wJob_t job_handle,const ifc_wprint_t * wprint_ifc_p,const ifc_print_job_t * print_ifc_p,wprint_job_params_t * job_params)217 static int _start_job(wJob_t job_handle, const ifc_wprint_t *wprint_ifc_p,
218         const ifc_print_job_t *print_ifc_p, wprint_job_params_t *job_params) {
219     msgQ_msg_t msg;
220     plugin_data_t *priv = NULL;
221 
222     do {
223         if (job_params == NULL) continue;
224 
225         job_params->plugin_data = NULL;
226         if ((wprint_ifc_p == NULL) || (print_ifc_p == NULL)) continue;
227 
228         priv = (plugin_data_t *) malloc(sizeof(plugin_data_t));
229         if (priv == NULL) continue;
230 
231         memset(priv, 0, sizeof(plugin_data_t));
232 
233         priv->job_handle = job_handle;
234         priv->job_params = job_params;
235         priv->send_tid = pthread_self();
236         priv->job_info.job_handle = _WJOBH_NONE;
237         priv->job_info.print_ifc = (ifc_print_job_t *) print_ifc_p;
238         priv->job_info.wprint_ifc = (ifc_wprint_t *) wprint_ifc_p;
239         priv->job_info.strip_height = job_params->strip_height;
240         priv->job_info.useragent = job_params->useragent;
241 
242         sem_init(&priv->buffs_sem, 0, MAX_SEND_BUFFS);
243         switch (job_params->pcl_type) {
244             case PCLm:
245                 priv->pcl_ifc = pclm_connect();
246                 break;
247             case PCLPWG:
248                 priv->pcl_ifc = pwg_connect();
249                 break;
250             default:
251                 break;
252         }
253 
254         if (priv->pcl_ifc == NULL) {
255             LOGE("ERROR: cannot start PCL job, no ifc found");
256             continue;
257         }
258 
259         priv->msgQ = priv->job_info.wprint_ifc->msgQCreate(
260                 (MAX_SEND_BUFFS * 2), sizeof(msgQ_msg_t));
261         if (priv->msgQ == MSG_Q_INVALID_ID) continue;
262 
263         if (_start_thread(priv) == ERROR) continue;
264 
265         job_params->plugin_data = (void *) priv;
266         msg.id = MSG_START_JOB;
267         priv->job_info.wprint_ifc->msgQSend(
268                 priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT, MSG_Q_FIFO);
269 
270         return OK;
271     } while (0);
272 
273     _cleanup_plugin_data(priv);
274     return ERROR;
275 }
276 
_setup_image_info(wprint_job_params_t * job_params,wprint_image_info_t * image_info,const char * mime_type,const char * pathname)277 static status_t _setup_image_info(wprint_job_params_t *job_params, wprint_image_info_t *image_info,
278         const char *mime_type, const char *pathname) {
279     FILE *imgfile;
280     status_t result;
281     plugin_data_t *priv;
282 
283     priv = (plugin_data_t *) job_params->plugin_data;
284     if (priv == NULL) return ERROR;
285 
286     if (pathname == NULL) {
287         LOGE("_setup_image_info(): cannot print file with NULL name");
288         return ERROR;
289     }
290 
291     if (!strlen(pathname)) {
292         LOGE("_setup_image_info(): filename was empty");
293         return ERROR;
294     }
295 
296     imgfile = fopen(pathname, "r");
297     if (imgfile == NULL) {
298         LOGE("_setup_image_info(): could not open %s", pathname);
299         return CORRUPT;
300     }
301 
302     LOGD("_setup_image_info(): fopen succeeded on %s", pathname);
303     wprint_image_setup(image_info, mime_type, priv->job_info.wprint_ifc,
304             job_params->pixel_units, job_params->pdf_render_resolution);
305     wprint_image_init(image_info, pathname, job_params->page_num);
306 
307     // get the image_info of the input file of specified MIME type
308     if ((result = wprint_image_get_info(imgfile, image_info)) == OK) {
309         wprint_rotation_t rotation = ROT_0;
310 
311         if ((job_params->render_flags & RENDER_FLAG_PORTRAIT_MODE) != 0) {
312             LOGI("_setup_image_info(): portrait mode");
313             rotation = ROT_0;
314         } else if ((job_params->render_flags & RENDER_FLAG_LANDSCAPE_MODE) != 0) {
315             LOGI("_setup_image_info(): landscape mode");
316             rotation = ROT_90;
317         } else if (wprint_image_is_landscape(image_info) &&
318                 ((job_params->render_flags & RENDER_FLAG_AUTO_ROTATE) != 0)) {
319             LOGI("_setup_image_info(): auto mode");
320             rotation = ROT_90;
321         }
322 
323         if ((job_params->render_flags & RENDER_FLAG_CENTER_ON_ORIENTATION) != 0) {
324             job_params->render_flags &= ~(RENDER_FLAG_CENTER_HORIZONTAL |
325                     RENDER_FLAG_CENTER_VERTICAL);
326             job_params->render_flags |= ((rotation == ROT_0) ? RENDER_FLAG_CENTER_HORIZONTAL
327                     : RENDER_FLAG_CENTER_VERTICAL);
328         }
329 
330         if ((job_params->duplex == DUPLEX_MODE_BOOK) &&
331                 (job_params->page_backside) &&
332                 ((job_params->render_flags & RENDER_FLAG_ROTATE_BACK_PAGE) != 0) &&
333                 ((job_params->render_flags & RENDER_FLAG_BACK_PAGE_PREROTATED) == 0)) {
334             rotation = ((rotation == ROT_0) ? ROT_180 : ROT_270);
335         }
336         LOGI("_setup_image_info(): rotation = %d", rotation);
337 
338         int image_padding = PAD_PRINT;
339         switch (job_params->pcl_type) {
340             case PCLm:
341             case PCLPWG:
342                 image_padding = PAD_ALL;
343                 break;
344             default:
345                 break;
346         }
347 
348         wprint_image_set_output_properties(image_info, rotation,
349                 job_params->printable_area_width, job_params->printable_area_height,
350                 job_params->print_top_margin, job_params->print_left_margin,
351                 job_params->print_right_margin, job_params->print_bottom_margin,
352                 job_params->render_flags, job_params->strip_height, MAX_SEND_BUFFS,
353                 image_padding);
354     } else {
355         LOGE("_setup_image_info(): file does not appear to be valid");
356         result = CORRUPT;
357     }
358     fclose(imgfile);
359     return result;
360 }
361 
_print_page(wprint_job_params_t * job_params,const char * mime_type,const char * pathname)362 static status_t _print_page(wprint_job_params_t *job_params, const char *mime_type,
363         const char *pathname) {
364     wprint_image_info_t *image_info;
365     status_t result;
366     int num_rows, height, image_row;
367     int blank_data;
368     char *buff;
369     int i, buff_index, buff_size;
370     char *buff_pool[MAX_SEND_BUFFS];
371 
372     int nbytes;
373     plugin_data_t *priv;
374     msgQ_msg_t msg;
375 
376     if (job_params == NULL) return ERROR;
377 
378     priv = (plugin_data_t *) job_params->plugin_data;
379 
380     if (priv == NULL) return ERROR;
381 
382     image_info = malloc(sizeof(wprint_image_info_t));
383 
384     if (image_info == NULL) return ERROR;
385 
386     if ((result = _setup_image_info(job_params, image_info, mime_type, pathname)) == OK) {
387         // allocate memory for a stripe of data
388         for (i = 0; i < MAX_SEND_BUFFS; i++) {
389             buff_pool[i] = NULL;
390         }
391 
392         blank_data = MAX_SEND_BUFFS;
393         buff_size = wprint_image_get_output_buff_size(image_info);
394         for (i = 0; i < MAX_SEND_BUFFS; i++) {
395             buff_pool[i] = malloc(buff_size);
396             if (buff_pool[i] == NULL) {
397                 break;
398             }
399             memset(buff_pool[i], 0xff, buff_size);
400         }
401 
402         if (i == MAX_SEND_BUFFS) {
403             msg.id = MSG_START_PAGE;
404             msg.param.start_page.extra_margin = ((job_params->duplex != DUPLEX_MODE_NONE) &&
405                     ((job_params->page_num & 0x1) == 0)) ? job_params->page_bottom_margin : 0.0f;
406             msg.param.start_page.width = wprint_image_get_width(image_info);
407             msg.param.start_page.height = wprint_image_get_height(image_info);
408             priv->job_info.num_components = image_info->num_components;
409             priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t),
410                     NO_WAIT, MSG_Q_FIFO);
411 
412             msg.id = MSG_SEND;
413             msg.param.send.bytes_per_row = BYTES_PER_PIXEL(wprint_image_get_width(image_info));
414 
415             // send blank rows for any offset
416             buff_index = 0;
417             num_rows = wprint_image_get_height(image_info);
418             image_row = 0;
419 
420             // decode and render each stripe into PCL3 raster format
421             while ((result != ERROR) && (num_rows > 0)) {
422                 if (priv->pcl_ifc->canCancelMidPage() && job_params->cancelled) {
423                     break;
424                 }
425                 sem_wait(&priv->buffs_sem);
426 
427                 buff = buff_pool[buff_index];
428                 buff_index = ((buff_index + 1) % MAX_SEND_BUFFS);
429 
430                 height = MIN(num_rows, job_params->strip_height);
431                 if (!job_params->cancelled) {
432                     nbytes = wprint_image_decode_stripe(image_info, image_row, &height,
433                             (unsigned char *) buff);
434 
435                     if (blank_data > 0) {
436                         blank_data--;
437                     }
438                 } else if (blank_data < MAX_SEND_BUFFS) {
439                     nbytes = buff_size;
440                     memset(buff, 0xff, buff_size);
441                     blank_data++;
442                 }
443 
444                 if (nbytes > 0) {
445                     msg.param.send.buffer = buff;
446                     msg.param.send.start_row = image_row;
447                     msg.param.send.num_rows = height;
448 
449                     result = priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg,
450                             sizeof(msgQ_msg_t), NO_WAIT, MSG_Q_FIFO);
451                     if (result < 0) {
452                         sem_post(&priv->buffs_sem);
453                     }
454 
455                     image_row += height;
456                     num_rows -= height;
457                 } else {
458                     sem_post(&priv->buffs_sem);
459                     if (nbytes < 0) {
460                         LOGE("_print_page(): ERROR: file appears to be corrupted");
461                         result = CORRUPT;
462                     }
463                     break;
464                 }
465             }
466 
467             if ((result == OK) && job_params->cancelled) {
468                 result = CANCELLED;
469             }
470 
471             LOGI("_print_page(): sends done, result: %d", result);
472 
473             // free the buffer and eject the page
474             msg.param.end_page.page = job_params->page_num;
475             LOGI("_print_page(): processed %d out of"
476                  " %d rows of page # %d from %s to printer %s %s {%s}",
477                     image_row, wprint_image_get_height(image_info), job_params->page_num, pathname,
478                     (job_params->last_page) ? "- last page" : "- ",
479                     (job_params->cancelled) ? "- job cancelled" : ".",
480                     (result == OK) ? "OK" : "ERROR");
481         } else {
482             msg.param.end_page.page = -1;
483             result = ERROR;
484             LOGE("_print_page(): plugin_pcl cannot allocate memory for image stripe");
485         }
486         for (i = 0; i < MAX_SEND_BUFFS; i++) {
487             msg.param.end_page.buffers[i] = buff_pool[i];
488         }
489         msg.param.end_page.count = MAX_SEND_BUFFS;
490 
491         // send the end page message
492         wprint_image_cleanup(image_info);
493     } else {
494         LOGE("_print_page(): _setup_image_info() is failed");
495         msg.param.end_page.page = -1;
496         msg.param.end_page.count = 0;
497         result = ERROR;
498     }
499     free(image_info);
500 
501     msg.id = MSG_END_PAGE;
502     priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT,
503             MSG_Q_FIFO);
504     return result;
505 }
506 
507 /*
508  * Prints a blank page
509  */
_print_blank_page(wJob_t job_handle,wprint_job_params_t * job_params,const char * mime_type,const char * pathname)510 static int _print_blank_page(wJob_t job_handle, wprint_job_params_t *job_params,
511         const char *mime_type, const char *pathname) {
512     msgQ_msg_t msg;
513     plugin_data_t *priv;
514 
515     if (job_params == NULL) return ERROR;
516 
517     priv = (plugin_data_t *) job_params->plugin_data;
518     if (priv == NULL) return ERROR;
519 
520     if ((!job_params->face_down_tray && job_params->duplex != DUPLEX_MODE_NONE) ||
521             priv->job_info.pixel_width <= 0 || priv->job_info.pixel_height <= 0) {
522         // in this case, the page size for blank page has not been decided yet
523         // so we need to calculate it
524         wprint_image_info_t *image_info = malloc(sizeof(wprint_image_info_t));
525         if (image_info == NULL) return ERROR;
526 
527         if (_setup_image_info(job_params, image_info, mime_type, pathname) == OK) {
528             priv->job_info.pixel_width = wprint_image_get_width(image_info);
529             priv->job_info.pixel_height = wprint_image_get_height(image_info);
530             priv->job_info.num_components = image_info->num_components;
531             wprint_image_cleanup(image_info);
532         }
533         free(image_info);
534     }
535 
536     msg.id = MSG_END_PAGE;
537     msg.param.end_page.page = -1;
538     msg.param.end_page.count = 0;
539     priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT,
540             MSG_Q_FIFO);
541     return OK;
542 }
543 
_end_job(wprint_job_params_t * job_params)544 static int _end_job(wprint_job_params_t *job_params) {
545     if (job_params != NULL) {
546         _stop_thread((plugin_data_t *) job_params->plugin_data);
547     }
548     return OK;
549 }
550 
libwprintplugin_pcl_reg(void)551 wprint_plugin_t *libwprintplugin_pcl_reg(void) {
552     static const wprint_plugin_t _pcl_plugin = {.version = WPRINT_PLUGIN_VERSION(0),
553             .priority = PRIORITY_LOCAL, .get_mime_types = _get_mime_types,
554             .get_print_formats = _get_print_formats, .start_job = _start_job,
555             .print_page = _print_page, .print_blank_page = _print_blank_page, .end_job = _end_job,};
556     return ((wprint_plugin_t *) &_pcl_plugin);
557 }