/*
* 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>
#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 _print_page(wprint_job_params_t *job_params, const char *mime_type,
const char *pathname) {
wprint_image_info_t *image_info;
FILE *imgfile;
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;
int image_padding = PAD_PRINT;
if (job_params == NULL) return ERROR;
priv = (plugin_data_t *) job_params->plugin_data;
if (priv == NULL) return ERROR;
switch (job_params->pcl_type) {
case PCLm:
case PCLPWG:
image_padding = PAD_ALL;
break;
default:
break;
}
if (pathname == NULL) {
LOGE("_print_page(): cannot print file with NULL name");
msg.param.end_page.page = -1;
msg.param.end_page.count = 0;
result = ERROR;
} else if (strlen(pathname)) {
image_info = malloc(sizeof(wprint_image_info_t));
if (image_info == NULL) return ERROR;
imgfile = fopen(pathname, "r");
if (imgfile) {
LOGD("_print_page(): 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("_print_page(): portrait mode");
rotation = ROT_0;
} else if ((job_params->render_flags & RENDER_FLAG_LANDSCAPE_MODE) != 0) {
LOGI("_print_page(): landscape mode");
rotation = ROT_90;
} else if (wprint_image_is_landscape(image_info) &&
((job_params->render_flags & RENDER_FLAG_AUTO_ROTATE) != 0)) {
LOGI("_print_page(): 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("_print_page(): rotation = %d", rotation);
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);
// 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;
} else {
msg.param.end_page.page = -1;
msg.param.end_page.count = 0;
result = CORRUPT;
LOGE("_print_page(): file does not appear to be valid");
}
// send the end page message
wprint_image_cleanup(image_info);
fclose(imgfile);
} else {
msg.param.end_page.page = -1;
msg.param.end_page.count = 0;
LOGE("_print_page(): could not open %s", pathname);
result = CORRUPT;
}
free(image_info);
} else {
LOGE("_print_page(): ERROR: filename was empty");
msg.param.end_page.page = -1;
msg.param.end_page.count = 0;
result = ERROR;
}
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) {
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;
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);
}