| /* | |
| * Copyright (C) 2016 The Android Open Source Project | |
| * Copyright (C) 2016 Mopria Alliance, Inc. | |
| * Copyright (C) 2013 Hewlett-Packard Development Company, L.P. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include "ifc_print_job.h" | |
| #include "lib_pcl.h" | |
| #include "wprint_image.h" | |
| #ifndef _GNU_SOURCE | |
| #define _GNU_SOURCE | |
| #endif | |
| #ifndef __USE_UNIX98 | |
| #define __USE_UNIX98 | |
| #endif | |
| #include <pthread.h> | |
| #include <semaphore.h> | |
| #include <string.h> | |
| #define MAX_SEND_BUFFS (BUFFERED_ROWS / STRIPE_HEIGHT) | |
| #define TAG "plugin_pcl" | |
| typedef enum { | |
| MSG_START_JOB, | |
| MSG_START_PAGE, | |
| MSG_SEND, | |
| MSG_END_JOB, | |
| MSG_END_PAGE, | |
| } msg_id_t; | |
| typedef struct { | |
| msg_id_t id; | |
| union { | |
| struct { | |
| float extra_margin; | |
| int width; | |
| int height; | |
| } start_page; | |
| struct { | |
| char *buffer; | |
| int start_row; | |
| int num_rows; | |
| int bytes_per_row; | |
| } send; | |
| struct { | |
| int page; | |
| char *buffers[MAX_SEND_BUFFS]; | |
| int count; | |
| } end_page; | |
| } param; | |
| } msgQ_msg_t; | |
| typedef struct { | |
| wJob_t job_handle; | |
| msg_q_id msgQ; | |
| pthread_t send_tid; | |
| pcl_job_info_t job_info; | |
| wprint_job_params_t *job_params; | |
| sem_t buffs_sem; | |
| ifc_pcl_t *pcl_ifc; | |
| } plugin_data_t; | |
| static const char *_mime_types[] = { | |
| MIME_TYPE_PDF, | |
| NULL}; | |
| static const char *_print_formats[] = { | |
| PRINT_FORMAT_PCLM, | |
| PRINT_FORMAT_PWG, | |
| PRINT_FORMAT_PDF, | |
| NULL}; | |
| static const char **_get_mime_types(void) { | |
| return _mime_types; | |
| } | |
| static const char **_get_print_formats(void) { | |
| return _print_formats; | |
| } | |
| static void _cleanup_plugin_data(plugin_data_t *priv) { | |
| if (priv != NULL) { | |
| if (priv->msgQ != MSG_Q_INVALID_ID) { | |
| priv->job_info.wprint_ifc->msgQDelete(priv->msgQ); | |
| } | |
| sem_destroy(&priv->buffs_sem); | |
| free(priv); | |
| } | |
| } | |
| /* | |
| * Waits to receive message from the msgQ. Handles messages and sends commands to handle jobs | |
| */ | |
| static void *_send_thread(void *param) { | |
| msgQ_msg_t msg; | |
| plugin_data_t *priv = (plugin_data_t *) param; | |
| while (priv->job_info.wprint_ifc->msgQReceive(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), | |
| WAIT_FOREVER) == OK) { | |
| if (msg.id == MSG_START_JOB) { | |
| priv->pcl_ifc->start_job(priv->job_handle, &priv->job_info, | |
| priv->job_params->media_size, priv->job_params->media_type, | |
| priv->job_params->pixel_units, priv->job_params->duplex, | |
| priv->job_params->dry_time, priv->job_params->color_space, | |
| priv->job_params->media_tray, priv->job_params->page_top_margin, | |
| priv->job_params->page_left_margin); | |
| } else if (msg.id == MSG_START_PAGE) { | |
| priv->pcl_ifc->start_page(&priv->job_info, msg.param.start_page.width, | |
| msg.param.start_page.height); | |
| } else if (msg.id == MSG_SEND) { | |
| if (!priv->pcl_ifc->canCancelMidPage() || !priv->job_params->cancelled) { | |
| priv->pcl_ifc->print_swath(&priv->job_info, msg.param.send.buffer, | |
| msg.param.send.start_row, msg.param.send.num_rows, | |
| msg.param.send.bytes_per_row); | |
| } | |
| sem_post(&priv->buffs_sem); | |
| } else if (msg.id == MSG_END_PAGE) { | |
| int i; | |
| priv->pcl_ifc->end_page(&priv->job_info, msg.param.end_page.page); | |
| for (i = 0; i < msg.param.end_page.count; i++) { | |
| if (msg.param.end_page.buffers[i] != NULL) { | |
| free(msg.param.end_page.buffers[i]); | |
| } | |
| } | |
| } else if (msg.id == MSG_END_JOB) { | |
| priv->pcl_ifc->end_job(&priv->job_info); | |
| break; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* | |
| * Starts pcl thread | |
| */ | |
| static status_t _start_thread(plugin_data_t *param) { | |
| sigset_t allsig, oldsig; | |
| status_t result; | |
| if (param == NULL) { | |
| return ERROR; | |
| } | |
| param->send_tid = pthread_self(); | |
| result = OK; | |
| sigfillset(&allsig); | |
| #if CHECK_PTHREAD_SIGMASK_STATUS | |
| result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig); | |
| #else // CHECK_PTHREAD_SIGMASK_STATUS | |
| pthread_sigmask(SIG_SETMASK, &allsig, &oldsig); | |
| #endif // CHECK_PTHREAD_SIGMASK_STATUS | |
| if (result == OK) { | |
| result = (status_t) pthread_create(&(param->send_tid), 0, _send_thread, (void *) param); | |
| if ((result == ERROR) && (param->send_tid != pthread_self())) { | |
| #if USE_PTHREAD_CANCEL | |
| pthread_cancel(param->send_tid); | |
| #else // else USE_PTHREAD_CANCEL | |
| pthread_kill(param->send_tid, SIGKILL); | |
| #endif // USE_PTHREAD_CANCEL | |
| param->send_tid = pthread_self(); | |
| } | |
| } | |
| if (result == OK) { | |
| sched_yield(); | |
| #if CHECK_PTHREAD_SIGMASK_STATUS | |
| result = pthread_sigmask(SIG_SETMASK, &oldsig, 0); | |
| #else // CHECK_PTHREAD_SIGMASK_STATUS | |
| pthread_sigmask(SIG_SETMASK, &oldsig, 0); | |
| #endif // CHECK_PTHREAD_SIGMASK_STATUS | |
| } | |
| return result; | |
| } | |
| /* | |
| * Stops pcl thread | |
| */ | |
| static status_t _stop_thread(plugin_data_t *priv) { | |
| status_t result = ERROR; | |
| if (priv == NULL) { | |
| return result; | |
| } | |
| if (!pthread_equal(priv->send_tid, pthread_self())) { | |
| msgQ_msg_t msg; | |
| msg.id = MSG_END_JOB; | |
| priv->job_info.wprint_ifc->msgQSend( | |
| priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT, MSG_Q_FIFO); | |
| pthread_join(priv->send_tid, 0); | |
| priv->send_tid = pthread_self(); | |
| result = OK; | |
| } | |
| _cleanup_plugin_data(priv); | |
| return result; | |
| } | |
| static int _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) { | |
| msgQ_msg_t msg; | |
| plugin_data_t *priv = NULL; | |
| do { | |
| if (job_params == NULL) continue; | |
| job_params->plugin_data = NULL; | |
| if ((wprint_ifc_p == NULL) || (print_ifc_p == NULL)) continue; | |
| priv = (plugin_data_t *) malloc(sizeof(plugin_data_t)); | |
| if (priv == NULL) continue; | |
| memset(priv, 0, sizeof(plugin_data_t)); | |
| priv->job_handle = job_handle; | |
| priv->job_params = job_params; | |
| priv->send_tid = pthread_self(); | |
| priv->job_info.job_handle = _WJOBH_NONE; | |
| priv->job_info.print_ifc = (ifc_print_job_t *) print_ifc_p; | |
| priv->job_info.wprint_ifc = (ifc_wprint_t *) wprint_ifc_p; | |
| priv->job_info.strip_height = job_params->strip_height; | |
| priv->job_info.useragent = job_params->useragent; | |
| sem_init(&priv->buffs_sem, 0, MAX_SEND_BUFFS); | |
| switch (job_params->pcl_type) { | |
| case PCLm: | |
| priv->pcl_ifc = pclm_connect(); | |
| break; | |
| case PCLPWG: | |
| priv->pcl_ifc = pwg_connect(); | |
| break; | |
| default: | |
| break; | |
| } | |
| if (priv->pcl_ifc == NULL) { | |
| LOGE("ERROR: cannot start PCL job, no ifc found"); | |
| continue; | |
| } | |
| priv->msgQ = priv->job_info.wprint_ifc->msgQCreate( | |
| (MAX_SEND_BUFFS * 2), sizeof(msgQ_msg_t)); | |
| if (priv->msgQ == MSG_Q_INVALID_ID) continue; | |
| if (_start_thread(priv) == ERROR) continue; | |
| job_params->plugin_data = (void *) priv; | |
| msg.id = MSG_START_JOB; | |
| priv->job_info.wprint_ifc->msgQSend( | |
| priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT, MSG_Q_FIFO); | |
| return OK; | |
| } while (0); | |
| _cleanup_plugin_data(priv); | |
| return ERROR; | |
| } | |
| static status_t _setup_image_info(wprint_job_params_t *job_params, wprint_image_info_t *image_info, | |
| const char *mime_type, const char *pathname) { | |
| FILE *imgfile; | |
| status_t result; | |
| plugin_data_t *priv; | |
| priv = (plugin_data_t *) job_params->plugin_data; | |
| if (priv == NULL) return ERROR; | |
| if (pathname == NULL) { | |
| LOGE("_setup_image_info(): cannot print file with NULL name"); | |
| return ERROR; | |
| } | |
| if (!strlen(pathname)) { | |
| LOGE("_setup_image_info(): filename was empty"); | |
| return ERROR; | |
| } | |
| imgfile = fopen(pathname, "r"); | |
| if (imgfile == NULL) { | |
| LOGE("_setup_image_info(): could not open %s", pathname); | |
| return CORRUPT; | |
| } | |
| LOGD("_setup_image_info(): fopen succeeded on %s", pathname); | |
| wprint_image_setup(image_info, mime_type, priv->job_info.wprint_ifc, | |
| job_params->pixel_units, job_params->pdf_render_resolution); | |
| wprint_image_init(image_info, pathname, job_params->page_num); | |
| // get the image_info of the input file of specified MIME type | |
| if ((result = wprint_image_get_info(imgfile, image_info)) == OK) { | |
| wprint_rotation_t rotation = ROT_0; | |
| if ((job_params->render_flags & RENDER_FLAG_PORTRAIT_MODE) != 0) { | |
| LOGI("_setup_image_info(): portrait mode"); | |
| rotation = ROT_0; | |
| } else if ((job_params->render_flags & RENDER_FLAG_LANDSCAPE_MODE) != 0) { | |
| LOGI("_setup_image_info(): landscape mode"); | |
| rotation = ROT_90; | |
| } else if (wprint_image_is_landscape(image_info) && | |
| ((job_params->render_flags & RENDER_FLAG_AUTO_ROTATE) != 0)) { | |
| LOGI("_setup_image_info(): auto mode"); | |
| rotation = ROT_90; | |
| } | |
| if ((job_params->render_flags & RENDER_FLAG_CENTER_ON_ORIENTATION) != 0) { | |
| job_params->render_flags &= ~(RENDER_FLAG_CENTER_HORIZONTAL | | |
| RENDER_FLAG_CENTER_VERTICAL); | |
| job_params->render_flags |= ((rotation == ROT_0) ? RENDER_FLAG_CENTER_HORIZONTAL | |
| : RENDER_FLAG_CENTER_VERTICAL); | |
| } | |
| if ((job_params->duplex == DUPLEX_MODE_BOOK) && | |
| (job_params->page_backside) && | |
| ((job_params->render_flags & RENDER_FLAG_ROTATE_BACK_PAGE) != 0) && | |
| ((job_params->render_flags & RENDER_FLAG_BACK_PAGE_PREROTATED) == 0)) { | |
| rotation = ((rotation == ROT_0) ? ROT_180 : ROT_270); | |
| } | |
| LOGI("_setup_image_info(): rotation = %d", rotation); | |
| int image_padding = PAD_PRINT; | |
| switch (job_params->pcl_type) { | |
| case PCLm: | |
| case PCLPWG: | |
| image_padding = PAD_ALL; | |
| break; | |
| default: | |
| break; | |
| } | |
| wprint_image_set_output_properties(image_info, rotation, | |
| job_params->printable_area_width, job_params->printable_area_height, | |
| job_params->print_top_margin, job_params->print_left_margin, | |
| job_params->print_right_margin, job_params->print_bottom_margin, | |
| job_params->render_flags, job_params->strip_height, MAX_SEND_BUFFS, | |
| image_padding); | |
| } else { | |
| LOGE("_setup_image_info(): file does not appear to be valid"); | |
| result = CORRUPT; | |
| } | |
| fclose(imgfile); | |
| return result; | |
| } | |
| static status_t _print_page(wprint_job_params_t *job_params, const char *mime_type, | |
| const char *pathname) { | |
| wprint_image_info_t *image_info; | |
| status_t result; | |
| int num_rows, height, image_row; | |
| int blank_data; | |
| char *buff; | |
| int i, buff_index, buff_size; | |
| char *buff_pool[MAX_SEND_BUFFS]; | |
| int nbytes; | |
| plugin_data_t *priv; | |
| msgQ_msg_t msg; | |
| if (job_params == NULL) return ERROR; | |
| priv = (plugin_data_t *) job_params->plugin_data; | |
| if (priv == NULL) return ERROR; | |
| image_info = malloc(sizeof(wprint_image_info_t)); | |
| if (image_info == NULL) return ERROR; | |
| if ((result = _setup_image_info(job_params, image_info, mime_type, pathname)) == OK) { | |
| // allocate memory for a stripe of data | |
| for (i = 0; i < MAX_SEND_BUFFS; i++) { | |
| buff_pool[i] = NULL; | |
| } | |
| blank_data = MAX_SEND_BUFFS; | |
| buff_size = wprint_image_get_output_buff_size(image_info); | |
| for (i = 0; i < MAX_SEND_BUFFS; i++) { | |
| buff_pool[i] = malloc(buff_size); | |
| if (buff_pool[i] == NULL) { | |
| break; | |
| } | |
| memset(buff_pool[i], 0xff, buff_size); | |
| } | |
| if (i == MAX_SEND_BUFFS) { | |
| msg.id = MSG_START_PAGE; | |
| msg.param.start_page.extra_margin = ((job_params->duplex != DUPLEX_MODE_NONE) && | |
| ((job_params->page_num & 0x1) == 0)) ? job_params->page_bottom_margin : 0.0f; | |
| msg.param.start_page.width = wprint_image_get_width(image_info); | |
| msg.param.start_page.height = wprint_image_get_height(image_info); | |
| priv->job_info.num_components = image_info->num_components; | |
| priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), | |
| NO_WAIT, MSG_Q_FIFO); | |
| msg.id = MSG_SEND; | |
| msg.param.send.bytes_per_row = BYTES_PER_PIXEL(wprint_image_get_width(image_info)); | |
| // send blank rows for any offset | |
| buff_index = 0; | |
| num_rows = wprint_image_get_height(image_info); | |
| image_row = 0; | |
| // decode and render each stripe into PCL3 raster format | |
| while ((result != ERROR) && (num_rows > 0)) { | |
| if (priv->pcl_ifc->canCancelMidPage() && job_params->cancelled) { | |
| break; | |
| } | |
| sem_wait(&priv->buffs_sem); | |
| buff = buff_pool[buff_index]; | |
| buff_index = ((buff_index + 1) % MAX_SEND_BUFFS); | |
| height = MIN(num_rows, job_params->strip_height); | |
| if (!job_params->cancelled) { | |
| nbytes = wprint_image_decode_stripe(image_info, image_row, &height, | |
| (unsigned char *) buff); | |
| if (blank_data > 0) { | |
| blank_data--; | |
| } | |
| } else if (blank_data < MAX_SEND_BUFFS) { | |
| nbytes = buff_size; | |
| memset(buff, 0xff, buff_size); | |
| blank_data++; | |
| } | |
| if (nbytes > 0) { | |
| msg.param.send.buffer = buff; | |
| msg.param.send.start_row = image_row; | |
| msg.param.send.num_rows = height; | |
| result = priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, | |
| sizeof(msgQ_msg_t), NO_WAIT, MSG_Q_FIFO); | |
| if (result < 0) { | |
| sem_post(&priv->buffs_sem); | |
| } | |
| image_row += height; | |
| num_rows -= height; | |
| } else { | |
| sem_post(&priv->buffs_sem); | |
| if (nbytes < 0) { | |
| LOGE("_print_page(): ERROR: file appears to be corrupted"); | |
| result = CORRUPT; | |
| } | |
| break; | |
| } | |
| } | |
| if ((result == OK) && job_params->cancelled) { | |
| result = CANCELLED; | |
| } | |
| LOGI("_print_page(): sends done, result: %d", result); | |
| // free the buffer and eject the page | |
| msg.param.end_page.page = job_params->page_num; | |
| LOGI("_print_page(): processed %d out of" | |
| " %d rows of page # %d from %s to printer %s %s {%s}", | |
| image_row, wprint_image_get_height(image_info), job_params->page_num, pathname, | |
| (job_params->last_page) ? "- last page" : "- ", | |
| (job_params->cancelled) ? "- job cancelled" : ".", | |
| (result == OK) ? "OK" : "ERROR"); | |
| } else { | |
| msg.param.end_page.page = -1; | |
| result = ERROR; | |
| LOGE("_print_page(): plugin_pcl cannot allocate memory for image stripe"); | |
| } | |
| for (i = 0; i < MAX_SEND_BUFFS; i++) { | |
| msg.param.end_page.buffers[i] = buff_pool[i]; | |
| } | |
| msg.param.end_page.count = MAX_SEND_BUFFS; | |
| // send the end page message | |
| wprint_image_cleanup(image_info); | |
| } else { | |
| LOGE("_print_page(): _setup_image_info() is failed"); | |
| msg.param.end_page.page = -1; | |
| msg.param.end_page.count = 0; | |
| result = ERROR; | |
| } | |
| free(image_info); | |
| msg.id = MSG_END_PAGE; | |
| priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT, | |
| MSG_Q_FIFO); | |
| return result; | |
| } | |
| /* | |
| * Prints a blank page | |
| */ | |
| static int _print_blank_page(wJob_t job_handle, wprint_job_params_t *job_params, | |
| const char *mime_type, const char *pathname) { | |
| msgQ_msg_t msg; | |
| plugin_data_t *priv; | |
| if (job_params == NULL) return ERROR; | |
| priv = (plugin_data_t *) job_params->plugin_data; | |
| if (priv == NULL) return ERROR; | |
| if ((!job_params->face_down_tray && job_params->duplex != DUPLEX_MODE_NONE) || | |
| priv->job_info.pixel_width <= 0 || priv->job_info.pixel_height <= 0) { | |
| // in this case, the page size for blank page has not been decided yet | |
| // so we need to calculate it | |
| wprint_image_info_t *image_info = malloc(sizeof(wprint_image_info_t)); | |
| if (image_info == NULL) return ERROR; | |
| if (_setup_image_info(job_params, image_info, mime_type, pathname) == OK) { | |
| priv->job_info.pixel_width = wprint_image_get_width(image_info); | |
| priv->job_info.pixel_height = wprint_image_get_height(image_info); | |
| priv->job_info.num_components = image_info->num_components; | |
| wprint_image_cleanup(image_info); | |
| } | |
| free(image_info); | |
| } | |
| msg.id = MSG_END_PAGE; | |
| msg.param.end_page.page = -1; | |
| msg.param.end_page.count = 0; | |
| priv->job_info.wprint_ifc->msgQSend(priv->msgQ, (char *) &msg, sizeof(msgQ_msg_t), NO_WAIT, | |
| MSG_Q_FIFO); | |
| return OK; | |
| } | |
| static int _end_job(wprint_job_params_t *job_params) { | |
| if (job_params != NULL) { | |
| _stop_thread((plugin_data_t *) job_params->plugin_data); | |
| } | |
| return OK; | |
| } | |
| wprint_plugin_t *libwprintplugin_pcl_reg(void) { | |
| static const wprint_plugin_t _pcl_plugin = {.version = WPRINT_PLUGIN_VERSION(0), | |
| .priority = PRIORITY_LOCAL, .get_mime_types = _get_mime_types, | |
| .get_print_formats = _get_print_formats, .start_job = _start_job, | |
| .print_page = _print_page, .print_blank_page = _print_blank_page, .end_job = _end_job,}; | |
| return ((wprint_plugin_t *) &_pcl_plugin); | |
| } |