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"); | 
