/* pdftoepdf.cc

   Copyright 1996-2006 Han The Thanh <thanh@pdftex.org>
   Copyright 2006-2010 Taco Hoekwater <taco@luatex.org>

   This file is part of LuaTeX.

   LuaTeX 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.

   LuaTeX 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 Lesser General Public
   License for more details.

   You should have received a copy of the GNU General Public License along
   with LuaTeX; if not, see <http://www.gnu.org/licenses/>. */

static const char _svn_version[] =
    "$Id: pdftoepdf.cc 4162 2011-04-16 08:44:24Z taco $ "
    "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.70.0/source/texk/web2c/luatexdir/image/pdftoepdf.cc $";

// define DEBUG

#include "epdf.h"

// This file is mostly C and not very much C++; it's just used to interface
// the functions of xpdf, which happens to be written in C++.

// The prefix "PTEX" for the PDF keys is special to pdfTeX;
// this has been registered with Adobe by Hans Hagen.

#define pdfkeyprefix "PTEX"

static GBool isInit = gFalse;

//**********************************************************************
// Maintain AVL tree of all PDF files for embedding

static avl_table *PdfDocumentTree = NULL;

// AVL sort PdfDocument into PdfDocumentTree by file_path

static int CompPdfDocument(const void *pa, const void *pb, void * /*p */ )
{
    return strcmp(((const PdfDocument *) pa)->file_path,
                  ((const PdfDocument *) pb)->file_path);
}

// Returns pointer to PdfDocument structure for PDF file.

static PdfDocument *findPdfDocument(char *file_path)
{
    PdfDocument *pdf_doc, tmp;
    assert(file_path != NULL);
    if (PdfDocumentTree == NULL)
        return NULL;
    tmp.file_path = file_path;
    pdf_doc = (PdfDocument *) avl_find(PdfDocumentTree, &tmp);
    return pdf_doc;
}

#define PDF_CHECKSUM_SIZE 32

static char *get_file_checksum(char *a, file_error_mode fe)
{
    struct stat finfo;
    char *ck = NULL;
    if (stat(a, &finfo) == 0) {
        off_t size = finfo.st_size;
        time_t mtime = finfo.st_mtime;
        ck = (char *) malloc(PDF_CHECKSUM_SIZE);
        if (ck == NULL)
            pdftex_fail("PDF inclusion: out of memory while processing '%s'",
                        a);
        snprintf(ck, PDF_CHECKSUM_SIZE, "%llu_%llu", (unsigned long long) size,
                 (unsigned long long) mtime);
    } else {
        switch (fe) {
        case FE_FAIL:
            pdftex_fail("PDF inclusion: could not stat() file '%s'", a);
            break;
        case FE_RETURN_NULL:
            if (ck != NULL)
                free(ck);
            ck = NULL;
            break;
        default:
            assert(0);
        }
    }
    return ck;
}

// Returns pointer to PdfDocument structure for PDF file.
// Creates a new PdfDocument structure if it doesn't exist yet.
// When fe = FE_RETURN_NULL, the function returns NULL in error case.

PdfDocument *refPdfDocument(char *file_path, file_error_mode fe)
{
    char *checksum;
    PdfDocument *pdf_doc;
    PDFDoc *doc = NULL;
    GooString *docName = NULL;
    int new_flag = 0;
    if ((checksum = get_file_checksum(file_path, fe)) == NULL) {
        assert(fe == FE_RETURN_NULL);
        return (PdfDocument *) NULL;
    }
    assert(checksum != NULL);
    if ((pdf_doc = findPdfDocument(file_path)) == NULL) {
#ifdef DEBUG
        fprintf(stderr, "\nDEBUG: New PdfDocument %s\n", file_path);
#endif
        new_flag = 1;
        pdf_doc = new PdfDocument;
        pdf_doc->file_path = xstrdup(file_path);
        pdf_doc->checksum = checksum;
        pdf_doc->doc = NULL;
        pdf_doc->inObjList = NULL;
        pdf_doc->ObjMapTree = NULL;
        pdf_doc->occurences = 0;        // 0 = unreferenced
        pdf_doc->pc = 0;
    } else {
#ifdef DEBUG
        fprintf(stderr, "\nDEBUG: Found PdfDocument %s (%d)\n",
                pdf_doc->file_path, pdf_doc->occurences);
#endif
        assert(pdf_doc->checksum != NULL);
        if (strncmp(pdf_doc->checksum, checksum, PDF_CHECKSUM_SIZE) != 0) {
            pdftex_fail("PDF inclusion: file has changed '%s'", file_path);
        }
        free(checksum);
    }
    assert(pdf_doc != NULL);
    if (pdf_doc->doc == NULL) {
#ifdef DEBUG
        fprintf(stderr, "\nDEBUG: New PDFDoc %s (%d)\n",
                pdf_doc->file_path, pdf_doc->occurences);
#endif
        docName = new GooString(file_path);
        doc = new PDFDoc(docName);      // takes ownership of docName
        pdf_doc->pc++;

        if (!doc->isOk() || !doc->okToPrint()) {
            switch (fe) {
            case FE_FAIL:
                pdftex_fail("xpdf: reading PDF image failed");
                break;
            case FE_RETURN_NULL:
                delete doc;
                // delete docName;
                if (new_flag == 1) {
                    if (pdf_doc->file_path != NULL)
                        free(pdf_doc->file_path);
                    if (pdf_doc->checksum != NULL)
                        free(pdf_doc->checksum);
                    delete pdf_doc;
                }
                return (PdfDocument *) NULL;
                break;
            default:
                assert(0);
            }
        }
        pdf_doc->doc = doc;
    }
    // PDF file could be opened without problems, checksum ok.
    if (PdfDocumentTree == NULL)
        PdfDocumentTree = avl_create(CompPdfDocument, NULL, &avl_xallocator);
    if ((PdfDocument *) avl_find(PdfDocumentTree, pdf_doc) == NULL) {
        void **aa = avl_probe(PdfDocumentTree, pdf_doc);
        assert(aa != NULL);
    }
    pdf_doc->occurences++;
#ifdef DEBUG
    fprintf(stderr, "\nDEBUG: Incrementing %s (%d)\n",
            pdf_doc->file_path, pdf_doc->occurences);
#endif
    return pdf_doc;
}

//**********************************************************************
// AVL sort ObjMap into ObjMapTree by object number and generation

// keep the ObjMap struct small, as these are accumulated until the end

struct ObjMap {
    Ref in;                     // object num/gen in orig. PDF file
    int out_num;                // object num after embedding (gen == 0)
};

static int CompObjMap(const void *pa, const void *pb, void * /*p */ )
{
    const Ref *a = &(((const ObjMap *) pa)->in);
    const Ref *b = &(((const ObjMap *) pb)->in);
    if (a->num > b->num)
        return 1;
    if (a->num < b->num)
        return -1;
    if (a->gen == b->gen)       // most likely gen == 0 anyway
        return 0;
    if (a->gen < b->gen)
        return -1;
    return 1;
}

static ObjMap *findObjMap(PdfDocument * pdf_doc, Ref in)
{
    ObjMap *obj_map, tmp;
    assert(pdf_doc != NULL);
    if (pdf_doc->ObjMapTree == NULL)
        return NULL;
    tmp.in = in;
    obj_map = (ObjMap *) avl_find(pdf_doc->ObjMapTree, &tmp);
    return obj_map;
}

static void addObjMap(PdfDocument * pdf_doc, Ref in, int out_num)
{
    ObjMap *obj_map = NULL;
    assert(findObjMap(pdf_doc, in) == NULL);
    if (pdf_doc->ObjMapTree == NULL)
        pdf_doc->ObjMapTree = avl_create(CompObjMap, NULL, &avl_xallocator);
    obj_map = new ObjMap;
    obj_map->in = in;
    obj_map->out_num = out_num;
    void **aa = avl_probe(pdf_doc->ObjMapTree, obj_map);
    assert(aa != NULL);
}

// When copying the Resources of the selected page, all objects are
// copied recursively top-down.  The findObjMap() function checks if an
// object has already been copied; if so, instead of copying just the
// new object number will be referenced.  The ObjMapTree guarantees,
// that during the entire LuaTeX run any object from any embedded PDF
// file will end up max. once in the output PDF file.  Indirect objects
// are not fetched during copying, but get a new object number from
// LuaTeX and then will be appended into a linked list.

static int addInObj(PDF pdf, PdfDocument * pdf_doc, Ref ref)
{
    ObjMap *obj_map;
    InObj *p, *q, *n;
    if (ref.num == 0) {
        pdftex_fail("PDF inclusion: reference to invalid object"
                    " (is the included pdf broken?)");
    }
    if ((obj_map = findObjMap(pdf_doc, ref)) != NULL)
        return obj_map->out_num;
    n = new InObj;
    n->ref = ref;
    n->next = NULL;
    n->num = pdf_new_objnum(pdf);
    addObjMap(pdf_doc, ref, n->num);
    if (pdf_doc->inObjList == NULL)
        pdf_doc->inObjList = n;
    else {
        // it is important to add new objects at the end of the list,
        // because new objects are being added while the list is being
        // written out by writeRefs().
        for (p = pdf_doc->inObjList; p != NULL; p = p->next)
            q = p;
        q->next = n;
    }
    return n->num;
}

//**********************************************************************
// Function converts double to string; very small and very large numbers
// are NOT converted to scientific notation.
// n must be a number or real conforming to the implementation limits
// of PDF as specified in appendix C.1 of the PDF Ref.
// These are:
// maximum value of ints is +2^32
// maximum value of reals is +2^15
// smalles values of reals is 1/(2^16)

static char *convertNumToPDF(double n)
{
    static const int precision = 6;
    static const int fact = (int) 1E6;  // must be 10^precision
    static const double epsilon = 0.5E-6;       // 2epsilon must be 10^-precision
    static char buf[64];
    // handle very small values: return 0
    if (fabs(n) < epsilon) {
        buf[0] = '0';
        buf[1] = '\0';
    } else {
        char ints[64];
        int bindex = 0, sindex = 0;
        int ival, fval;
        // handle the sign part if n is negative
        if (n < 0) {
            buf[bindex++] = '-';
            n = -n;
        }
        n += epsilon;           // for rounding
        // handle the integer part, simply with sprintf
        ival = (int) floor(n);
        n -= ival;
        sprintf(ints, "%d", ival);
        while (ints[sindex] != 0)
            buf[bindex++] = ints[sindex++];
        // handle the fractional part up to 'precision' digits
        fval = (int) floor(n * fact);
        if (fval) {
            // set a dot
            buf[bindex++] = '.';
            sindex = bindex + precision;
            buf[sindex--] = '\0';
            // fill up trailing zeros with the string terminator NULL
            while (((fval % 10) == 0) && (sindex >= bindex)) {
                buf[sindex--] = '\0';
                fval /= 10;
            }
            // fill up the fractional part back to front
            while (sindex >= bindex) {
                buf[sindex--] = (fval % 10) + '0';
                fval /= 10;
            }
        } else
            buf[bindex++] = '\0';
    }
    return (char *) buf;
}

static void copyObject(PDF, PdfDocument *, Object *);

static void copyString(PDF pdf, GooString * string)
{
    char *p;
    unsigned char c;
    size_t i, l;
    p = string->getCString();
    l = (size_t) string->getLength();
    if (strlen(p) == l) {
        pdf_puts(pdf, "(");
        for (; *p != 0; p++) {
            c = (unsigned char) *p;
            if (c == '(' || c == ')' || c == '\\')
                pdf_printf(pdf, "\\%c", c);
            else if (c < 0x20 || c > 0x7F)
                pdf_printf(pdf, "\\%03o", (int) c);
            else
                pdf_out(pdf, c);
        }
        pdf_puts(pdf, ")");
    } else {
        pdf_puts(pdf, "<");
        for (i = 0; i < l; i++) {
            c = (unsigned char) string->getChar(i);
            pdf_printf(pdf, "%.2x", (int) c);
        }
        pdf_puts(pdf, ">");
    }
}

static void copyName(PDF pdf, char *s)
{
    pdf_puts(pdf, "/");
    for (; *s != 0; s++) {
        if (isdigit(*s) || isupper(*s) || islower(*s) || *s == '_' ||
            *s == '.' || *s == '-' || *s == '+')
            pdf_out(pdf, *s);
        else
            pdf_printf(pdf, "#%.2X", *s & 0xFF);
    }
}

static void copyArray(PDF pdf, PdfDocument * pdf_doc, Array * array)
{
    int i, l;
    Object obj1;
    pdf_puts(pdf, "[");
    for (i = 0, l = array->getLength(); i < l; ++i) {
        array->getNF(i, &obj1);
        if (!obj1.isName())
            pdf_puts(pdf, " ");
        copyObject(pdf, pdf_doc, &obj1);
        obj1.free();
    }
    pdf_puts(pdf, "]");
}

static void copyDict(PDF pdf, PdfDocument * pdf_doc, Dict * dict)
{
    int i, l;
    Object obj1;
    pdf_puts(pdf, "<<");
    for (i = 0, l = dict->getLength(); i < l; ++i) {
        copyName(pdf, dict->getKey(i));
        pdf_puts(pdf, " ");
        dict->getValNF(i, &obj1);
        copyObject(pdf, pdf_doc, &obj1);
        obj1.free();
        pdf_puts(pdf, "\n");
    }
    pdf_puts(pdf, ">>");
}

static void copyStreamStream(PDF pdf, Stream * str)
{
    int c;
    str->reset();
    while ((c = str->getChar()) != EOF) {
        pdf_out(pdf, c);
        pdf->last_byte = c;
    }
}

static void copyStream(PDF pdf, PdfDocument * pdf_doc, Stream * stream)
{
    copyDict(pdf, pdf_doc, stream->getDict());
    pdf_puts(pdf, "stream\n");
    copyStreamStream(pdf, stream->getUndecodedStream());
    if (pdf->last_byte != '\n')
        pdf_puts(pdf, "\n");
    pdf_puts(pdf, "endstream"); // can't simply write pdf_end_stream()
}

static void copyObject(PDF pdf, PdfDocument * pdf_doc, Object * obj)
{
    switch (obj->getType()) {
    case objBool:
        pdf_printf(pdf, "%s", obj->getBool()? "true" : "false");
        break;
    case objInt:
        pdf_printf(pdf, "%i", obj->getInt());
        break;
    case objReal:
        pdf_printf(pdf, "%s", convertNumToPDF(obj->getReal()));
        break;
        // not needed:
        // GBool isNum() { return type == objInt || type == objReal; }
    case objString:
        copyString(pdf, obj->getString());
        break;
    case objName:
        copyName(pdf, obj->getName());
        break;
    case objNull:
        pdf_puts(pdf, "null");
        break;
    case objArray:
        copyArray(pdf, pdf_doc, obj->getArray());
        break;
    case objDict:
        copyDict(pdf, pdf_doc, obj->getDict());
        break;
    case objStream:
        copyStream(pdf, pdf_doc, obj->getStream());
        break;
    case objRef:
        pdf_printf(pdf, "%d 0 R", addInObj(pdf, pdf_doc, obj->getRef()));
        break;
    case objCmd:
    case objError:
    case objEOF:
    case objNone:
        pdftex_fail("PDF inclusion: type <%s> cannot be copied",
                    obj->getTypeName());
        break;
    default:
        assert(0);              // xpdf doesn't have any other types
    }
}

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

static void writeRefs(PDF pdf, PdfDocument * pdf_doc)
{
    InObj *r, *n;
    XRef *xref;
    Object obj1;
    xref = pdf_doc->doc->getXRef();
    for (r = pdf_doc->inObjList; r != NULL;) {
        xref->fetch(r->ref.num, r->ref.gen, &obj1);
        if (obj1.isStream())
            pdf_begin_obj(pdf, r->num, 0);
        else
            pdf_begin_obj(pdf, r->num, 2);      // \pdfobjcompresslevel = 2 is for this
        copyObject(pdf, pdf_doc, &obj1);
        obj1.free();
        pdf_puts(pdf, "\n");
        pdf_end_obj(pdf);
        n = r->next;
        delete r;
        pdf_doc->inObjList = r = n;
    }
}

// get the pagebox coordinates according to the pagebox_spec

static PDFRectangle *get_pagebox(Page * page, int pagebox_spec)
{
    switch (pagebox_spec) {
    case PDF_BOX_SPEC_MEDIA:
        return page->getMediaBox();
        break;
    case PDF_BOX_SPEC_CROP:
        return page->getCropBox();
        break;
    case PDF_BOX_SPEC_BLEED:
        return page->getBleedBox();
        break;
    case PDF_BOX_SPEC_TRIM:
        return page->getTrimBox();
        break;
    case PDF_BOX_SPEC_ART:
        return page->getArtBox();
        break;
    default:
        pdftex_fail("PDF inclusion: unknown value of pagebox spec (%i)",
                    (int) pagebox_spec);
    }
    return page->getMediaBox(); // to make the compiler happy
}

// Reads various information about the PDF and sets it up for later inclusion.
// This will fail if the PDF version of the PDF is higher than
// minor_pdf_version_wanted or page_name is given and can not be found.
// It makes no sense to give page_name _and_ page_num.
// Returns the page number.

void
read_pdf_info(image_dict * idict, int minor_pdf_version_wanted,
              int pdf_inclusion_errorlevel, img_readtype_e readtype)
{
    PdfDocument *pdf_doc;
    Page *page;
    int rotate;
    PDFRectangle *pagebox;
    int pdf_major_version_found, pdf_minor_version_found;
    float xsize, ysize, xorig, yorig;
    assert(idict != NULL);
    assert(img_type(idict) == IMG_TYPE_PDF);
    assert(readtype == IMG_CLOSEINBETWEEN);     // only this is implemented
    // initialize
    if (isInit == gFalse) {
        globalParams = new GlobalParams();
        globalParams->setErrQuiet(gFalse);
        isInit = gTrue;
    }
    // open PDF file
    pdf_doc = refPdfDocument(img_filepath(idict), FE_FAIL);
    // check PDF version
    // this works only for PDF 1.x -- but since any versions of PDF newer
    // than 1.x will not be backwards compatible to PDF 1.x, pdfTeX will
    // then have to changed drastically anyway.
    pdf_major_version_found = pdf_doc->doc->getPDFMajorVersion();
    pdf_minor_version_found = pdf_doc->doc->getPDFMinorVersion();
    if ((pdf_major_version_found > 1)
        || (pdf_minor_version_found > minor_pdf_version_wanted)) {
        const char *msg =
            "PDF inclusion: found PDF version <%d.%d>, but at most version <1.%d> allowed";
        if (pdf_inclusion_errorlevel > 0) {
            pdftex_fail(msg, pdf_major_version_found, pdf_minor_version_found,
                        minor_pdf_version_wanted);
        } else {
            pdftex_warn(msg, pdf_major_version_found, pdf_minor_version_found,
                        minor_pdf_version_wanted);
        }
    }
    img_totalpages(idict) = pdf_doc->doc->getCatalog()->getNumPages();
    if (img_pagename(idict)) {
        // get page by name
        GooString name(img_pagename(idict));
        LinkDest *link = pdf_doc->doc->findDest(&name);
        if (link == NULL || !link->isOk())
            pdftex_fail("PDF inclusion: invalid destination <%s>",
                        img_pagename(idict));
        Ref ref = link->getPageRef();
        img_pagenum(idict) =
            pdf_doc->doc->getCatalog()->findPage(ref.num, ref.gen);
        if (img_pagenum(idict) == 0)
            pdftex_fail("PDF inclusion: destination is not a page <%s>",
                        img_pagename(idict));
        delete link;
    } else {
        // get page by number
        if (img_pagenum(idict) <= 0
            || img_pagenum(idict) > img_totalpages(idict))
            pdftex_fail("PDF inclusion: required page <%i> does not exist",
                        (int) img_pagenum(idict));
    }
    // get the required page
    page = pdf_doc->doc->getCatalog()->getPage(img_pagenum(idict));

    // get the pagebox coordinates (media, crop,...) to use.
    pagebox = get_pagebox(page, img_pagebox(idict));
    if (pagebox->x2 > pagebox->x1) {
        xorig = pagebox->x1;
        xsize = pagebox->x2 - pagebox->x1;
    } else {
        xorig = pagebox->x2;
        xsize = pagebox->x1 - pagebox->x2;
    }
    if (pagebox->y2 > pagebox->y1) {
        yorig = pagebox->y1;
        ysize = pagebox->y2 - pagebox->y1;
    } else {
        yorig = pagebox->y2;
        ysize = pagebox->y1 - pagebox->y2;
    }
    // The following 4 parameters are raw. Do _not_ modify by /Rotate!
    img_xsize(idict) = bp2int(xsize);
    img_ysize(idict) = bp2int(ysize);
    img_xorig(idict) = bp2int(xorig);
    img_yorig(idict) = bp2int(yorig);

    // Handle /Rotate parameter. Only multiples of 90 deg. are allowed
    // (PDF Ref. v1.3, p. 78).
    rotate = page->getRotate();
    switch (((rotate % 360) + 360) % 360) {     // handles also neg. angles
    case 0:
        img_rotation(idict) = 0;
        break;
    case 90:
        img_rotation(idict) = 3;        // PDF counts clockwise!
        break;
    case 180:
        img_rotation(idict) = 2;
        break;
    case 270:
        img_rotation(idict) = 1;
        break;
    default:
        pdftex_warn
            ("PDF inclusion: "
             "/Rotate parameter in PDF file not multiple of 90 degrees.");
    }

    // currently unused info whether PDF contains a /Group
    if (page->getGroup() != NULL)
        img_set_group(idict);

    if (readtype == IMG_CLOSEINBETWEEN)
        unrefPdfDocument(img_filepath(idict));
}

//**********************************************************************
// Writes the current epf_doc.
// Here the included PDF is copied, so most errors that can happen
// during PDF inclusion will arise here.

void write_epdf(PDF pdf, image_dict * idict)
{
    PdfDocument *pdf_doc;
    Page *page;
    Ref *pageref;
    Dict *pageDict;
    Object obj1, contents, pageobj, pagesobj1, pagesobj2, *op1, *op2, *optmp;
    PDFRectangle *pagebox;
    int i, l;
    float bbox[4];
    char s[256];
    const char *pagedictkeys[] =
        { "Group", "LastModified", "Metadata", "PieceInfo", "Resources",
        "SeparationInfo", NULL
    };
    assert(idict != NULL);

    // open PDF file
    pdf_doc = refPdfDocument(img_filepath(idict), FE_FAIL);
    page = pdf_doc->doc->getCatalog()->getPage(img_pagenum(idict));
    pageref = pdf_doc->doc->getCatalog()->getPageRef(img_pagenum(idict));
    assert(pageref != NULL);    // was checked already in read_pdf_info()
    pdf_doc->doc->getXRef()->fetch(pageref->num, pageref->gen, &pageobj);
    pageDict = pageobj.getDict();

    // write the Page header
    pdf_puts(pdf, "/Type /XObject\n/Subtype /Form\n");
    if (img_attr(idict) != NULL && strlen(img_attr(idict)) > 0)
        pdf_printf(pdf, "%s\n", img_attr(idict));
    pdf_puts(pdf, "/FormType 1\n");

    // write additional information
    pdf_printf(pdf, "/%s.FileName (%s)\n", pdfkeyprefix,
               convertStringToPDFString(pdf_doc->file_path,
                                        strlen(pdf_doc->file_path)));
    pdf_printf(pdf, "/%s.PageNumber %i\n", pdfkeyprefix,
               (int) img_pagenum(idict));
    pdf_doc->doc->getDocInfoNF(&obj1);
    if (obj1.isRef()) {
        // the info dict must be indirect (PDF Ref p. 61)
        pdf_printf(pdf, "/%s.InfoDict ", pdfkeyprefix);
        pdf_printf(pdf, "%d 0 R\n", addInObj(pdf, pdf_doc, obj1.getRef()));
    }
    obj1.free();
    if (img_is_bbox(idict)) {
        bbox[0] = int2bp(img_bbox(idict)[0]);
        bbox[1] = int2bp(img_bbox(idict)[1]);
        bbox[2] = int2bp(img_bbox(idict)[2]);
        bbox[3] = int2bp(img_bbox(idict)[3]);
    } else {
        // get the pagebox coordinates (media, crop,...) to use.
        pagebox = get_pagebox(page, img_pagebox(idict));
        bbox[0] = pagebox->x1;
        bbox[1] = pagebox->y1;
        bbox[2] = pagebox->x2;
        bbox[3] = pagebox->y2;
    }
    sprintf(s, "/BBox [%.8f %.8f %.8f %.8f]\n", bbox[0], bbox[1], bbox[2],
            bbox[3]);
    pdf_puts(pdf, stripzeros(s));
    // The /Matrix calculation is replaced by transforms in out_img().

    // Now all relevant parts of the Page dictionary are copied:

    // Metadata validity check (as a stream it must be indirect)
    pageDict->lookupNF((char *) "Metadata", &obj1);
    if (!obj1.isNull() && !obj1.isRef())
        pdftex_warn("PDF inclusion: /Metadata must be indirect object");
    obj1.free();

    // copy selected items in Page dictionary
    for (i = 0; pagedictkeys[i] != NULL; i++) {
        pageDict->lookupNF((char *) pagedictkeys[i], &obj1);
        if (!obj1.isNull()) {
            pdf_printf(pdf, "/%s ", pagedictkeys[i]);
            copyObject(pdf, pdf_doc, &obj1);    // preserves indirection
        }
        obj1.free();
    }

    // If there are no Resources in the Page dict of the embedded page,
    // try to inherit the Resources from the Pages tree of the embedded
    // PDF file, climbing up the tree until the Resources are found.
    // (This fixes a problem with Scribus 1.3.3.14.)
    pageDict->lookupNF((char *) "Resources", &obj1);
    if (obj1.isNull()) {
        op1 = &pagesobj1;
        op2 = &pagesobj2;
        pageDict->lookup((char *) "Parent", op1);
        while (op1->isDict()) {
            obj1.free();
            op1->dictLookupNF((char *) "Resources", &obj1);
            if (!obj1.isNull()) {
                pdf_puts(pdf, "/Resources ");
                copyObject(pdf, pdf_doc, &obj1);
                break;
            }
            op1->dictLookup((char *) "Parent", op2);
            optmp = op1;
            op1 = op2;
            op2 = optmp;
            op2->free();
        };
        if (!op1->isDict())
            pdftex_warn("PDF inclusion: Page /Resources missing.");
        op1->free();
    }
    obj1.free();

    // write the Page contents
    page->getContents(&contents);
    if (contents.isStream()) {
        // Variant A: get stream and recompress under control
        // of \pdfcompresslevel
        //
        // pdfbeginstream();
        // copyStreamStream(contents->getStream());
        // pdfendstream();

        // Variant B: copy stream without recompressing
        //
        contents.streamGetDict()->lookup((char *) "F", &obj1);
        if (!obj1.isNull()) {
            pdftex_fail("PDF inclusion: Unsupported external stream");
        }
        obj1.free();
        contents.streamGetDict()->lookup((char *) "Length", &obj1);
        assert(!obj1.isNull());
        pdf_puts(pdf, "/Length ");
        copyObject(pdf, pdf_doc, &obj1);
        obj1.free();
        pdf_puts(pdf, "\n");
        contents.streamGetDict()->lookup((char *) "Filter", &obj1);
        if (!obj1.isNull()) {
            pdf_puts(pdf, "/Filter ");
            copyObject(pdf, pdf_doc, &obj1);
            obj1.free();
            pdf_puts(pdf, "\n");
            contents.streamGetDict()->lookup((char *) "DecodeParms", &obj1);
            if (!obj1.isNull()) {
                pdf_puts(pdf, "/DecodeParms ");
                copyObject(pdf, pdf_doc, &obj1);
                pdf_puts(pdf, "\n");
            }
        }
        obj1.free();
        pdf_puts(pdf, ">>\nstream\n");
        copyStreamStream(pdf, contents.getStream()->getBaseStream());
        pdf_end_stream(pdf);
    } else if (contents.isArray()) {
        pdf_begin_stream(pdf);
        for (i = 0, l = contents.arrayGetLength(); i < l; ++i) {
            copyStreamStream(pdf, (contents.arrayGet(i, &obj1))->getStream());
            obj1.free();
            if (i < (l - 1)) {
                // put a space between streams to be on the safe side (streams
                // should have a trailing space here, but one never knows)
                pdf_puts(pdf, " ");
            }
        }
        pdf_end_stream(pdf);
    } else {                    // the contents are optional, but we need to include an empty stream
        pdf_begin_stream(pdf);
        pdf_end_stream(pdf);
    }
    // write out all indirect objects
    writeRefs(pdf, pdf_doc);
    contents.free();
    pageobj.free();
    // unrefPdfDocument() must come after contents.free() and pageobj.free()!
    // TH: The next line makes repeated pdf inclusion unacceptably slow
#if 0
    unrefPdfDocument(img_filepath(idict));
#endif
}

//**********************************************************************
// Deallocate a PdfDocument with all its resources

static void deletePdfDocumentPdfDoc(PdfDocument * pdf_doc)
{
    InObj *r, *n;
    assert(pdf_doc != NULL);
    // this may be probably needed for an emergency destroyPdfDocument()
    for (r = pdf_doc->inObjList; r != NULL; r = n) {
        n = r->next;
        delete r;
    }
#ifdef DEBUG
    fprintf(stderr, "\nDEBUG: Deleting PDFDoc %s\n", pdf_doc->file_path);
#endif
    delete pdf_doc->doc;
    pdf_doc->doc = NULL;
    pdf_doc->pc++;
}

static void destroyPdfDocument(void *pa, void * /*pb */ )
{
    PdfDocument *pdf_doc = (PdfDocument *) pa;
    deletePdfDocumentPdfDoc(pdf_doc);
    // TODO: delete rest of pdf_doc
}

// Called when an image has been written and its resources in image_tab are
// freed and it's not referenced anymore.

void unrefPdfDocument(char *file_path)
{
    PdfDocument *pdf_doc = findPdfDocument(file_path);
    assert(pdf_doc != NULL);
    assert(pdf_doc->occurences != 0);   // aim for point landing
    pdf_doc->occurences--;
#ifdef DEBUG
    fprintf(stderr, "\nDEBUG: Decrementing %s (%d)\n",
            pdf_doc->file_path, pdf_doc->occurences);
#endif
    if (pdf_doc->occurences == 0) {
        assert(pdf_doc->inObjList == NULL);     // should be eaten up already
        deletePdfDocumentPdfDoc(pdf_doc);
    }
}

// Called when PDF embedding system is finalized.
// Now deallocate all remaining PdfDocuments.

void epdf_free()
{
    if (PdfDocumentTree != NULL)
        avl_destroy(PdfDocumentTree, destroyPdfDocument);
    PdfDocumentTree = NULL;
    if (isInit == gTrue)
        delete globalParams;
    isInit = gFalse;
}
