/***********************************************************************
This is experimental JBIG2 image support to pdfTeX.

Copyright (C) 2002, 2003 by Hartmut Henkel

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, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The author may be contacted via the e-mail address

	hartmut_henkel@gmx.de

JBIG2 image decoding is part of Adobe PDF-1.4, and requires Acroread
5.0 or later.

References:

14492 FCD: Information technology -- coded representation of picture
and audio information -- lossy/lossless coding of bi-level images /
JBIG committee, 1999 July 16.  This JBIG2 Working Draft is available
from http://www.jpeg.org/public/fcd14492.pdf.  The references in the
C-code correspond to the sections of this document.

PDF references: Adobe portable document format version
1.4 / Adobe Systems Incorporated. --- 3rd ed. Available from
http://partners.adobe.com/asn/developer/acrosdk/docs/filefmtspecs/
PDFReference.zip

NEWS:

04 Jan. 2003: Rewritten

13 Nov. 2002: pdfcrypting removed

08 Dec. 2002: bug in page 0 stream writing repaired.
Strategy for multiple page inclusion from same JBIG2 file: When writing
1st image, create fresh PDF object for page 0, and include any page
0 segments from complete file (even if these segments are not needed
for image). When writing next image, check by filename comparison if
PDF object for page 0 of this JBIG2 file has already been written. This
can only remember the file name for the direct predecessor JBIG2 image
(but images of other types might come inbetween). If such page 0 PDF
object exists, reference it. Else create fresh one.

09 Dec. 2002: JBIG2 seg. page numbers > 0 are now set to 1, see PDF Ref.

C-source formatted by `indent -kr'

$Id: writejbig2.c,v 1.1 2003/01/08 22:11:59 hahe Exp $
***********************************************************************/

#include "writejbig2.h"

LIST filelist = { NULL, NULL, NULL };

/**********************************************************************/

void initfileinfo(FILEINFO * fip)
{
    fip->file = NULL;
    fip->filename = NULL;
    initlinkedlist(&(fip->pages));
    initlinkedlist(&(fip->page0));
    fip->fhdrflags = 0;
    fip->sequentialaccess = 0;
    fip->numofpages = 0;
    fip->streamstart = 0;
    fip->pdfpage0objnum = 0;
    fip->phase = INITIAL;
}

/**********************************************************************/

void initpageinfo(PAGEINFO * pip)
{
    initlinkedlist(&(pip->segments));
    pip->pagenum = 0;
    pip->width = 0;
    pip->height = 0;
    pip->xres = 0;
    pip->yres = 0;
    pip->pagesegmentflags = 0;
    pip->stripinginfo = 0;
    pip->stripedheight = 0;
}

/**********************************************************************/

void initseginfo(SEGINFO * sip)
{
    sip->segnum = 0;
    sip->isrefered = 0;
    sip->refers = 0;
    sip->shdrflags = 0;
    sip->pageassocsize = 0;
    sip->reftosegcount = 0;
    sip->countofrefered = 0;
    sip->fieldlen = 0;
    sip->segnumwidth = 0;
    sip->segpage = 0;
    sip->segdatalen = 0;
    sip->hdrstart = 0;
    sip->hdrend = 0;
    sip->datastart = 0;
    sip->dataend = 0;
    sip->endofstripeflag = 0;
    sip->endofpageflag = 0;
    sip->pageinfoflag = 0;
    sip->endoffileflag = 0;
}

/**********************************************************************/

void initlinkedlist(LIST * lp)
{
    lp->first = NULL;
    lp->last = NULL;
    lp->root = NULL;
}

/**********************************************************************/

LIST *litem_append(LIST * lp)
{
    LITEM *ip;
    ip = xtalloc(1, LITEM);
    if (!lp->first) {
	lp->first = ip;
	ip->prev = NULL;
    } else {
	lp->last->next = ip;
	ip->prev = lp->last;
    }
    lp->last = ip;
    ip->next = NULL;
    ip->d = NULL;
    return lp;
}

/**********************************************************************/

LIST *litem_remove(LIST * lp)
{
    LITEM *ip;
    ip = lp->last;
    if (ip) {
	if (ip->d)
	    xfree(ip->d);
	ip = lp->last->prev;
	xfree(lp->last);
	if (ip)
	    ip->next = NULL;
	lp->last = ip;
    }
    return ip ? lp : NULL;
}

/**********************************************************************/

LIST *list_remove(LIST * lp)
{
    while (litem_remove(lp));
    return NULL;
}

/**********************************************************************/

void file_append(LIST * lp)
{
    lp = litem_append(lp);
    lp->last->d = xtalloc(1, FILEINFO);
    initfileinfo(lp->last->d);
}

/**********************************************************************/

void page_append(LIST * lp)
{
    lp = litem_append(lp);
    lp->last->d = xtalloc(1, PAGEINFO);
    initpageinfo(lp->last->d);
}

/**********************************************************************/

void segment_append(LIST * lp)
{
    lp = litem_append(lp);
    lp->last->d = xtalloc(1, SEGINFO);
    initseginfo(lp->last->d);
}

/**********************************************************************/

FILEINFO *fileinfo_create()
{
    FILEINFO *fip;
    fip = xtalloc(1, FILEINFO);
    initfileinfo(fip);
    return fip;
}

/**********************************************************************/

void pages_maketree(LIST * plp)
{
    plp->root = list_mktree(plp->first, plp->last);
}

/**********************************************************************/

void segments_maketree(LIST * slp)
{
    slp->root = list_mktree(slp->first, slp->last);
}

/**********************************************************************/

LITEM *list_mktree(LITEM * fromip, LITEM * toip)
{
    LITEM *ip;
    unsigned long int i = 0, j;

    ip = fromip;

    while (ip->next && ip != toip) {
	ip = ip->next;
	i++;
    }
    ip = fromip;
    for (j = 0; j < i / 2; j++)
	ip = ip->next;
    if (fromip != ip)
	ip->lchild = list_mktree(fromip, ip->prev);
    else
	ip->lchild = ip;
    if (ip != toip)
	ip->rchild = list_mktree(ip->next, toip);
    else
	ip->rchild = ip;
    return ip;
}

/**********************************************************************/

PAGEINFO *find_pageinfo(LIST * lp, unsigned long pagenum)
{
    LITEM *p = lp->root;

    while (pagenum != ((PAGEINFO *) p->d)->pagenum) {
	if (p->lchild == p->rchild) {
	    p = NULL;
	    break;
	}
	if (pagenum > ((PAGEINFO *) p->d)->pagenum)
	    p = p->rchild;
	else
	    p = p->lchild;
    }
    return p ? p->d : NULL;
}

/**********************************************************************/

SEGINFO *find_seginfo(LIST * lp, unsigned long segnum)
{
    LITEM *p = lp->root;
    SEGINFO *sip;

    sip = lp->root->d;
    while (segnum != ((SEGINFO *) p->d)->segnum) {
	if (p->lchild == p->rchild) {
	    p = NULL;
	    break;
	}
	if (segnum > ((SEGINFO *) p->d)->segnum)
	    p = p->rchild;
	else
	    p = p->lchild;
    }
    return p ? p->d : NULL;
}

/**********************************************************************/

unsigned int read2bytes(FILE * f)
{
    unsigned int c = xgetc(f);
    return (c << 8) + xgetc(f);
}

/**********************************************************************/

unsigned long read4bytes(FILE * f)
{
    unsigned int l = read2bytes(f);
    return (l << 16) + read2bytes(f);
}

/**********************************************************************/

unsigned long getstreamlen(LITEM * slip, int refer)
{
    SEGINFO *sip;
    unsigned long len = 0;

    while (slip) {
	sip = slip->d;
	if (refer || sip->isrefered)
	    len +=
		sip->hdrend - sip->hdrstart + sip->dataend -
		sip->datastart;
	slip = slip->next;
    }
    return len;
}

/**********************************************************************/

void readfilehdr(FILEINFO * fip)
{
    unsigned int i;

    /* Annex D.4 File header syntax */
    /* Annex D.4.1 ID string */
    unsigned char jbig2_id[] =
	{ 0x97, 'J', 'B', '2', 0x0d, 0x0a, 0x1a, 0x0a };

    fseek(fip->file, 0, SEEK_SET);
    for (i = 0; i < 8; i++)
	if (xgetc(fip->file) != jbig2_id[i])
	    pdftex_fail
		("readfilehdr(): reading JBIG2 image file failed (ID string missing)");
    /* Annex D.4.2 File header flags */
    fip->fhdrflags = xgetc(fip->file);
    fip->sequentialaccess = fip->fhdrflags & 0x01;
    /* Annex D.4.3 Number of pages */
    if (!(fip->fhdrflags >> 1) & 0x01)	/* known number of pages */
	fip->numofpages = read4bytes(fip->file);
    /* --- at end of file header --- */
}

/**********************************************************************/
/* for first time reading of file */

void readseghdr(FILEINFO * fip, SEGINFO * sip)
{
    unsigned int i;

    sip->hdrstart = xftell(fip->file, fip->filename);
    /* 7.2.2 Segment number */
    sip->segnum = read4bytes(fip->file);
    /* 7.2.3 Segment header flags */
    sip->shdrflags = xgetc(fip->file);
    sip->pageassocsize = (sip->shdrflags >> 6) & 0x01;
    /* 7.2.4 Referred-to segment count and retention flags */
    sip->reftosegcount = (unsigned int) xgetc(fip->file);
    sip->countofrefered = sip->reftosegcount >> 5;
    if (sip->countofrefered < 5)
	sip->fieldlen = 1;
    else {
	sip->fieldlen = 5 + sip->countofrefered / 8;
	xfseek(fip->file, sip->fieldlen - 1, SEEK_CUR, fip->filename);
    }
    /* 7.2.5 Referred-to segment numbers */
    if (sip->segnum <= 256)
	sip->segnumwidth = 1;
    else if (sip->segnum <= 65536)
	sip->segnumwidth = 2;
    else
	sip->segnumwidth = 4;
    for (i = 0; i < sip->countofrefered; i++) {
	switch (sip->segnumwidth) {
	case 1:
	    (void) xgetc(fip->file);
	    break;
	case 2:
	    (void) read2bytes(fip->file);
	    break;
	case 4:
	    (void) read4bytes(fip->file);
	    break;
	}
    }
    /* 7.2.6 Segment page association */
    if (sip->pageassocsize)
	sip->segpage = read4bytes(fip->file);
    else
	sip->segpage = xgetc(fip->file);
    /* 7.2.7 Segment data length */
    sip->segdatalen = read4bytes(fip->file);
    sip->hdrend = xftell(fip->file, fip->filename);
    checkseghdrflags(sip);
    /* ---- at end of segment header ---- */
}

/**********************************************************************/
/* for writing, marks refered page0 segments, sets segpage > 0 to 1 */

void writeseghdr(FILEINFO * fip, SEGINFO * sip)
{
    unsigned int i;
    unsigned long referedseg;

    /* 7.2.2 Segment number */
    /* 7.2.3 Segment header flags */
    /* 7.2.4 Referred-to segment count and retention flags */
    for (i = 0; i < 5 + sip->fieldlen; i++)
	pdfout(xgetc(fip->file));
    /* 7.2.5 Referred-to segment numbers */
    for (i = 0; i < sip->countofrefered; i++) {
	switch (sip->segnumwidth) {
	case 1:
	    referedseg = xgetc(fip->file);
	    pdfout(referedseg);
	    break;
	case 2:
	    referedseg = read2bytes(fip->file);
	    pdfout((referedseg >> 8) & 0xff);
	    pdfout(referedseg & 0xff);
	    break;
	case 4:
	    referedseg = read4bytes(fip->file);
	    pdfout((referedseg >> 24) & 0xff);
	    pdfout((referedseg >> 16) & 0xff);
	    pdfout((referedseg >> 8) & 0xff);
	    pdfout(referedseg & 0xff);
	    break;
	}
	if (fip->page0.last && !sip->refers)
	    markpage0seg(fip, referedseg);
    }
    if (sip->countofrefered)
	sip->refers = 1;
    /* 7.2.6 Segment page association */
    if (sip->pageassocsize)
	for (i = 0; i < 3; i++) {
	    (void) xgetc(fip->file);
	    pdfout(0);
	}
    (void) xgetc(fip->file);
    pdfout(sip->segpage ? 1 : 0);
    /* 7.2.7 Segment data length */
    for (i = 0; i < 4; i++)
	pdfout(xgetc(fip->file));
    /* ---- at end of segment header ---- */
}

/**********************************************************************/
/* for recursive marking of refered page0 segments */

void checkseghdr(FILEINFO * fip, SEGINFO * sip)
{
    unsigned int i;
    unsigned long referedseg;

    /* 7.2.2 Segment number */
    /* 7.2.3 Segment header flags */
    /* 7.2.4 Referred-to segment count and retention flags */
    xfseek(fip->file, 5 + sip->fieldlen, SEEK_CUR, fip->filename);
    /* 7.2.5 Referred-to segment numbers */
    for (i = 0; i < sip->countofrefered; i++) {
	switch (sip->segnumwidth) {
	case 1:
	    referedseg = xgetc(fip->file);
	    break;
	case 2:
	    referedseg = read2bytes(fip->file);
	    break;
	case 4:
	    referedseg = read4bytes(fip->file);
	    break;
	}
	if (!sip->refers)
	    markpage0seg(fip, referedseg);
    }
    if (sip->countofrefered)
	sip->refers = 1;
    /* 7.2.6 Segment page association */
    /* 7.2.7 Segment data length */
    if (sip->pageassocsize)
	xfseek(fip->file, 8, SEEK_CUR, fip->filename);
    else
	xfseek(fip->file, 5, SEEK_CUR, fip->filename);
    /* ---- at end of segment header ---- */
}

/**********************************************************************/

void checkseghdrflags(SEGINFO * sip)
{
    sip->pageinfoflag = 0;
    sip->endofpageflag = 0;
    sip->endofstripeflag = 0;
    sip->endoffileflag = 0;

    /* 7.3 Segment types */
    switch (sip->shdrflags & 0x3f) {
    case M_SymbolDictionary:
    case M_IntermediateTextRegion:
    case M_ImmediateTextRegion:
    case M_ImmediateLosslessTextRegion:
    case M_PatternDictionary:
    case M_IntermediateHalftoneRegion:
    case M_ImmediateHalftoneRegion:
    case M_ImmediateLosslessHalftoneRegion:
    case M_IntermediateGenericRegion:
    case M_ImmediateGenericRegion:
    case M_ImmediateLosslessGenericRegion:
    case M_IntermediateGenericRefinementRegion:
    case M_ImmediateGenericRefinementRegion:
    case M_ImmediateLosslessGenericRefinementRegion:
	break;
    case M_PageInformation:
	sip->pageinfoflag = 1;
	break;
    case M_EndOfPage:
	sip->endofpageflag = 1;
	break;
    case M_EndOfStripe:
	sip->endofstripeflag = 1;
	break;
    case M_EndOfFile:
	sip->endoffileflag = 1;
	break;
    case M_Profiles:
    case M_Tables:
    case M_Extension:
	break;
    default:
	pdftex_fail("checkseghdrflags(): unknown segment type");
	break;
    }
}

/**********************************************************************/

void markpage0seg(FILEINFO * fip, unsigned long referedseg)
{
    PAGEINFO *pip;
    SEGINFO *sip;

    pip = fip->page0.first->d;
    sip = find_seginfo(&(pip->segments), referedseg);
    if (sip) {
	if (!sip->refers && sip->countofrefered)
	    checkseghdr(fip, sip);
	sip->isrefered = 1;
    }
}

/**********************************************************************/

unsigned long findstreamstart(FILEINFO * fip)
{
    SEGINFO tmp;
    do				/* find random-access stream start */
	readseghdr(fip, &tmp);
    while (!tmp.endoffileflag);
    fip->streamstart = tmp.hdrend;
    (void) readfilehdr(fip);
    return fip->streamstart;
}

/**********************************************************************/

FILEINFO *rd_jbig2_info(FILEINFO * fip)
{
    unsigned long seekdist = 0;	/* for sequential-access only */
    unsigned long streampos = 0;	/* for random-access only */
    unsigned long currentpage = 0;
    int sipavail = 0;
    SEGINFO *sip;
    PAGEINFO *pip;
    LIST *slp;

    fip->file = xfopen(fip->filename, FOPEN_RBIN_MODE);
    (void) readfilehdr(fip);
    if (!fip->sequentialaccess)	/* D.2 Random-access organisation */
	streampos = findstreamstart(fip);

    while (1) {			/* loop over segments */
	if (!sipavail) {
	    sip = xtalloc(1, SEGINFO);
	    sipavail = 1;
	}
	initseginfo(sip);
	readseghdr(fip, sip);
	if (sip->endoffileflag)
	    break;
	if (sip->segpage) {
	    if (sip->segpage > currentpage) {
		page_append(&(fip->pages));
		currentpage = sip->segpage;
	    }
	    pip = fip->pages.last->d;
	} else {
	    if (!fip->page0.last)
		page_append(&(fip->page0));
	    pip = fip->page0.last->d;
	}
	if (!sip->endofpageflag) {
	    slp = litem_append(&(pip->segments));
	    slp->last->d = sip;
	    sipavail = 0;
	}
	if (!fip->sequentialaccess)
	    sip->datastart = streampos;
	else
	    sip->datastart = sip->hdrend;
	sip->dataend = sip->datastart + sip->segdatalen;
	if (!fip->sequentialaccess
	    && (sip->pageinfoflag || sip->endofstripeflag))
	    xfseek(fip->file, sip->datastart, SEEK_SET, fip->filename);
	seekdist = sip->segdatalen;
	/* 7.4.8 Page information segment syntax */
	if (sip->pageinfoflag) {
	    pip->pagenum = sip->segpage;
	    pip->width = read4bytes(fip->file);
	    pip->height = read4bytes(fip->file);
	    pip->xres = read4bytes(fip->file);
	    pip->yres = read4bytes(fip->file);
	    pip->pagesegmentflags = xgetc(fip->file);
	    /* 7.4.8.6 Page striping information */
	    pip->stripinginfo = read2bytes(fip->file);
	    seekdist -= 19;
	}
	if (sip->endofstripeflag) {
	    pip->stripedheight = read4bytes(fip->file);
	    seekdist -= 4;
	}
	if (!fip->sequentialaccess
	    && (sip->pageinfoflag || sip->endofstripeflag))
	    xfseek(fip->file, sip->hdrend, SEEK_SET, fip->filename);
	if (!fip->sequentialaccess)
	    streampos += sip->segdatalen;
	if (fip->sequentialaccess)
	    xfseek(fip->file, seekdist, SEEK_CUR, fip->filename);
	if (sip->endofpageflag && currentpage && (pip->stripinginfo >> 15))
	    pip->height = pip->stripedheight;
    }
    if (sipavail)
	xfree(sip);
    xfclose(fip->file, fip->filename);
    return fip;
}

/**********************************************************************/

void wr_jbig2(FILEINFO * fip, unsigned long page)
{
    PAGEINFO *pip;
    LITEM *slip;
    SEGINFO *sip;
    unsigned long i;

    if (page)
	pip = find_pageinfo(&(fip->pages), page);
    else
	pip = find_pageinfo(&(fip->page0), page);
    if (!pip)
	pdftex_fail("wr_jbig2(): page %d not found \n", page);
    if (page) {
	pdf_puts("/Type /XObject\n");
	pdf_puts("/Subtype /Image\n");
	pdf_printf("/Width %i\n", pip->width);
	pdf_printf("/Height %i\n", pip->height);
	pdf_puts("/ColorSpace /DeviceGray\n");
	pdf_puts("/BitsPerComponent 1\n");
	pdf_printf("/Length %i\n", getstreamlen(pip->segments.first, 1));
	pdf_puts("/Filter [/JBIG2Decode]\n");
	if (fip->page0.last) {
	    if (!fip->pdfpage0objnum) {
		pdfcreateobj(0, 0);
		fip->pdfpage0objnum = objptr;
	    }
	    pdf_printf("/DecodeParms [<< /JBIG2Globals %d 0 R >>]\n",
		       fip->pdfpage0objnum);
	}
    } else {
	pdf_puts(">>\n");	/* WRONG - ONLY TEST */
	pdf_puts("endobj\n");	/* WRONG - ONLY TEST */
	pdfbegindict(fip->pdfpage0objnum);
	pdf_printf("/Length %i\n", getstreamlen(pip->segments.first, 0));
    }
    pdf_puts(">>\n");
    pdf_puts("stream\n");
    slip = pip->segments.first;
    fip->file = xfopen(fip->filename, FOPEN_RBIN_MODE);
    while (slip) {		/* loop over page segments */
	sip = slip->d;
	if (sip->isrefered || page) {
	    xfseek(fip->file, sip->hdrstart, SEEK_SET, fip->filename);
	    /* mark refered-to page 0 segments, change segpages > 1 to 1 */
	    writeseghdr(fip, sip);
	    xfseek(fip->file, sip->datastart, SEEK_SET, fip->filename);
	    for (i = sip->datastart; i < sip->dataend; i++)
		pdfout(xgetc(fip->file));
	}
	slip = slip->next;
    }
    xfclose(fip->file, fip->filename);
    pdf_puts("endstream\n");
    pdf_puts("endobj\n");
}

/**********************************************************************/

void read_jbig2_info(integer img)
{
    LIST *flp = &filelist;
    LITEM *flip;
    FILEINFO *fip;
    int filefound = 0;
    PAGEINFO *pip;

    flip = flp->first;
    while (flip) {		/* loop over files */
	fip = flip->d;
	if (strcmp(fip->filename, img_name(img)) == 0) {
	    filefound = 1;
	    break;
	}
	flip = flip->next;
    }
    if (!filefound) {
	file_append(flp);
	fip = flp->last->d;
	fip->filename = malloc(strlen(img_name(img)) + 1);
	strcpy(fip->filename, img_name(img));
    }
    if (fip->phase == INITIAL) {
	rd_jbig2_info(fip);
	fip->phase = HAVEINFO;
	pages_maketree(&(fip->pages));
	if (fip->page0.last) {
	    pages_maketree(&(fip->page0));
	    pip = fip->page0.first->d;
	    segments_maketree(&(pip->segments));
	}
    }
    if (jbig2_ptr(img)->selected_page)
	pip = find_pageinfo(&(fip->pages), jbig2_ptr(img)->selected_page);
    else
	pip = find_pageinfo(&(fip->page0), jbig2_ptr(img)->selected_page);
    if (!pip)
	pdftex_fail
	    ("read_jbig2_info(): page %d not found)",
	     jbig2_ptr(img)->selected_page);

    if (jbig2_ptr(img)->selected_page) {
	img_width(img) = pip->width;
	img_height(img) = pip->height;
	img_xres(img) = (int) (pip->xres * 0.0254 + 0.5);
	img_yres(img) = (int) (pip->yres * 0.0254 + 0.5);
    } else {
	img_width(img) = 1;	/* dummy values to keep pdftex quiet */
	img_height(img) = 1;
    }
}

/**********************************************************************/

void write_jbig2(integer img)
{
    LIST *flp = &filelist;
    LITEM *flip;
    FILEINFO *fip;
    int filefound = 0;
    PAGEINFO *pip;

    flip = flp->first;
    while (flip) {		/* loop over files */
	fip = flip->d;
	if (strcmp(fip->filename, img_name(img)) == 0) {
	    filefound = 1;
	    break;
	}
	flip = flip->next;
    }
    if (!filefound)
	pdftex_fail("write_jbig2(): JBIG2 file %s not found)",
		    img_name(img));
    if (fip->phase != HAVEINFO)
	pdftex_fail
	    ("write_jbig2(): Attempted to write page before rd_jbig2_info()");
    if (jbig2_ptr(img)->selected_page)
	pip = find_pageinfo(&(fip->pages), jbig2_ptr(img)->selected_page);
    else
	pip = find_pageinfo(&(fip->page0), jbig2_ptr(img)->selected_page);
    if (!pip)
	pdftex_fail
	    ("write_jbig2(): page %d not found)",
	     jbig2_ptr(img)->selected_page);
    wr_jbig2(fip, pip->pagenum);
}

/**********************************************************************/
