/* (c) 2000 Karel Kulhavy, Clocksoft
 * clock@atrey.karlin.mff.cuni.cz
 * This program is a stdin filter that performs these operations:
 * 1) Downconverts 17x15 oversampled binary image data (black-or-white
 *    pixels, 1 bit per pixel, pbmraw format) to 256-level grayscale
 *    pgmraw format and saves it to basename_%d.png.
 * 2) At the end, generates necessary html's
 * 3) The input pages must be concatenated
 * 4) Recommended source of the pbm: Aladdin Ghostscript
 * If there is an input argument (one number as first arg), it's treated
 * as number of first page that comes at fd #0. Default value is 0.
 * Parameters: <hundred_dpi> <basename_> <titlestring> <bottom_html_code>
 * <pageoffset> <input_filename> [first page number]

 * Little benchmarks:   Normal (gs+pbm2png)     1:32
                        Without pbm2png         0:28
                        Without libpng          0:51
                        Without grayscaling     0:32

 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <png.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

unsigned char *basename_;
/*
	basename:
		pbm2png.c:32: `basename' redeclared as different kind of symbol
		/usr/include/string.h:245: previous declaration of `basename'
*/
unsigned char *titlestring;
unsigned char *bottom;
int pageoffset=-13;
unsigned long t1[256]; /* Conversion from 1-bit to 2-bit expansion */
unsigned long t2[256]; /* Conversion from 2-bit to 4-bit expansion */
unsigned char *l1; /* Input data, output data. lw<<1 bytes long */
unsigned long *l2; /* 2-bit expansion lw unsigned long's long*/
unsigned long long *l4; /* 4-bit expansion lw unsigned long long's long */
int ox,oy;
int lw; /* Width of the input in 16-pixel units. */
int lb; /* Width of the input in 8-pixel units */
FILE *of;
int filenumber;
int x,y;
png_structp png_ptr;
png_infop info_ptr;
unsigned long ppm;
float dpi;
unsigned char string[8192];
unsigned char *filename;
int ifd;

void
gentables(void)
{
        int a,d;
        unsigned long b;
        unsigned char c;

        for (a=0;a<256;a++){
                b=0;
                c=a;
                for (d=7;d>=0;d--){
                        b<<=2;
                        b|=(c>>d)&1;
                }
                t1[a]=b;
                b=0;
                c=a;
                for (d=4;d;d--){
                      b<<=4;
                      b|=c&3;
                      c>>=2;
               }
                t2[a]=b;
        }
}

void
make_index(void)
{
	FILE *f;

	f=fopen("index.html","w");

	fprintf(f,"<html><head><title>%s</title></head><body bgcolor=\"#000000\" text=\"#00ff00\" link=\"#ffff00\""
		" vlink=\"#00ffff\" alink=\"#ffff00\">\n",titlestring);
	fprintf(f,"<a href=\"../\"><img src=\"up.png\" border=0></a><br>"
		"<a href=\"%s.pdf\">Download in PDF format</a><br>"
		"<h1>%s</h1><h2>Page index</h2>",basename_,titlestring);
	{
		int n;
		unsigned char *t;
		FILE *g;

		fprintf(f,"<table cellspacing=0 cellpadding=0 border=1>\n<tr>");
		for (n=0;n<filenumber;n++){
			if (!(n%40)&&n){
				fprintf(f,"</tr>\n<tr>");
			}
			fprintf(f,"<td><a href=\"%s%d.html\">%d</a></td>\n",basename_,n,n+pageoffset);
		}

		fprintf(f,"</tr></table><h2>");
		g=fopen("index.dir","rb");
		if (g)
		{
			t=malloc(65536);
			fprintf(f,"Contents Index</h2><table border=0 cellspacing=0 cellpadding=0>");
			while(fgets(t,65536,g)){
				int val=0;
				int len=strlen(t);
				unsigned char *u=t;
				if (len&&t[len-1]=='\n') t[len-1]=0;
				if (*t>'9'||*t<'0') continue;
				while(*u>='0'&&*u<='9'){
					val*=10;
					val+=*u-'0';
					u++;
				}
				val--;
				u++; /* The char behind the number is ignored */
				fprintf(f,"<tr><td>%d </td><td><a href=\"%s%d.html\">%s</a></td></tr>\n",val+pageoffset
					,basename_,val,u);
			}
			fprintf(f,"</table>");
			fclose(g);
			free(t);
		}
		fprintf(f,"</body></html>");
		fclose(f);

	}
}


void
make_page(int index)
{
 FILE *f;

 sprintf(string,"%s%d.html",basename_,index);
 f=fopen(string,"w");

 fprintf(f,"<html><head><title>%s</title></head><body bgcolor=\"#000000\" text=\"#00ff00\" link=\"#ffff00\" vlink=\"#00ffff\" alink=\"#ffff00\">\n",titlestring);
 fprintf(f,"<h1>%s, Page %d</h1>\n",titlestring,index+pageoffset);
 fprintf(f,"<p><table border=0 cellspacing=0 cellpadding=2><tr>");
 fprintf(f,"<td valign=\"top\"><table border=0 cellpadding=0 cellspacing=0><tr><td>");
 if (index)
 {
  fprintf(f,"<a href=\"%s%d.html\"><img src=\"left.png\" border=0></a>\n",basename_,index-1);
 }
 else
 {
  fprintf(f,"<img src=\"left.png\" border=0>\n");
 }
 fprintf(f,"</td><td>");
 if (index<filenumber-1)
 {
  fprintf(f,"<a href=\"%s%d.html\"><img src=\"right.png\" border=0></a>\n",basename_,index+1);
 }
 else
 {
  fprintf(f,"<img src=\"right.png\" border=0>\n");
 }
 fprintf(f,"</td></tr><tr><td colspan=2><a href=\"index.html\"><img src=\"idx.png\" border=0></a></td>");
 fprintf(f,"</tr></table>");
 fprintf(f,"</td><td><img src=\"%s%d.png\" border=\"0\"></td>\n</tr></table>",basename_,index);
 fprintf(f,"</body></html>");
 fclose(f);
}

/* Returns 0 is OK, exit(1) on error, returns 1 on broken pipe */
int
sure_read(unsigned char *dest, size_t len)
{
        ssize_t rd;
        if (!len) return 0;
        again:
        rd=read(ifd,dest,len);
        if (rd==len) return 0;
        if (!rd) return 1;
        if (rd<0&&(errno==EINTR||errno==EAGAIN||errno==EWOULDBLOCK)) goto again;
        if (rd>0&&rd<len){
                /*fprintf(stderr,"read %d/%d",rd,len);*/
                len-=rd;
                dest+=rd;
                fflush(stderr);
                goto again;
        }
        fprintf(stderr,"read error.\n");
        perror("");
        exit(1);
}

int
eat_up_whitespace_and_comments(void)
{
        again:
        sure_read(string,1);
        if (*string==' '||*string=='\t'){
                goto again;
        }
        if (*string=='\n'||*string=='\r'){
                goto again;
        }
        if (*string=='#'){
                /* Eat up comment */
                aa:
                sure_read(string,1);
                if (*string!='\n'&&*string!='\r') goto aa;
                goto again;
        }
        return *string;
}

int
read_header(void)
{


        /*
        if (filenumber)
        {
                int a;

                for (a=0;a<256;a++){
                        sure_read(string,1);
                        printf("%d ", *string);
                        fflush(stdout);
                }
                exit(0);
        }
        */
        if (sure_read(string,2)) return 1; /* P1 */
        x=eat_up_whitespace_and_comments()-'0';
        again:
        sure_read(string,1);
        if (*string!=' '&&*string!='\t'&&*string!='\n'&&*string!='\r'){
                x*=10;
                x+=*string-'0';
                goto again;
        }
        y=eat_up_whitespace_and_comments()-'0';
        bgain:
        sure_read(string,1);
        if (*string!=' '&&*string!='\t'&&*string!='\n'&&*string!='\r'){
                y*=10;
                y+=*string-'0';
                goto bgain;
        }
        return 0;
}

#ifndef Z_BEST_COMPRESSION
#define Z_BEST_COMPRESSION 9
#endif

void
open_png(void)
{
 png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
 info_ptr=png_create_info_struct(png_ptr);
 png_init_io(png_ptr,of);
 png_set_filter(png_ptr,0,PNG_FILTER_NONE|PNG_FILTER_SUB|PNG_FILTER_UP
  |PNG_FILTER_AVG|PNG_FILTER_PAETH);
 png_set_compression_level(png_ptr,Z_BEST_COMPRESSION);
 png_set_IHDR(png_ptr,info_ptr,ox,oy,8,PNG_COLOR_TYPE_GRAY,PNG_INTERLACE_NONE,
  PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
 png_set_gAMA(png_ptr,info_ptr,1.0);
 png_set_pHYs(png_ptr,info_ptr,ppm,ppm,PNG_RESOLUTION_METER);
 png_write_info(png_ptr,info_ptr);
}

void
close_png(void)
{
 png_write_end(png_ptr,info_ptr);
 png_destroy_write_struct(&png_ptr,&info_ptr);
}

/* Represents the same pixels that are in l2 in l4. */
void
move_2_to_4(void)
{
        unsigned long long bit4;
        unsigned long bit2;
        int index;

        for (index=lw-1;index>=0;index--){
                bit2=l2[index];
                bit4=t2[bit2&255];
                bit4<<=16;
                bit2>>=8;
                bit4|=t2[bit2&255];
                bit4<<=16;
                bit2>>=8;
                bit4|=t2[bit2&255];
                bit4<<=16;
                bit2>>=8;
                bit4|=t2[bit2&255];
                l4[index]=bit4;
        }
}

/* Adds the same pixels that are in l2 to l4. */
void
add_2_to_4(void)
{
        unsigned long long bit4;
        unsigned long bit2;
        int index;

        for (index=lw-1;index>=0;index--){
                bit2=l2[index];
                bit4=t2[bit2&255];
                bit4<<=16;
                bit2>>=8;
                bit4|=t2[bit2&255];
                bit4<<=16;
                bit2>>=8;
                bit4|=t2[bit2&255];
                bit4<<=16;
                bit2>>=8;
                bit4|=t2[bit2&255];
                l4[index]+=bit4;
        }
}

/* Moves pixels from l1 to l2 with format change */
void
move_1_to_2(void)
{
        int index;

        for (index=lw-1;index>=0;index--)
                l2[index]=(t1[l1[index<<1]]<<16)|t1[l1[(index<<1)+1]];
}

/* Adds pixels from l1 to l2 with format change */
void
add_1_to_2(void)
{
        int index;

        for (index=lw-1;index>=0;index--)
                l2[index]+=(t1[l1[index<<1]]<<16)|t1[l1[(index<<1)+1]];
}

/* Loads 1 line into l1 */
void
load(void)
{
        sure_read(l1,lb);
}

/* Loads 3 lines into l2 */
void
load_to_2(void)
{
        load();
        move_1_to_2();
        load();
        add_1_to_2();
        load();
        add_1_to_2();
}

/* Loads 15 lines into l4 */
void
load_to_4(void)
{
        load_to_2();
        move_2_to_4();
        load_to_2();
        add_2_to_4();
        load_to_2();
        add_2_to_4();
        load_to_2();
        add_2_to_4();
        load_to_2();
        add_2_to_4();
}

/* Converts the data from l4 to l1 including suming up 17 adjacent pixels */
void
export_from_4(void)
{
        int a,b,reg_hold;
        unsigned char sum;
        unsigned long long *loader=l4;
        unsigned long long reg=0;

        reg_hold=0;
        for (a=0;a<ox;a++){
                sum=0;
                for (b=17;b;b--){
                      if (!reg_hold){
                              reg=*loader++;
                              reg_hold=16;
                      }
                      sum+=reg&15;
                      reg>>=4;
                      reg_hold--;
                }
                l1[a]=sum;
        }
        png_write_row(png_ptr,l1);
}

/*
 * Parameters: <hundred_dpi> <basename_> <titlestring> <bottom_html_code>
 * <pageoffset> [first page number]
 */
int
main(int argc, char **argv)
{
        int a,z;

        if (argc<7){
                fprintf(stderr,"Usage: pbm2png <hundred_dpi> <basename_> <titlestring> <bottom_html_code> <pageoffset> <ifname> [starting_filenumber]\n");
        return 0;
        }
        dpi=atof(argv[1])/10;
        ppm=dpi*1000/25.4;
        basename_=argv[2];
        titlestring=argv[3];
	bottom=argv[4];
        pageoffset=atol(argv[5]);
        filename=argv[6];
	again0:
        ifd=open(filename,O_RDONLY);
	if (ifd<0){
		if (errno==EAGAIN||errno==EINTR||errno==EWOULDBLOCK) goto
			again0;
		else {
			perror("");
			exit(1);
		}
	}
        fprintf(stderr,"filename %s, %d\n",filename,ifd);
        if (argc>=8){
                filenumber=atol(argv[7]);
        }
        gentables();
        again:
        fprintf(stderr,"\nFile %i\n",filenumber);
        if (read_header()){
                for (a=0;a<filenumber;a++)
                        make_page(a);
		make_index();
                return 0;
        }
        lw=(x+15)>>4;
        lb=(x+7)>>3;
        ox=x/17;
        oy=y/15;
        fprintf(stderr,"Input: %i*%i pixels, %f*%f dpi, %.1fMB.\n",x,y,dpi*17,dpi*15,(float)lb*y/1048576);
        fprintf(stderr,"Ouput: %i*%i pixels, %f*%f dpi, %.1fKB raw data.\n",ox,oy,dpi,dpi,(float)ox*oy/1024);
        l1=(unsigned char*)malloc(lw*2);
        l2=(unsigned long*)malloc(lw*sizeof(unsigned long));
        l4=(unsigned long long*)malloc(lw*sizeof(unsigned long long));
        sprintf(string,"%s%d.png",basename_,filenumber);
        filenumber++;
        of=fopen(string,"w");
        open_png();
        for (z=oy;z;z--){
                if (!(z&15)){
                        fprintf(stderr,".");
                        fflush(stderr);
                }
                load_to_4();
                export_from_4();
        }
        close_png();
        fprintf(stderr,"\nWritten %lu bytes of data, ratio %.1f%%\n",ftell(of),(float)ftell(of)*100/ox/oy);
        fclose(of);
        for (a=y%15;a;a--)
                load();
        free(l1);
        free(l2);
        free(l4);
        goto again;
}
