diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/Makefile.am | 2 | ||||
-rw-r--r-- | frontend/jpegtopdf.c | 709 | ||||
-rw-r--r-- | frontend/jpegtopdf.h | 77 | ||||
-rw-r--r-- | frontend/scanimage.c | 198 |
4 files changed, 949 insertions, 37 deletions
diff --git a/frontend/Makefile.am b/frontend/Makefile.am index 47c50f3..9b92645 100644 --- a/frontend/Makefile.am +++ b/frontend/Makefile.am @@ -16,7 +16,7 @@ endif AM_CPPFLAGS += -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include -scanimage_SOURCES = scanimage.c sicc.c sicc.h stiff.c stiff.h +scanimage_SOURCES = scanimage.c jpegtopdf.c jpegtopdf.h sicc.c sicc.h stiff.c stiff.h scanimage_LDADD = ../backend/libsane.la ../sanei/libsanei.la ../lib/liblib.la \ $(PNG_LIBS) $(JPEG_LIBS) diff --git a/frontend/jpegtopdf.c b/frontend/jpegtopdf.c new file mode 100644 index 0000000..8f144b5 --- /dev/null +++ b/frontend/jpegtopdf.c @@ -0,0 +1,709 @@ +/* scanimage -- command line scanning utility + * Uses the SANE library. + * + * Copyright (C) 2021 Thierry HUCHARD <thierry@ordissimo.com> + * + * For questions and comments contact the sane-devel mailinglist (see + * http://www.sane-project.org/mailing-lists.html). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include "jpegtopdf.h" + +#ifndef PATH_MAX +# define PATH_MAX 4096 +#endif + +#define SANE_NO_ERR (0) +#define SANE_NO_ERR_CANCLED (1) + +#define SANE_ERR (-128) +#define SANE_FILE_ERR (-1) + + +/* Creater/Producer */ +#define SANE_PDF_CREATER "sane" +#define SANE_PDF_PRODUCER "sane" + +/* PDF File Header */ +#define SANE_PDF_HEADER "%%PDF-1.3\n" + +/* trailer format */ +#define SANE_PDF_TRAILER_OBJ "trailer\n<<\n/Size %d\n/Root 1 0 R\n/Info 3 0 R\n>>\nstartxref\n%lld\n%%%%EOF\n" + +/* xref format */ +#define SANE_PDF_XREF_OBJ1 "xref\n0 %d\n0000000000 65535 f \n" +#define SANE_PDF_XREF_OBJ2 "%010lld 00000 n \n" + +/* Catalog format */ +#define SANE_PDF_CATALOG_OBJ "1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\n" + +/* Pages format */ +#define SANE_PDF_PAGES_OBJ1 "2 0 obj\n<<\n/Type /Pages\n/Kids [ " +#define SANE_PDF_PAGES_OBJ2 "%d 0 R " +#define SANE_PDF_PAGES_OBJ3 "]\n/Count %d\n>>\nendobj\n" + +/* Info format */ +#define SANE_PDF_INFO_OBJ "3 0 obj\n<<\n/Creator (" SANE_PDF_CREATER ")\n/Producer (" SANE_PDF_PRODUCER ")\n/CreationDate %s\n>>\nendobj\n" +#define SANE_PDF_INFO_DATES "(D:%4d%02d%02d%02d%02d%02d%c%02d'%02d')" + +/* Page format */ +#define SANE_PDF_PAGE_OBJ1 "%d 0 obj\n<<\n/Type /Page\n/Parent 2 0 R\n" +#define SANE_PDF_PAGE_OBJ2 "/Resources\n<<\n/XObject << /Im%d %d 0 R >>\n/ProcSet [ /PDF /%s ]\n>>\n" +#define SANE_PDF_PAGE_OBJ3 "/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\nendobj\n" +#define SANE_PDF_PAGE_OBJ3_180 "/Rotate 180\n/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\nendobj\n" +#define SANE_PDF_PAGE_OBJ SANE_PDF_PAGE_OBJ1 SANE_PDF_PAGE_OBJ2 SANE_PDF_PAGE_OBJ3 +#define SANE_PDF_PAGE_OBJ_180 SANE_PDF_PAGE_OBJ1 SANE_PDF_PAGE_OBJ2 SANE_PDF_PAGE_OBJ3_180 + +/* Contents format */ +#define SANE_PDF_CONTENTS_OBJ1 "%d 0 obj\n<< /Length %d 0 R >>\nstream\n" +#define SANE_PDF_CONTENTS_OBJ2 "q\n%d 0 0 %d 0 0 cm\n/Im%d Do\nQ\n" + +/* XObject(Image) format */ +#define SANE_PDF_IMAGE_OBJ1 "%d 0 obj\n<<\n/Length %d 0 R\n/Type /XObject\n/Subtype /Image\n" +#define SANE_PDF_IMAGE_OBJ2 "/Width %d /Height %d\n/ColorSpace /%s\n/BitsPerComponent %d\n" +#define SANE_PDF_IMAGE_OBJ3 "/Filter /DCTDecode\n>>\nstream\n" +#define SANE_PDF_IMAGE_OBJ SANE_PDF_IMAGE_OBJ1 SANE_PDF_IMAGE_OBJ2 SANE_PDF_IMAGE_OBJ3 + +/* Length format */ +#define SANE_PDF_LENGTH_OBJ "%d 0 obj\n%d\nendobj\n" + +/* end of stream/object */ +#define SANE_PDF_END_ST_OBJ "endstream\nendobj\n" + + +/* object id of first page */ +#define SANE_PDF_FIRST_PAGE_ID (4) + +/* xref max value */ +#define SANE_PDF_XREF_MAX (9999999999LL) + +/* pdfwork->offset_table */ +enum { + SANE_PDF_ENDDOC_XREF = 0, + SANE_PDF_ENDDOC_CATALOG, + SANE_PDF_ENDDOC_PAGES, + SANE_PDF_ENDDOC_INFO, + SANE_PDF_ENDDOC_NUM, +}; + +/* pdfpage->offset_table */ +enum { + SANE_PDF_PAGE_OBJ_PAGE = 0, + SANE_PDF_PAGE_OBJ_IMAGE, + SANE_PDF_PAGE_OBJ_IMAGE_LEN, + SANE_PDF_PAGE_OBJ_CONTENTS, + SANE_PDF_PAGE_OBJ_CONTENTS_LEN, + SANE_PDF_PAGE_OBJ_NUM, +}; + +/* Page object info */ +typedef struct sane_pdf_page { + SANE_Int page; /* page No. */ + SANE_Int obj_id; /* Page object id */ + SANE_Int image_type; /* ColorSpace, BitsPerComponent */ + SANE_Int res; /* image resolution */ + SANE_Int w; /* width (image res) */ + SANE_Int h; /* height (image res) */ + SANE_Int w_72; /* width (72dpi) */ + SANE_Int h_72; /* height (72dpi) */ + SANE_Int64 offset_table[SANE_PDF_PAGE_OBJ_NUM]; /* xref table */ + SANE_Int stream_len; /* stream object length */ + SANE_Int status; /* page object status */ + struct sane_pdf_page *prev; /* previous page data */ + struct sane_pdf_page *next; /* next page data */ +} SANE_pdf_page; + + +/* PDF Work */ +typedef struct { + SANE_Int obj_num; /* xref - num, trailer - Size */ + SANE_Int page_num; /* Pages - Count */ + SANE_Int64 offset_table[SANE_PDF_ENDDOC_NUM]; /* xref table */ + SANE_pdf_page *first; /* first page data */ + SANE_pdf_page *last; /* last page data */ + FILE* fd; /* destination file */ +} SANE_pdf_work; + +static SANE_Int re_write_if_fail( + FILE * fd, + void * lpSrc, + SANE_Int writeSize ) +{ + SANE_Int ret = SANE_ERR, ldata_1st, ldata_2nd; + + if( ( fd == NULL ) || ( lpSrc == NULL ) || ( writeSize <= 0 ) ) { + fprintf ( stderr, "[re_write_if_fail]Parameter is error.\n" ); + goto EXIT; + } + else if( ( ldata_1st = fwrite( (SANE_Byte *)lpSrc, 1, writeSize, fd ) ) != writeSize ){ + fprintf ( stderr, "[re_write_if_fail]Can't write file(1st request:%d -> write:%d).\n", writeSize, ldata_1st ); + if( ( ldata_2nd = fwrite( (SANE_Byte*)lpSrc+ldata_1st, 1, writeSize-ldata_1st, fd) ) != writeSize-ldata_1st ){ /* For detect write() error */ + fprintf ( stderr, "[re_write_if_fail]Can't write file(2nd request:%d -> write:%d).\n", writeSize-ldata_1st, ldata_2nd ); + goto EXIT; + } + } + ret = SANE_NO_ERR; +EXIT: + return ret; +} + +static SANE_Int64 _get_current_offset( FILE *fd ) +{ + SANE_Int64 offset64 = (SANE_Int64)fseek( fd, 0, SEEK_CUR ); + + if ( offset64 > SANE_PDF_XREF_MAX ) offset64 = -1; + + return offset64; +} + +static SANE_Int _get_current_time( struct tm *pt, SANE_Byte *sign_c, int *ptz_h, int *ptz_m ) +{ + SANE_Int ret = SANE_ERR; + time_t t; + long tz; + + if ( pt == NULL || sign_c == NULL || ptz_h == NULL || ptz_m == NULL ) { + goto EXIT; + } + + memset ((void *)pt, 0, sizeof(struct tm) ); + /* get time */ + if( ( t = time( NULL ) ) < 0 ) { + fprintf ( stderr, " Can't get time.\n" ); + goto EXIT; + } + /* get localtime */ + if ( localtime_r( &t, pt ) == NULL ) { + fprintf ( stderr, " Can't get localtime.\n" ); + goto EXIT; + } + /* get time difference ( OHH'mm' ) */ + tz = timezone; + if ( tz > 0 ) { + *sign_c = '-'; + } + else { + tz = -tz; + *sign_c = '+'; + } + *ptz_h = tz / 60 / 60; + *ptz_m = ( tz / 60 ) % 60; + + ret = SANE_NO_ERR; +EXIT: + return ret; +} + +SANE_Int sane_pdf_open( void **ppw, FILE *fd ) +{ + SANE_Int ret = SANE_ERR; + SANE_pdf_work *p = NULL; + + if ( fd == NULL ) { + fprintf ( stderr, " Initialize parameter is error!\n" ); + goto EXIT; + } + else if ( ( p = (SANE_pdf_work *)calloc(1, sizeof(SANE_pdf_page) ) ) == NULL ) { + fprintf ( stderr, " Can't get work memory!\n" ); + goto EXIT; + } + + p->fd = fd; + p->obj_num = SANE_PDF_FIRST_PAGE_ID - 1; /* Catalog, Pages, Info */ + p->page_num = 0; + p->first = NULL; + p->last = NULL; + + *ppw = (void *)p; + + ret = SANE_NO_ERR; +EXIT: + return ret; +} + +void sane_pdf_close( void *pw ) +{ + SANE_pdf_page *cur, *next; + SANE_pdf_work *pwork = (SANE_pdf_work *)pw; + + if ( pwork == NULL ) { + fprintf ( stderr, " Initialize parameter is error!\n"); + goto EXIT; + } + + cur = pwork->first; + while ( cur != NULL ) { + next = cur->next; + free( (void *)cur ); + cur = next; + } + + free ( (void *)pwork ); + +EXIT: + return ; +} + +SANE_Int sane_pdf_start_doc( void *pw ) +{ + SANE_Int ret = SANE_ERR, ldata; + SANE_Byte str[32]; + SANE_Int len; + SANE_pdf_work *pwork = (SANE_pdf_work *)pw; + + if ( pwork == NULL ) { + fprintf ( stderr, " Initialize parameter is error!\n"); + goto EXIT; + } + + len = snprintf( (char*)str, sizeof(str), SANE_PDF_HEADER ); + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + ret = SANE_NO_ERR; +EXIT: + return ret; +} + +SANE_Int sane_pdf_end_doc( void *pw ) +{ + SANE_Int ret = SANE_ERR, ldata, i, size, w_count; + SANE_pdf_page *p = NULL; + SANE_Byte str[1024], str_t[64]; + SANE_Int len; + SANE_pdf_work *pwork = (SANE_pdf_work *)pw; + + struct tm tm; + SANE_Byte sign_c; + int tz_h = 0, tz_m = 0; + + if ( pwork == NULL ) { + fprintf ( stderr, " Initialize parameter is error!\n"); + goto EXIT; + } + + size = pwork->obj_num + 1; + w_count = 1; + + /* <1> Pages */ + if ( ( pwork->offset_table[ SANE_PDF_ENDDOC_PAGES ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write Pages(1) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_PAGES_OBJ1 ); + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* write Pages(2) ... Kids array */ + p = pwork->first; + i = 0; + while ( p != NULL ) { + i++; + if ( p->status != SANE_NO_ERR ) { + fprintf ( stderr, " page(%d) is NG!\n", i ); + goto EXIT; + } + + len = snprintf( (char*)str, sizeof(str), SANE_PDF_PAGES_OBJ2, (int)p->obj_id ); /* Page object id */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + p = p->next; + } + + /* write Pages(3) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_PAGES_OBJ3, (int)pwork->page_num ); /* Count */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <2> Catalog */ + if ( ( pwork->offset_table[ SANE_PDF_ENDDOC_CATALOG ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write Catalog */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_CATALOG_OBJ ); + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <3> Info */ + if ( ( pwork->offset_table[ SANE_PDF_ENDDOC_INFO ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + if ( _get_current_time( &tm, &sign_c, &tz_h, &tz_m ) == SANE_ERR ) { + fprintf ( stderr, " Error is occured in _get_current_time.\n" ); + goto EXIT; + } + /* Dates format */ + len = snprintf((char*)str_t, sizeof(str_t), SANE_PDF_INFO_DATES, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, sign_c, tz_h, tz_m ); + if ( (size_t)len >= sizeof(str_t) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + /* write Info */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_INFO_OBJ, str_t ); /* CreationDate */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <4> xref */ + if ( ( pwork->offset_table[ SANE_PDF_ENDDOC_XREF ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write xref(1) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_XREF_OBJ1, (int)size ); /* object num */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* write xref(2) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_XREF_OBJ2 SANE_PDF_XREF_OBJ2 SANE_PDF_XREF_OBJ2, + pwork->offset_table[ SANE_PDF_ENDDOC_CATALOG ], /* object id = 1 : Catalog */ + pwork->offset_table[ SANE_PDF_ENDDOC_PAGES ], /* object id = 2 : Pages */ + pwork->offset_table[ SANE_PDF_ENDDOC_INFO ] ); /* object id = 3 : Info */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + w_count += SANE_PDF_FIRST_PAGE_ID - 1; + + /* write xref(3) */ + p = pwork->first; + while ( p != NULL ) { + /* write offset : SANE_PDF_PAGE_OBJ_PAGE -> SANE_PDF_PAGE_OBJ_CONTENTS_LEN */ + for ( i = 0; i < SANE_PDF_PAGE_OBJ_NUM; i++ ) { + len = snprintf( (char*)str, sizeof(str), SANE_PDF_XREF_OBJ2, p->offset_table[ i ] ); /* object id = 3 ~ */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + w_count ++; + } + p = p->next; + } + /* check object number */ + if ( w_count != size ) { + fprintf ( stderr, " object number is wrong.\n" ); + goto EXIT; + } + + /* <4> trailer */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_TRAILER_OBJ, + (int)size, /* object num */ + pwork->offset_table[ SANE_PDF_ENDDOC_XREF ] ); /* xref offset */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + + ret = SANE_NO_ERR; +EXIT: + return ret; +} + +SANE_Int sane_pdf_start_page( + void *pw, + SANE_Int w, + SANE_Int h, + SANE_Int res, + SANE_Int type, + SANE_Int rotate ) +{ + SANE_Int ret = SANE_ERR, ldata; + SANE_pdf_page *p = NULL; + SANE_Byte str[1024]; + SANE_Int len, len_c; + SANE_Byte *ProcSetImage[SANE_PDF_IMAGE_NUM] = { (SANE_Byte *)"ImageC", (SANE_Byte *)"ImageG", (SANE_Byte *)"ImageG" }; + SANE_Byte *ColorSpace[SANE_PDF_IMAGE_NUM] = { (SANE_Byte *)"DeviceRGB", (SANE_Byte *)"DeviceGray", (SANE_Byte *)"DeviceGray" }; + SANE_Int BitsPerComponent[SANE_PDF_IMAGE_NUM] = { 8, 8, 1 }; + SANE_pdf_work *pwork = (SANE_pdf_work *)pw; + + if ( pwork == NULL || w <= 0 || h <= 0 || res <= 0 || + !( type == SANE_PDF_IMAGE_COLOR || type == SANE_PDF_IMAGE_GRAY || type == SANE_PDF_IMAGE_MONO ) || + !( rotate == SANE_PDF_ROTATE_OFF || rotate == SANE_PDF_ROTATE_ON ) ) { + fprintf ( stderr, " Initialize parameter is error!\n"); + goto EXIT; + } + else if ( ( p = (SANE_pdf_page *)calloc( 1, sizeof(SANE_pdf_page) ) ) == NULL ) { + fprintf ( stderr, " Can't get work memory!\n" ); + goto EXIT; + } + + pwork->obj_num += SANE_PDF_PAGE_OBJ_NUM; + pwork->page_num ++; + + p->prev = p->next = NULL; + if ( pwork->first == NULL ) { + /* append first page */ + pwork->first = p; + } + if ( pwork->last == NULL ) { + /* append first page */ + pwork->last = p; + } + else { + /* append page */ + pwork->last->next = p; + p->prev = pwork->last; + pwork->last = p; + } + + p->page = pwork->page_num; + /* page obj id : page1=4, page2=4+5=9, page3=4+5*2=14, ... */ + p->obj_id = SANE_PDF_FIRST_PAGE_ID + ( p->page - 1 ) * SANE_PDF_PAGE_OBJ_NUM; + p->image_type = type; + p->res = res; + p->w = w; p->h = h; + p->w_72 = w * 72 / res; p->h_72 = h * 72 / res; + p->stream_len = 0; + p->status = SANE_ERR; + + /* <1> Page */ + if ( ( p->offset_table[ SANE_PDF_PAGE_OBJ_PAGE ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write Page */ + if ( rotate == SANE_PDF_ROTATE_OFF ) { + len = snprintf( (char*)str, sizeof(str), SANE_PDF_PAGE_OBJ, + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_PAGE), /* object id ( Page ) */ + (int)p->page, /* ImX (X = page number) ... XObject/Image Name */ + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_IMAGE), /* object id ( XObject/Image ) */ + ProcSetImage[ type ], /* ProcSet */ + (int)p->w_72, (int)p->h_72, /* MediaBox */ + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_CONTENTS) ); /* object id ( Contents ) */ + } + else { + len = snprintf( (char*)str, sizeof(str), SANE_PDF_PAGE_OBJ_180, + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_PAGE), /* object id ( Page ) */ + (int)p->page, /* ImX (X = page number) ... XObject/Image Name */ + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_IMAGE), /* object id ( XObject/Image ) */ + ProcSetImage[ type ], /* ProcSet */ + (int)p->w_72, (int)p->h_72, /* MediaBox */ + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_CONTENTS) ); /* object id ( Contents ) */ + } + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <2> Contents */ + if ( ( p->offset_table[ SANE_PDF_PAGE_OBJ_CONTENTS ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write Contents(1) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_CONTENTS_OBJ1, + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_CONTENTS), /* object id ( Contents ) */ + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_CONTENTS_LEN) ); /* object id ( Length of Contents ) */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + /* write Contents(2) */ + len_c = len = snprintf( (char*)str, sizeof(str), SANE_PDF_CONTENTS_OBJ2, + (int)p->w_72, (int)p->h_72, /* CTM ( scaling ) */ + (int)p->page ); /* ImX (X = page number) ... XObject/Image Name */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* write Contents(3) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_END_ST_OBJ ); + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <3> Length of Contents - stream */ + if ( ( p->offset_table[ SANE_PDF_PAGE_OBJ_CONTENTS_LEN ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write Length */ + len = snprintf( (char *)str, sizeof(str), SANE_PDF_LENGTH_OBJ, + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_CONTENTS_LEN), /* object id ( Length of Contents ) */ + len_c ); /* length value */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <4> XObject(Image) */ + if ( ( p->offset_table[ SANE_PDF_PAGE_OBJ_IMAGE ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write XObject */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_IMAGE_OBJ, + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_IMAGE), /* object id ( XObject(Image) ) */ + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_IMAGE_LEN), /* object id ( Length of XObject ) */ + (int)p->w, (int)p->h, /* Width/Height */ + ColorSpace[ type ], /* ColorSpace */ + (int)BitsPerComponent[ type ] ); /* BitsPerComponent */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + ret = SANE_NO_ERR; +EXIT: + return ret; + +} + +SANE_Int sane_pdf_end_page( void *pw ) +{ + SANE_Int ret = SANE_ERR, ldata; + SANE_pdf_page *p = NULL; + SANE_Byte str[1024]; + SANE_Int len; + SANE_pdf_work *pwork = (SANE_pdf_work *)pw; + + if ( pwork == NULL ) { + fprintf ( stderr, " Initialize parameter is error!\n" ); + goto EXIT; + } + + p = pwork->last; + + /* <1> endstream, endobj (XObject) */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_END_ST_OBJ ); + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + /* <2> Length of XObject - stream */ + if ( ( p->offset_table[ SANE_PDF_PAGE_OBJ_IMAGE_LEN ] = _get_current_offset( pwork->fd ) ) < 0 ) { + fprintf ( stderr, " offset > %lld\n", SANE_PDF_XREF_MAX ); + goto EXIT; + } + /* write Length */ + len = snprintf( (char*)str, sizeof(str), SANE_PDF_LENGTH_OBJ, + (int)(p->obj_id + SANE_PDF_PAGE_OBJ_IMAGE_LEN), /* object id ( Length of XObject stream ) */ + (int)p->stream_len ); /* length value */ + if ( (size_t)len >= sizeof(str) || len < 0 ) { + fprintf ( stderr, " string is too long!\n" ); + goto EXIT; + } + if ( ( ldata = re_write_if_fail( pwork->fd, str, len ) ) < 0 ) { + fprintf ( stderr, " Error is occured in re_write_if_fail.\n" ); + goto EXIT; + } + + ret = SANE_NO_ERR; + p->status = SANE_NO_ERR; +EXIT: + return ret; +} diff --git a/frontend/jpegtopdf.h b/frontend/jpegtopdf.h new file mode 100644 index 0000000..123c8a0 --- /dev/null +++ b/frontend/jpegtopdf.h @@ -0,0 +1,77 @@ +/* scanimage -- command line scanning utility
+ * Uses the SANE library.
+ *
+ * Copyright (C) 2021 Thierry HUCHARD <thierry@ordissimo.com>
+ *
+ * For questions and comments contact the sane-devel mailinglist (see
+ * http://www.sane-project.org/mailing-lists.html).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef __JPEG_TO_PDF_H__
+#define __JPEG_TO_PDF_H__
+
+#include "../include/_stdint.h"
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/saneopts.h"
+
+
+
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+typedef long long SANE_Int64;
+
+/* sane_pdf_StartPage - type */
+enum {
+ SANE_PDF_IMAGE_COLOR = 0, /* RGB24bit */
+ SANE_PDF_IMAGE_GRAY, /* Gray8bit */
+ SANE_PDF_IMAGE_MONO, /* Gray1bit */
+ SANE_PDF_IMAGE_NUM,
+};
+
+/* sane_pdf_StartPage - rotate */
+enum {
+ SANE_PDF_ROTATE_OFF = 0, /* rotate off */
+ SANE_PDF_ROTATE_ON, /* rotate 180 degrees */
+};
+
+
+typedef struct mynode
+{
+ SANE_Int page;
+ SANE_Int show_page;
+ SANE_Int rotate;
+ struct mynode *prev;
+ struct mynode *next;
+ FILE* fd;
+ SANE_Int file_size;
+ SANE_Byte file_path[ PATH_MAX ];
+} SANE_PDF_NODE, *LPSANE_PDF_NODE;
+
+
+SANE_Int sane_pdf_open( void **ppw, FILE* fd );
+void sane_pdf_close( void *pw );
+
+SANE_Int sane_pdf_start_doc( void *pw );
+SANE_Int sane_pdf_end_doc( void *pw );
+
+SANE_Int sane_pdf_start_page( void *pw, SANE_Int w, SANE_Int h, SANE_Int res, SANE_Int type, SANE_Int rotate );
+SANE_Int sane_pdf_end_page( void *pw );
+
+#endif /* __JPEG_TO_PDF_H__ */
diff --git a/frontend/scanimage.c b/frontend/scanimage.c index 901a7c8..b587b9d 100644 --- a/frontend/scanimage.c +++ b/frontend/scanimage.c @@ -60,6 +60,10 @@ #include "sicc.h" #include "stiff.h" +#ifdef HAVE_LIBJPEG +#include "jpegtopdf.h" +#endif + #include "../include/md5.h" #ifndef PATH_MAX @@ -119,6 +123,7 @@ static struct option basic_options[] = { #define OUTPUT_TIFF 2 #define OUTPUT_PNG 3 #define OUTPUT_JPEG 4 +#define OUTPUT_PDF 5 #define BASE_OPTSTRING "d:hi:Lf:o:B::nvVTAbp" #define STRIP_HEIGHT 256 /* # lines we increment image height */ @@ -633,6 +638,9 @@ print_option (SANE_Device * device, int opt_num, const SANE_Option_Descriptor *o else if(!(opt->cap & SANE_CAP_SOFT_SELECT) && (opt->cap & SANE_CAP_SOFT_DETECT)) fputs (" [read-only]", stdout); + else if (opt->cap & SANE_CAP_ADVANCED) + fputs (" [advanced]", stdout); + fputs ("\n ", stdout); column = 8; @@ -1328,7 +1336,7 @@ advance (Image * image) } static SANE_Status -scan_it (FILE *ofp) +scan_it (FILE *ofp, void* pw) { int i, len, first_frame = 1, offset = 0, must_buffer = 0; uint64_t hundred_percent = 0; @@ -1354,6 +1362,8 @@ scan_it (FILE *ofp) struct jpeg_error_mgr jerr; #endif + (void)pw; + do { if (!first_frame) @@ -1448,6 +1458,14 @@ scan_it (FILE *ofp) break; #endif #ifdef HAVE_LIBJPEG + case OUTPUT_PDF: + sane_pdf_start_page ( pw, parm.pixels_per_line, parm.lines, + resolution_value, SANE_PDF_IMAGE_COLOR, + SANE_PDF_ROTATE_OFF); + write_jpeg_header (parm.format, parm.pixels_per_line, + parm.lines, resolution_value, + ofp, &cinfo, &jerr); + break; case OUTPUT_JPEG: write_jpeg_header (parm.format, parm.pixels_per_line, parm.lines, resolution_value, @@ -1465,7 +1483,7 @@ scan_it (FILE *ofp) pngbuf = malloc(parm.bytes_per_line); #endif #ifdef HAVE_LIBJPEG - if(output_format == OUTPUT_JPEG) + if(output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF) jpegbuf = malloc(parm.bytes_per_line); #endif @@ -1629,7 +1647,7 @@ scan_it (FILE *ofp) else #endif #ifdef HAVE_LIBJPEG - if (output_format == OUTPUT_JPEG) + if (output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF) { int i = 0; int left = len; @@ -1729,6 +1747,14 @@ scan_it (FILE *ofp) break; #endif #ifdef HAVE_LIBJPEG + case OUTPUT_PDF: + sane_pdf_start_page ( pw, parm.pixels_per_line, parm.lines, + resolution_value, SANE_PDF_IMAGE_COLOR, + SANE_PDF_ROTATE_OFF); + write_jpeg_header (parm.format, parm.pixels_per_line, + parm.lines, resolution_value, + ofp, &cinfo, &jerr); + break; case OUTPUT_JPEG: write_jpeg_header (parm.format, parm.pixels_per_line, parm.lines, resolution_value, @@ -1760,7 +1786,7 @@ scan_it (FILE *ofp) png_write_end(png_ptr, info_ptr); #endif #ifdef HAVE_LIBJPEG - if(output_format == OUTPUT_JPEG) + if(output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF) jpeg_finish_compress(&cinfo); #endif @@ -1775,7 +1801,7 @@ cleanup: } #endif #ifdef HAVE_LIBJPEG - if(output_format == OUTPUT_JPEG) { + if(output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF) { jpeg_destroy_compress(&cinfo); free(jpegbuf); } @@ -2017,7 +2043,8 @@ static int guess_output_format(const char* output_file) { ".jpg", OUTPUT_JPEG }, { ".jpeg", OUTPUT_JPEG }, { ".tiff", OUTPUT_TIFF }, - { ".tif", OUTPUT_TIFF } + { ".tif", OUTPUT_TIFF }, + { ".pdf", OUTPUT_PDF } }; for (unsigned i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) { @@ -2052,6 +2079,7 @@ main (int argc, char **argv) SANE_Status status; char *full_optstring; SANE_Int version_code; + void *pw = NULL; FILE *ofp = NULL; buffer_size = (32 * 1024); /* default size */ @@ -2155,6 +2183,15 @@ main (int argc, char **argv) exit(1); #endif } + else if (strcmp (optarg, "pdf") == 0) + { +#ifdef HAVE_LIBJPEG + output_format = OUTPUT_PDF; +#else + fprintf(stderr, "PDF support not compiled in\n"); + exit(1); +#endif + } else if (strcmp (optarg, "pnm") == 0) { output_format = OUTPUT_PNM; @@ -2308,7 +2345,7 @@ standard output.\n\ Parameters are separated by a blank from single-character options (e.g.\n\ -d epson) and by a \"=\" from multi-character options (e.g. --device-name=epson).\n\ -d, --device-name=DEVICE use a given scanner device (e.g. hp:/dev/scanner)\n\ - --format=pnm|tiff|png|jpeg file format of output file\n\ + --format=pnm|tiff|png|jpeg|pdf file format of output file\n\ -i, --icc-profile=PROFILE include this ICC profile into TIFF file\n", prog_name); printf ("\ -L, --list-devices show available scanner devices\n\ @@ -2622,6 +2659,9 @@ List of available devices:", prog_name); break; #endif #ifdef HAVE_LIBJPEG + case OUTPUT_PDF: + format = "out%d.pdf"; + break; case OUTPUT_JPEG: format = "out%d.jpg"; break; @@ -2642,6 +2682,13 @@ List of available devices:", prog_name); scanimage_exit(1); } } +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + sane_pdf_open(&pw, ofp ); + sane_pdf_start_doc( pw ); + } +#endif } if (batch) @@ -2667,11 +2714,14 @@ List of available devices:", prog_name); { char path[PATH_MAX]; char part_path[PATH_MAX]; - if (batch) /* format is NULL unless batch mode */ + if (batch) /* format is NULL unless batch mode */ { sprintf (path, format, n); /* love --(C++) */ strcpy (part_path, path); - strcat (part_path, ".part"); +#ifdef HAVE_LIBJPEG + if (output_format != OUTPUT_PDF) +#endif + strcat (part_path, ".part"); } @@ -2689,6 +2739,13 @@ List of available devices:", prog_name); { if (ofp) { +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + sane_pdf_end_doc( pw ); + sane_pdf_close ( pw ); + } +#endif fclose (ofp); ofp = NULL; } @@ -2711,8 +2768,15 @@ List of available devices:", prog_name); { fprintf (stderr, "%s: sane_start: %s\n", prog_name, sane_strstatus (status)); - if (ofp) + if (ofp ) { +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + sane_pdf_end_doc( pw ); + sane_pdf_close ( pw ); + } +#endif fclose (ofp); ofp = NULL; } @@ -2723,15 +2787,42 @@ List of available devices:", prog_name); /* write to .part file while scanning is in progress */ if (batch) { - if (NULL == (ofp = fopen (part_path, "w"))) +#ifdef HAVE_LIBJPEG + SANE_Bool init_pdf = SANE_FALSE; +#endif + if (ofp == NULL) + { + ofp = fopen (part_path, "w"); +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF && ofp != NULL) + init_pdf = SANE_TRUE; +#endif + } + if (NULL == ofp) { fprintf (stderr, "cannot open %s\n", part_path); sane_cancel (device); return SANE_STATUS_ACCESS_DENIED; } +#ifdef HAVE_LIBJPEG + if (init_pdf ) + { + sane_pdf_open( &pw, ofp ); + sane_pdf_start_doc ( pw ); + } +#endif + } + + status = scan_it (ofp, pw); + +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + sane_pdf_end_page( pw ); + fflush( ofp ); } +#endif - status = scan_it (ofp); if (batch) { fprintf (stderr, "Scanned page %d.", n); @@ -2745,32 +2836,47 @@ List of available devices:", prog_name); status = SANE_STATUS_GOOD; if (batch) { - if (!ofp || 0 != fclose(ofp)) - { - fprintf (stderr, "cannot close image file\n"); - sane_cancel (device); - return SANE_STATUS_ACCESS_DENIED; - } - else +#ifdef HAVE_LIBJPEG + if (output_format != OUTPUT_PDF) { - ofp = NULL; - /* let the fully scanned file show up */ - if (rename (part_path, path)) - { - fprintf (stderr, "cannot rename %s to %s\n", - part_path, path); - sane_cancel (device); - return SANE_STATUS_ACCESS_DENIED; - } - if (batch_print) - { - fprintf (stdout, "%s\n", path); - fflush (stdout); - } +#endif + if (!ofp || 0 != fclose(ofp)) + { + fprintf (stderr, "cannot close image file\n"); + sane_cancel (device); + return SANE_STATUS_ACCESS_DENIED; + } + else + { + ofp = NULL; + /* let the fully scanned file show up */ + if (rename (part_path, path)) + { + fprintf (stderr, "cannot rename %s to %s\n", + part_path, path); + sane_cancel (device); + return SANE_STATUS_ACCESS_DENIED; + } + if (batch_print) + { + fprintf (stdout, "%s\n", path); + fflush (stdout); + } + } +#ifdef HAVE_LIBJPEG } +#endif } else { +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + sane_pdf_end_doc( pw ); + fflush( ofp ); + sane_pdf_close ( pw ); + } +#endif if (output_file && ofp) { fclose(ofp); @@ -2783,13 +2889,20 @@ List of available devices:", prog_name); { if (ofp) { - fclose (ofp); - ofp = NULL; - } + fclose (ofp); + ofp = NULL; + } unlink (part_path); } else { +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + sane_pdf_end_doc( pw ); + sane_pdf_close ( pw ); + } +#endif if (output_file && ofp) { fclose(ofp); @@ -2807,6 +2920,19 @@ List of available devices:", prog_name); if (batch) { +#ifdef HAVE_LIBJPEG + if (output_format == OUTPUT_PDF) + { + if (output_file && ofp) + { + sane_pdf_end_doc( pw ); + fflush( ofp ); + sane_pdf_close ( pw ); + fclose(ofp); + ofp = NULL; + } + } +#endif int num_pgs = (n - batch_start_at) / batch_increment; fprintf (stderr, "Batch terminated, %d page%s scanned\n", num_pgs, num_pgs == 1 ? "" : "s"); |