src/pdfio.c
l_int32
ptraConcatenatePdfToData(L_PTRA    *pa_data,
                         SARRAY    *sa,
                         l_uint8  **pdata,
                         size_t    *pnbytes)
{
char     *fname, *str_pages, *str_trailer;
l_uint8  *pdfdata, *data;
l_int32   i, j, index, nobj, npages;
l_int32  *sizes, *locs;
size_t    size;
L_BYTEA  *bas, *bad, *bat1, *bat2;
NUMA     *na, *na_locs, *na_objs, *napage, *na_sizes, *na_outlocs;
NUMAA    *naa_locs;  /* object locations on each page */
NUMAA    *naa_objs;  /* object mapping numbers to new values */

    PROCNAME("ptraConcatenatePdfToData");

    if (!pdata)
        return ERROR_INT("&data not defined", procName, 1);
    *pdata = NULL;
    if (!pnbytes)
        return ERROR_INT("&nbytes not defined", procName, 1);
    *pnbytes = 0;
    if (!pa_data)
        return ERROR_INT("pa_data not defined", procName, 1);

        /* Parse the files and find the object locations.
         * Remove file data that cannot be parsed. */
    ptraGetActualCount(pa_data, &npages);
    naa_locs = numaaCreate(npages);
    for (i = 0; i < npages; i++) {
        bas = (L_BYTEA *)ptraGetHandle(pa_data, i);
        if (parseTrailerPdf(bas, &na_locs) != 0) {
            bas = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
            l_byteaDestroy(&bas);
            if (sa) {
                fname = sarrayGetString(sa, i, L_NOCOPY);
                L_ERROR_STRING("can't parse file %s; skipping",
                               procName, fname);
            }
            else {
                L_ERROR_INT("can't parse file %d; skipping", procName, i);
            }
        }
        else {
            numaaAddNuma(naa_locs, na_locs, L_INSERT);
        }
    }

        /* Recompute npages in case some of the files were not pdf */
    ptraCompactArray(pa_data);
    ptraGetActualCount(pa_data, &npages);
    if (npages == 0) {
        numaaDestroy(&naa_locs);
        return ERROR_INT("no parsable pdf files found", procName, 1);
    }

        /* Find the mapping from initial to final object numbers */
    naa_objs = numaaCreate(npages);  /* stores final object numbers */
    napage = numaCreate(npages);  /* stores "Page" object numbers */
    index = 0;
    for (i = 0; i < npages; i++) {
        na = numaaGetNuma(naa_locs, i, L_CLONE);
        nobj = numaGetCount(na);
        if (i == 0) {
            numaAddNumber(napage, 4);  /* object 4 on first page */
            na_objs = numaMakeSequence(0.0, 1.0, nobj - 1);
            index = nobj - 1;
        }
        else {  /* skip the first 3 objects in each file */
            numaAddNumber(napage, index);  /* Page object is first we add */
            na_objs = numaMakeConstant(0.0, nobj - 1);
            numaReplaceNumber(na_objs, 3, 3);  /* refers to parent of all */
            for (j = 4; j < nobj - 1; j++)
                numaSetValue(na_objs, j, index++);
        }
        numaaAddNuma(naa_objs, na_objs, L_INSERT);
        numaDestroy(&na);
    }

        /* Make the Pages object (#3) */
    str_pages = generatePagesObjStringPdf(napage);

        /* Build the output */
    bad = l_byteaCreate(5000);
    na_outlocs = numaCreate(0);  /* locations of all output objects */
    for (i = 0; i < npages; i++) {
        bas = (L_BYTEA *)ptraGetHandle(pa_data, i);
        pdfdata = l_byteaGetData(bas, &size);
        na_locs = numaaGetNuma(naa_locs, i, L_CLONE);  /* locs on this page */
        na_objs = numaaGetNuma(naa_objs, i, L_CLONE);  /* obj # on this page */
        nobj = numaGetCount(na_locs) - 1;
        na_sizes = numaMakeDelta(na_locs);  /* object sizes on this page */
        sizes = numaGetIArray(na_sizes);
        locs = numaGetIArray(na_locs);
        if (i == 0) {
            l_byteaAppendData(bad, pdfdata, sizes[0]);
            l_byteaAppendData(bad, pdfdata + locs[1], sizes[1]);
            l_byteaAppendData(bad, pdfdata + locs[2], sizes[2]);
            l_byteaAppendString(bad, str_pages);
            for (j = 0; j < 4; j++)
                numaAddNumber(na_outlocs, locs[j]);
        }
        for (j = 4; j < nobj; j++) {
            numaAddNumber(na_outlocs, l_byteaGetSize(bad));
            bat1 = l_byteaInitFromMem(pdfdata + locs[j], sizes[j]);
            bat2 = substituteObjectNumbers(bat1, na_objs);
            data = l_byteaGetData(bat2, &size);
            l_byteaAppendData(bad, data, size);
            l_byteaDestroy(&bat1);
            l_byteaDestroy(&bat2);
        }
        if (i == npages - 1)  /* last one */
            numaAddNumber(na_outlocs, l_byteaGetSize(bad));
        FREE(sizes);
        FREE(locs);
        numaDestroy(&na_locs);
        numaDestroy(&na_objs);
        numaDestroy(&na_sizes);
    }

        /* Add the trailer */
    str_trailer = makeTrailerStringPdf(na_outlocs);
    l_byteaAppendString(bad, str_trailer);

        /* Transfer the output data */
    *pdata = l_byteaCopyData(bad, pnbytes);
    l_byteaDestroy(&bad);

#if  DEBUG_MULTIPAGE
    fprintf(stderr, "******** object mapper **********");
    numaaWriteStream(stderr, naa_objs);

    fprintf(stderr, "******** Page object numbers ***********");
    numaWriteStream(stderr, napage);

    fprintf(stderr, "******** Pages object ***********\n");
    fprintf(stderr, "%s\n", str_pages);
#endif  /* DEBUG_MULTIPAGE */

    numaaDestroy(&naa_locs);
    numaaDestroy(&naa_objs);
    numaDestroy(&napage);
    numaDestroy(&na_outlocs);
    FREE(str_pages);
    FREE(str_trailer);
    return 0;
}
/*!
 *  ptraConcatenatePdfToData()
 *
 *      Input:  ptra (array of pdf strings, each for a single-page pdf file)
 *              sarray (<optional> of pathnames for input pdf files)
 *              &data (<return> concatenated pdf data in memory)
 *              &nbytes (<return> number of bytes in pdf data)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This only works with leptonica-formatted single-page pdf files.
 *          pdf files generated by other programs will have unpredictable
 *          (and usually bad) results.  The requirements for each pdf file:
 *            (a) The Catalog and Info objects are the first two.
 *            (b) Object 3 is Pages
 *            (c) Object 4 is Page
 *            (d) The remaining objects are Contents, XObjects, and ColorSpace
 *      (2) We remove trailers from each page, and append the full trailer
 *          for all pages at the end.
 *      (3) For all but the first file, remove the ID and the first 3
 *          objects (catalog, info, pages), so that each subsequent
 *          file has only objects of these classes:
 *              Page, Contents, XObject, ColorSpace (Indexed RGB).
 *          For those objects, we substitute these refs to objects
 *          in the local file:
 *              Page:  Parent(object 3), Contents, XObject(typically multiple)
 *              XObject:  [ColorSpace if indexed]
 *          The Pages object on the first page (object 3) has a Kids array
 *          of references to all the Page objects, with a Count equal
 *          to the number of pages.  Each Page object refers back to
 *          this parent.
 */
ptraConcatenatePdfToData()
Input:  ptra (array of pdf strings, each for a single-page pdf file)
sarray ( of pathnames for input pdf files)
&data ( concatenated pdf data in memory)
&nbytes ( number of bytes in pdf data)
Return: 0 if OK, 1 on error

(1) This only works with leptonica-formatted single-page pdf files.
pdf files generated by other programs will have unpredictable
(and usually bad) results.  The requirements for each pdf file:
(a) The Catalog and Info objects are the first two.
(b) Object 3 is Pages
(c) Object 4 is Page
(d) The remaining objects are Contents, XObjects, and ColorSpace
(2) We remove trailers from each page, and append the full trailer
for all pages at the end.
(3) For all but the first file, remove the ID and the first 3
objects (catalog, info, pages), so that each subsequent
file has only objects of these classes:
Page, Contents, XObject, ColorSpace (Indexed RGB).
For those objects, we substitute these refs to objects
in the local file:
Page:  Parent(object 3), Contents, XObject(typically multiple)
XObject:  [ColorSpace if indexed]
The Pages object on the first page (object 3) has a Kids array
of references to all the Page objects, with a Count equal
to the number of pages.  Each Page object refers back to
this parent.
ptraConcatenatePdfToData () Input : ptra ( array of pdf strings each for a single - page pdf file ) sarray ( of pathnames for input pdf files ) &data ( concatenated pdf data in memory ) &nbytes ( number of bytes in pdf data ) Return : 0 if OK 1 on error ( 1 ) This only works with leptonica - formatted single - page pdf files . pdf files generated by other programs will have unpredictable ( and usually bad ) results . The requirements for each pdf file : ( a ) The Catalog and Info objects are the first two . ( b ) Object 3 is Pages ( c ) Object 4 is Page ( d ) The remaining objects are Contents XObjects and ColorSpace ( 2 ) We remove trailers from each page and append the full trailer for all pages at the end . ( 3 ) For all but the first file remove the ID and the first 3 objects ( catalog info pages ) so that each subsequent file has only objects of these classes : Page Contents XObject ColorSpace ( Indexed RGB ) . For those objects we substitute these refs to objects in the local file : Page : Parent ( object 3 ) Contents XObject ( typically multiple ) XObject : [ ColorSpace if indexed ] The Pages object on the first page ( object 3 ) has a Kids array of references to all the Page objects with a Count equal to the number of pages . Each Page object refers back to this parent .
l_int32
ptraConcatenatePdfToData(L_PTRA    *pa_data,
                         SARRAY    *sa,
                         l_uint8  **pdata,
                         size_t    *pnbytes)
{
char     *fname, *str_pages, *str_trailer;
l_uint8  *pdfdata, *data;
l_int32   i, j, index, nobj, npages;
l_int32  *sizes, *locs;
size_t    size;
L_BYTEA  *bas, *bad, *bat1, *bat2;
NUMA     *na, *na_locs, *na_objs, *napage, *na_sizes, *na_outlocs;
NUMAA    *naa_locs;  
NUMAA    *naa_objs;  
    PROCNAME("ptraConcatenatePdfToData");
    if (!pdata)
        return ERROR_INT("&data not defined", procName, 1);
    *pdata = NULL;
    if (!pnbytes)
        return ERROR_INT("&nbytes not defined", procName, 1);
    *pnbytes = 0;
    if (!pa_data)
        return ERROR_INT("pa_data not defined", procName, 1);
    ptraGetActualCount(pa_data, &npages);
    naa_locs = numaaCreate(npages);
    for (i = 0; i < npages; i++) {
        bas = (L_BYTEA *)ptraGetHandle(pa_data, i);
        if (parseTrailerPdf(bas, &na_locs) != 0) {
            bas = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
            l_byteaDestroy(&bas);
            if (sa) {
                fname = sarrayGetString(sa, i, L_NOCOPY);
                L_ERROR_STRING("can't parse file %s; skipping",
                               procName, fname);
            }
            else {
                L_ERROR_INT("can't parse file %d; skipping", procName, i);
            }
        }
        else {
            numaaAddNuma(naa_locs, na_locs, L_INSERT);
        }
    }
    ptraCompactArray(pa_data);
    ptraGetActualCount(pa_data, &npages);
    if (npages == 0) {
        numaaDestroy(&naa_locs);
        return ERROR_INT("no parsable pdf files found", procName, 1);
    }
    naa_objs = numaaCreate(npages);  
    napage = numaCreate(npages);  
    index = 0;
    for (i = 0; i < npages; i++) {
        na = numaaGetNuma(naa_locs, i, L_CLONE);
        nobj = numaGetCount(na);
        if (i == 0) {
            numaAddNumber(napage, 4);  
            na_objs = numaMakeSequence(0.0, 1.0, nobj - 1);
            index = nobj - 1;
        }
        else {  
            numaAddNumber(napage, index);  
            na_objs = numaMakeConstant(0.0, nobj - 1);
            numaReplaceNumber(na_objs, 3, 3);  
            for (j = 4; j < nobj - 1; j++)
                numaSetValue(na_objs, j, index++);
        }
        numaaAddNuma(naa_objs, na_objs, L_INSERT);
        numaDestroy(&na);
    }
    str_pages = generatePagesObjStringPdf(napage);
    bad = l_byteaCreate(5000);
    na_outlocs = numaCreate(0);  
    for (i = 0; i < npages; i++) {
        bas = (L_BYTEA *)ptraGetHandle(pa_data, i);
        pdfdata = l_byteaGetData(bas, &size);
        na_locs = numaaGetNuma(naa_locs, i, L_CLONE);  
        na_objs = numaaGetNuma(naa_objs, i, L_CLONE);  
        nobj = numaGetCount(na_locs) - 1;
        na_sizes = numaMakeDelta(na_locs);  
        sizes = numaGetIArray(na_sizes);
        locs = numaGetIArray(na_locs);
        if (i == 0) {
            l_byteaAppendData(bad, pdfdata, sizes[0]);
            l_byteaAppendData(bad, pdfdata + locs[1], sizes[1]);
            l_byteaAppendData(bad, pdfdata + locs[2], sizes[2]);
            l_byteaAppendString(bad, str_pages);
            for (j = 0; j < 4; j++)
                numaAddNumber(na_outlocs, locs[j]);
        }
        for (j = 4; j < nobj; j++) {
            numaAddNumber(na_outlocs, l_byteaGetSize(bad));
            bat1 = l_byteaInitFromMem(pdfdata + locs[j], sizes[j]);
            bat2 = substituteObjectNumbers(bat1, na_objs);
            data = l_byteaGetData(bat2, &size);
            l_byteaAppendData(bad, data, size);
            l_byteaDestroy(&bat1);
            l_byteaDestroy(&bat2);
        }
        if (i == npages - 1)  
            numaAddNumber(na_outlocs, l_byteaGetSize(bad));
        FREE(sizes);
        FREE(locs);
        numaDestroy(&na_locs);
        numaDestroy(&na_objs);
        numaDestroy(&na_sizes);
    }
    str_trailer = makeTrailerStringPdf(na_outlocs);
    l_byteaAppendString(bad, str_trailer);
    *pdata = l_byteaCopyData(bad, pnbytes);
    l_byteaDestroy(&bad);
#if  DEBUG_MULTIPAGE
    fprintf(stderr, "******** object mapper **********");
    numaaWriteStream(stderr, naa_objs);
    fprintf(stderr, "******** Page object numbers ***********");
    numaWriteStream(stderr, napage);
    fprintf(stderr, "******** Pages object ***********\n");
    fprintf(stderr, "%s\n", str_pages);
#endif  
    numaaDestroy(&naa_locs);
    numaaDestroy(&naa_objs);
    numaDestroy(&napage);
    numaDestroy(&na_outlocs);
    FREE(str_pages);
    FREE(str_trailer);
    return 0;
}
l_int32 ptraConcatenatePdfToData ( L_PTRA * pa_data , SARRAY * sa , l_uint8 * * pdata , size_t * pnbytes ) { char * fname , * str_pages , * str_trailer ; l_uint8 * pdfdata , * data ; l_int32 i , j , index , nobj , npages ; l_int32 * sizes , * locs ; size_t size ; L_BYTEA * bas , * bad , * bat1 , * bat2 ; NUMA * na , * na_locs , * na_objs , * napage , * na_sizes , * na_outlocs ; NUMAA * naa_locs ; NUMAA * naa_objs ; PROCNAME ( " " ) ; if ( ! pdata ) return ERROR_INT ( " " , procName , 1 ) ; * pdata = NULL ; if ( ! pnbytes ) return ERROR_INT ( " " , procName , 1 ) ; * pnbytes = 0 ; if ( ! pa_data ) return ERROR_INT ( " " , procName , 1 ) ; ptraGetActualCount ( pa_data , & npages ) ; naa_locs = numaaCreate ( npages ) ; for ( i = 0 ; i < npages ; i ++ ) { bas = ( L_BYTEA * ) ptraGetHandle ( pa_data , i ) ; if ( parseTrailerPdf ( bas , & na_locs ) != 0 ) { bas = ( L_BYTEA * ) ptraRemove ( pa_data , i , L_NO_COMPACTION ) ; l_byteaDestroy ( & bas ) ; if ( sa ) { fname = sarrayGetString ( sa , i , L_NOCOPY ) ; L_ERROR_STRING ( " " , procName , fname ) ; } else { L_ERROR_INT ( " " , procName , i ) ; } } else { numaaAddNuma ( naa_locs , na_locs , L_INSERT ) ; } } ptraCompactArray ( pa_data ) ; ptraGetActualCount ( pa_data , & npages ) ; if ( npages == 0 ) { numaaDestroy ( & naa_locs ) ; return ERROR_INT ( " " , procName , 1 ) ; } naa_objs = numaaCreate ( npages ) ; napage = numaCreate ( npages ) ; index = 0 ; for ( i = 0 ; i < npages ; i ++ ) { na = numaaGetNuma ( naa_locs , i , L_CLONE ) ; nobj = numaGetCount ( na ) ; if ( i == 0 ) { numaAddNumber ( napage , 4 ) ; na_objs = numaMakeSequence ( 0.0 , 1.0 , nobj - 1 ) ; index = nobj - 1 ; } else { numaAddNumber ( napage , index ) ; na_objs = numaMakeConstant ( 0.0 , nobj - 1 ) ; numaReplaceNumber ( na_objs , 3 , 3 ) ; for ( j = 4 ; j < nobj - 1 ; j ++ ) numaSetValue ( na_objs , j , index ++ ) ; } numaaAddNuma ( naa_objs , na_objs , L_INSERT ) ; numaDestroy ( & na ) ; } str_pages = generatePagesObjStringPdf ( napage ) ; bad = l_byteaCreate ( 5000 ) ; na_outlocs = numaCreate ( 0 ) ; for ( i = 0 ; i < npages ; i ++ ) { bas = ( L_BYTEA * ) ptraGetHandle ( pa_data , i ) ; pdfdata = l_byteaGetData ( bas , & size ) ; na_locs = numaaGetNuma ( naa_locs , i , L_CLONE ) ; na_objs = numaaGetNuma ( naa_objs , i , L_CLONE ) ; nobj = numaGetCount ( na_locs ) - 1 ; na_sizes = numaMakeDelta ( na_locs ) ; sizes = numaGetIArray ( na_sizes ) ; locs = numaGetIArray ( na_locs ) ; if ( i == 0 ) { l_byteaAppendData ( bad , pdfdata , sizes [ 0 ] ) ; l_byteaAppendData ( bad , pdfdata + locs [ 1 ] , sizes [ 1 ] ) ; l_byteaAppendData ( bad , pdfdata + locs [ 2 ] , sizes [ 2 ] ) ; l_byteaAppendString ( bad , str_pages ) ; for ( j = 0 ; j < 4 ; j ++ ) numaAddNumber ( na_outlocs , locs [ j ] ) ; } for ( j = 4 ; j < nobj ; j ++ ) { numaAddNumber ( na_outlocs , l_byteaGetSize ( bad ) ) ; bat1 = l_byteaInitFromMem ( pdfdata + locs [ j ] , sizes [ j ] ) ; bat2 = substituteObjectNumbers ( bat1 , na_objs ) ; data = l_byteaGetData ( bat2 , & size ) ; l_byteaAppendData ( bad , data , size ) ; l_byteaDestroy ( & bat1 ) ; l_byteaDestroy ( & bat2 ) ; } if ( i == npages - 1 ) numaAddNumber ( na_outlocs , l_byteaGetSize ( bad ) ) ; FREE ( sizes ) ; FREE ( locs ) ; numaDestroy ( & na_locs ) ; numaDestroy ( & na_objs ) ; numaDestroy ( & na_sizes ) ; } str_trailer = makeTrailerStringPdf ( na_outlocs ) ; l_byteaAppendString ( bad , str_trailer ) ; * pdata = l_byteaCopyData ( bad , pnbytes ) ; l_byteaDestroy ( & bad ) ; #if DEBUG_MULTIPAGE fprintf ( stderr , " " ) ; numaaWriteStream ( stderr , naa_objs ) ; fprintf ( stderr , " " ) ; numaWriteStream ( stderr , napage ) ; fprintf ( stderr , " \n " ) ; fprintf ( stderr , " \n " , str_pages ) ; #endif numaaDestroy ( & naa_locs ) ; numaaDestroy ( & naa_objs ) ; numaDestroy ( & napage ) ; numaDestroy ( & na_outlocs ) ; FREE ( str_pages ) ; FREE ( str_trailer ) ; return 0 ; }
ptraConcatenatePdfToData()
      Input:  ptra (array of pdf strings, each for a single-page pdf file)
              sarray (<optional> of pathnames for input pdf files)
              &data (<return> concatenated pdf data in memory)
              &nbytes (<return> number of bytes in pdf data)
      Return: 0 if OK, 1 on error
ptraConcatenatePdfToData () Input : ptra ( array of pdf strings each for a single - page pdf file ) sarray ( <optional > of pathnames for input pdf files ) &data ( <return > concatenated pdf data in memory ) &nbytes ( <return > number of bytes in pdf data ) Return : 0 if OK 1 on error
/* object locations on each page */ /* object mapping numbers to new values */ /* Parse the files and find the object locations.
         * Remove file data that cannot be parsed. */ /* Recompute npages in case some of the files were not pdf */ /* Find the mapping from initial to final object numbers */ /* stores final object numbers */ /* stores "Page" object numbers */ /* object 4 on first page */ /* skip the first 3 objects in each file */ /* Page object is first we add */ /* refers to parent of all */ /* Make the Pages object (#3) */ /* Build the output */ /* locations of all output objects */ /* locs on this page */ /* obj # on this page */ /* object sizes on this page */ /* last one */ /* Add the trailer */ /* Transfer the output data */ /* DEBUG_MULTIPAGE */