/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

   Copyright (C) 2007-2019 by Jin-Hwan Cho and Shunsaku Hirata,
   the dvipdfmx project team.

   Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>

   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.
*/

#include "dpx-pdfximage.h"

#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "tectonic_bridge_core.h"
#include "dpx-bmpimage.h"
#include "dpx-dpxconf.h"
#include "dpx-dpxfile.h"
#include "dpx-dpxutil.h"
#include "dpx-epdf.h"
#include "dpx-error.h"
#include "dpx-jpegimage.h"
#include "dpx-mem.h"
#include "dpx-mfileio.h"
#include "dpx-pdfdev.h"
#include "dpx-pdfdraw.h"
#include "dpx-pdfnames.h"
#include "dpx-pdfobj.h"
#include "dpx-pngimage.h"

static int check_for_ps (rust_input_handle_t handle);


#define IMAGE_TYPE_UNKNOWN -1
#define IMAGE_TYPE_PDF      0
#define IMAGE_TYPE_JPEG     1
#define IMAGE_TYPE_PNG      2
#define IMAGE_TYPE_EPS      5
#define IMAGE_TYPE_BMP      6
#define IMAGE_TYPE_JP2      7


struct attr_
{
    int      width, height;
    double   xdensity, ydensity;
    pdf_rect bbox;

    /* Not appropriate place but... someone need them. */
    int      page_no;
    int      page_count;
    int      bbox_type;  /* Ugh */
    pdf_obj *dict;
    char     tempfile;
};

struct pdf_ximage_
{
    char        *ident;
    char         res_name[16];

    int          subtype;

    struct attr_ attr;

    char        *filename;
    char *fullname;
    pdf_obj     *reference;
    pdf_obj     *resource;
    int reserved;
};


/* verbose, verbose, verbose... */
struct opt_
{
    char  *cmdtmpl;
};

static struct opt_ _opts = {
    NULL
};

struct ic_
{
    int         count, capacity;
    pdf_ximage *ximages;
};

static struct ic_  _ic = {
    0, 0, NULL
};

static void
pdf_init_ximage_struct (pdf_ximage *I)
{
    I->ident    = NULL;
    I->filename = NULL;
    I->fullname = NULL;

    I->subtype  = -1;
    memset(I->res_name, 0, 16);
    I->reference = NULL;
    I->resource  = NULL;
    I->reserved = 0;

    I->attr.width = I->attr.height = 0;
    I->attr.xdensity = I->attr.ydensity = 1.0;
    I->attr.bbox.llx = I->attr.bbox.lly = 0;
    I->attr.bbox.urx = I->attr.bbox.ury = 0;

    I->attr.page_no    = 1;
    I->attr.page_count = 1;
    I->attr.bbox_type  = 0;

    I->attr.dict     = NULL;
    I->attr.tempfile = 0;
}

static void
pdf_clean_ximage_struct (pdf_ximage *I)
{
    free(I->ident);
    free(I->filename);
    free(I->fullname);
    pdf_release_obj(I->reference);
    pdf_release_obj(I->resource);/* unsafe? */
    pdf_release_obj(I->attr.dict);
    pdf_init_ximage_struct(I);
}


void
pdf_init_images (void)
{
    struct ic_ *ic = &_ic;
    ic->count    = 0;
    ic->capacity = 0;
    ic->ximages  = NULL;
}

void
pdf_close_images (void)
{
    struct ic_ *ic = &_ic;
    if (ic->ximages) {
        int  i;
        for (i = 0; i < ic->count; i++) {
            pdf_ximage *I = ic->ximages+i;
            if (I->attr.tempfile) {
                /*
                 * It is important to remove temporary files at the end because
                 * we cache file names. Since we use mkstemp to create them, we
                 * might get the same file name again if we delete the first file.
                 * (This happens on NetBSD, reported by Jukka Salmi.)
                 * We also use this to convert a PS file only once if multiple
                 * pages are imported from that file.
                 */
                if (dpx_conf.verbose_level > 1 && dpx_conf.file.keep_cache != 1)
                    dpx_message("pdf_image>> deleting temporary file \"%s\"\n", I->filename);
                dpx_delete_temp_file(I->filename, false); /* temporary filename freed here */
                I->filename = NULL;
                dpx_delete_temp_file(I->fullname, false); /* temporary filename freed here */
                I->fullname = NULL;
            }
            pdf_clean_ximage_struct(I);
        }
        ic->ximages = mfree(ic->ximages);
        ic->count = ic->capacity = 0;
    }

    _opts.cmdtmpl = mfree(_opts.cmdtmpl);
}

static int
source_image_type (rust_input_handle_t handle)
{
    int format = IMAGE_TYPE_UNKNOWN;

    ttstub_input_seek(handle, 0, SEEK_SET);

    /* Original check order: jpeg, jp2, png, bmp, pdf, ps */

    if (check_for_jpeg(handle))
        format = IMAGE_TYPE_JPEG;
    /* else if (check_for_jp2(fp))
     *    format = IMAGE_TYPE_JP2; */
    else if (check_for_png(handle))
        format = IMAGE_TYPE_PNG;
    else if (check_for_bmp(handle))
        format = IMAGE_TYPE_BMP;
    else if (check_for_pdf(handle))
        format = IMAGE_TYPE_PDF;
    else if (check_for_ps(handle))
        format = IMAGE_TYPE_EPS;
    else {
        dpx_warning("Tectonic was unable to detect an image's format");
        format = IMAGE_TYPE_UNKNOWN;
    }

    ttstub_input_seek(handle, 0, SEEK_SET);
    return format;
}

static int
load_image (const char *ident, const char *filename, const char *fullname,
            int format, rust_input_handle_t handle,
            load_options options)
{
    struct ic_ *ic = &_ic;
    int id = -1, reserved = 0;
    pdf_ximage *I;

    if (ident) {
        for (id = 0; id < ic->count; id++) {
            I = &ic->ximages[id];
            if (I->ident && !strcmp(ident, I->ident)) {
                if (I->reserved) {
                    reserved = 1;
                    break;
                }
            }
        }
    }
    if (!reserved) {
        id = ic->count;
        if (ic->count >= ic->capacity) {
            ic->capacity += 16;
            ic->ximages   = RENEW(ic->ximages, ic->capacity, pdf_ximage);
        }
        I  = &ic->ximages[id];
        pdf_init_ximage_struct(I);
        if (ident) {
            I->ident = NEW(strlen(ident)+1, char);
            strcpy(I->ident, ident);
        }
        ic->count++;
    }
    if (filename) {
        I->filename = NEW(strlen(filename)+1, char);
        strcpy(I->filename, filename);
    }
    if (fullname) {
        I->fullname = NEW(strlen(fullname)+1, char);
        strcpy(I->fullname, fullname);
    }

    I->attr.page_no = options.page_no;
    I->attr.bbox_type = options.bbox_type;
    I->attr.dict = options.dict; /* unsafe? */

    switch (format) {
    case IMAGE_TYPE_JPEG:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[JPEG]");
        if (jpeg_include_image(I, handle) < 0)
            goto error;
        I->subtype = PDF_XOBJECT_TYPE_IMAGE;
        break;
    case IMAGE_TYPE_JP2:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[JP2]");
        /*if (jp2_include_image(I, fp) < 0)*/
        dpx_warning("Tectonic: JP2 not yet supported");
        goto error;
        /*I->subtype = PDF_XOBJECT_TYPE_IMAGE;
          break;*/
    case IMAGE_TYPE_PNG:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[PNG]");
        if (png_include_image(I, handle) < 0)
            goto error;
        I->subtype = PDF_XOBJECT_TYPE_IMAGE;
        break;
    case IMAGE_TYPE_BMP:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[BMP]");
        if (bmp_include_image(I, handle) < 0)
            goto error;
        I->subtype = PDF_XOBJECT_TYPE_IMAGE;
        break;
    case IMAGE_TYPE_PDF:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[PDF]");
        {
            int result = pdf_include_page(I, handle, fullname, options);
            /* Tectonic: this used to try ps_include_page() */
            if (result != 0)
                goto error;
        }
        if (dpx_conf.verbose_level > 0)
            dpx_message(",Page:%d", I->attr.page_no);
        I->subtype  = PDF_XOBJECT_TYPE_FORM;
        break;
    case IMAGE_TYPE_EPS:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[EPS]");
        dpx_warning("sorry, PostScript images are not supported by Tectonic");
        dpx_warning("for details, please see https://github.com/tectonic-typesetting/tectonic/issues/27");
        goto error;
    default:
        if (dpx_conf.verbose_level > 0)
            dpx_message("[UNKNOWN]");
        /* Tectonic: this used to try ps_include_page() */
        goto error;
    }

    switch (I->subtype) {
    case PDF_XOBJECT_TYPE_IMAGE:
        sprintf(I->res_name, "Im%d", id);
        break;
    case PDF_XOBJECT_TYPE_FORM:
        sprintf(I->res_name, "Fm%d", id);
        break;
    default:
        _tt_abort("Unknown XObject subtype: %d", I->subtype);
    }

    return id;

error:
    pdf_clean_ximage_struct(I);
    return -1;
}

int
pdf_ximage_load_image (const char *ident, const char *filename, load_options options)
{
    struct ic_ *ic = &_ic;
    int i, id = -1;
    pdf_ximage *I;
    int format;
    rust_input_handle_t handle;

    for (i = 0; i < ic->count; i++) {
        I = &ic->ximages[i];

        if (I->filename && streq_ptr(filename, I->filename)) {
            id = i;
            break;
        }
    }

    if (id >= 0) {
        if (I->attr.page_no == options.page_no &&
            !pdf_compare_object(I->attr.dict, options.dict) && /* ????? */
            I->attr.bbox_type == options.bbox_type) {
            return id;
        }

        /* Tectonic: no filename futzing */
        /* f = I->fullname; */
    }

    /* This happens if we've already inserted the image into the PDF output.
     * In my one test case, it seems to just work to plunge along merrily
     * ahead ...
     *
     * if (f) {
     *   <"we already have converted this file; f is the temporary file name">
     *   fullname = NEW(strlen(f)+1, char);
     *   strcpy(fullname, f);
     * } else { kpse_find_file() }
     */

    handle = ttstub_input_open(filename, TTBC_FILE_FORMAT_PICT, 0);
    if (handle == INVALID_HANDLE) {
        if (dpx_conf.compat_mode == dpx_mode_compat_mode) {
            dpx_warning("Error opening image file \"%s\"", filename);
        } else {
            _tt_abort("Image inclusion failed for \"%s\".", filename);
        }
        return -1;
    }

    if (dpx_conf.verbose_level > 0)
        dpx_message("(Image:%s", filename);

    format = source_image_type(handle);
    /* Tectonic: no external tools to deal with funky image formats */
    id = load_image(ident, filename, filename, format, handle, options);

    ttstub_input_close(handle);

    if (dpx_conf.verbose_level > 0)
        dpx_message(")");

    if (id < 0) {
        if (dpx_conf.compat_mode == dpx_mode_compat_mode) {
            if (format == IMAGE_TYPE_PDF || format == IMAGE_TYPE_EPS) {
                dpx_warning("pdf: image inclusion failed for \"%s\" (page=%d).", filename, options.page_no);
            } else {
                dpx_warning("pdf: image inclusion failed for \"%s\".", filename);
            }
        } else {
            if (format == IMAGE_TYPE_PDF || format == IMAGE_TYPE_EPS) {
                _tt_abort("pdf: image inclusion failed for \"%s\" (page=%d).", filename, options.page_no);
            } else {
                _tt_abort("pdf: image inclusion failed for \"%s\".", filename);
            }
        }
    }

    return id;
}

int
pdf_ximage_findresource (const char *ident)
{
    struct ic_ *ic = &_ic;
    int         id = -1;
    pdf_ximage *I;

    for (id = 0; id < ic->count; id++) {
        I = &ic->ximages[id];
        if (I->ident && !strcmp(ident, I->ident)) {
            return id;
        }
    }

    return -1;
}

/* Reference: PDF Reference 1.5 v6, pp.321--322
 *
 * TABLE 4.42 Additional entries specific to a type 1 form dictionary
 *
 * BBox rectangle (Required) An array of four numbers in the form coordinate
 *                system, giving the coordinates of the left, bottom, right,
 *                and top edges, respectively, of the form XObject's bounding
 *                box. These boundaries are used to clip the form XObject and
 *                to determine its size for caching.
 *
 * Matrix array   (Optional) An array of six numbers specifying the form
 *                matrix, which maps form space into user space.
 *                Default value: the identity matrix [1 0 0 1 0 0].
 */
void
pdf_ximage_init_form_info (xform_info *info)
{
    info->flags    = 0;
    info->bbox.llx = 0;
    info->bbox.lly = 0;
    info->bbox.urx = 0;
    info->bbox.ury = 0;
    info->matrix.a = 1.0;
    info->matrix.b = 0.0;
    info->matrix.c = 0.0;
    info->matrix.d = 1.0;
    info->matrix.e = 0.0;
    info->matrix.f = 0.0;
}

/* Reference: PDF Reference 1.5 v6, pp.303--306
 *
 * TABLE 4.42 Additional entries specific to an image dictionary
 *
 * Width integer  (Required) The width of the image, in samples.
 *
 * Height integer (Required) The height of the image, in samples.
 *
 * ColorSpace name or array
 *                (Required for images, except those that use the JPXDecode
 *                filter; not allowed for image masks) The color space in
 *                which image samples are specified. This may be any type
 *                of color space except Patter.
 *
 *                If the image uses the JPXDecode filter, this entry is
 *                optional.
 *
 * BitsPerComponent integer
 *                (Required except for image masks and images that use the
 *                JPXDecode filter) The number of bits used to represent
 *                each color component. Only a single value may be specified;
 *                the number of bits is the same for all color components.
 *                Valid values are 1,2,4,8, and (in PDF1.5) 16. If ImageMask
 *                is true, this entry is optional, and if speficified, its
 *                value must be 1.
 *
 *                If the image stream uses the JPXDecode filter, this entry
 *                is optional and ignored if present. The bit depth is
 *                determined in the process of decoding the JPEG2000 image.
 */
void
pdf_ximage_init_image_info (ximage_info *info)
{
    info->flags  = 0;
    info->width  = 0;
    info->height = 0;
    info->bits_per_component = 0;
    info->num_components = 0;
    info->min_dpi = 0;
    info->xdensity = info->ydensity = 1.0;
}

void
pdf_ximage_set_image (pdf_ximage *I, void *image_info, pdf_obj *resource)
{
    pdf_obj     *dict;
    ximage_info *info = image_info;

    if (!PDF_OBJ_STREAMTYPE(resource))
        _tt_abort("Image XObject must be of stream type.");

    I->subtype = PDF_XOBJECT_TYPE_IMAGE;

    I->attr.width  = info->width;  /* The width of the image, in samples */
    I->attr.height = info->height; /* The height of the image, in samples */
    I->attr.xdensity = info->xdensity;
    I->attr.ydensity = info->ydensity;

    dict = pdf_stream_dict(resource);
    pdf_add_dict(dict, pdf_new_name("Type"),    pdf_new_name("XObject"));
    pdf_add_dict(dict, pdf_new_name("Subtype"), pdf_new_name("Image"));
    pdf_add_dict(dict, pdf_new_name("Width"),   pdf_new_number(info->width));
    pdf_add_dict(dict, pdf_new_name("Height"),  pdf_new_number(info->height));
    if (info->bits_per_component > 0) /* Ignored for JPXDecode filter. FIXME */
        pdf_add_dict(dict, pdf_new_name("BitsPerComponent"),
                     pdf_new_number(info->bits_per_component));
    if (I->attr.dict)
        pdf_merge_dict(dict, I->attr.dict);

    if (I->ident) {
        int error;

        error = pdf_names_add_object(global_names, I->ident, strlen(I->ident), pdf_link_obj(resource));
        if (I->reference)
            pdf_release_obj(I->reference);
        if (error) {
            I->reference = pdf_ref_obj(resource);
        } else {
            /* Need to create object reference before closing it */
            I->reference = pdf_names_lookup_reference(global_names, I->ident, strlen(I->ident));
            pdf_names_close_object(global_names, I->ident, strlen(I->ident));
        }
        I->reserved = 0;
    } else {
        I->reference = pdf_ref_obj(resource);
    }

    pdf_release_obj(resource); /* Caller don't know we are using reference. */
    I->resource  = NULL;
}

void
pdf_ximage_set_form (pdf_ximage *I, void *form_info, pdf_obj *resource)
{
    xform_info *info = form_info;
    pdf_coord p1, p2, p3, p4;

    I->subtype   = PDF_XOBJECT_TYPE_FORM;

    /* Image's attribute "bbox" here is affected by /Rotate entry of included
     * PDF page.
     */
    p1.x = info->bbox.llx; p1.y = info->bbox.lly;
    pdf_dev_transform(&p1, &info->matrix);
    p2.x = info->bbox.urx; p2.y = info->bbox.lly;
    pdf_dev_transform(&p2, &info->matrix);
    p3.x = info->bbox.urx; p3.y = info->bbox.ury;
    pdf_dev_transform(&p3, &info->matrix);
    p4.x = info->bbox.llx; p4.y = info->bbox.ury;
    pdf_dev_transform(&p4, &info->matrix);

    I->attr.bbox.llx = min4(p1.x, p2.x, p3.x, p4.x);
    I->attr.bbox.lly = min4(p1.y, p2.y, p3.y, p4.y);
    I->attr.bbox.urx = max4(p1.x, p2.x, p3.x, p4.x);
    I->attr.bbox.ury = max4(p1.y, p2.y, p3.y, p4.y);

    if (I->ident) {
        int error;

        error = pdf_names_add_object(global_names, I->ident, strlen(I->ident), pdf_link_obj(resource));
        if (I->reference)
            pdf_release_obj(I->reference);
        if (error) {
            I->reference = pdf_ref_obj(resource);
        } else {
            /* Need to create object reference before closing it */
            I->reference = pdf_names_lookup_reference(global_names, I->ident, strlen(I->ident));
            pdf_names_close_object(global_names, I->ident, strlen(I->ident));
        }
        I->reserved = 0;
    } else {
        I->reference = pdf_ref_obj(resource);
    }

    pdf_release_obj(resource); /* Caller don't know we are using reference. */
    I->resource  = NULL;
}

int
pdf_ximage_get_page (pdf_ximage *I)
{
    return I->attr.page_no;
}

#define CHECK_ID(c,n) do {                              \
        if ((n) < 0 || (n) >= (c)->count) {             \
            _tt_abort("Invalid XObject ID: %d", (n));       \
        }                                               \
    } while (0)
#define GET_IMAGE(c,n) (&((c)->ximages[(n)]))

pdf_obj *
pdf_ximage_get_reference (int id)
{
    struct ic_ *ic = &_ic;
    pdf_ximage *I;

    CHECK_ID(ic, id);

    I = GET_IMAGE(ic, id);
    if (!I->reference && I->resource)
        I->reference = pdf_ref_obj(I->resource);

    return pdf_link_obj(I->reference);
}

/* called from pdfdoc.c only for late binding */
int
pdf_ximage_defineresource (const char *ident,
                           int subtype, void *info, pdf_obj *resource)
{
    struct ic_ *ic = &_ic;
    int         id, reserved = 0;
    pdf_ximage *I;

    if (ident) {
        for (id = 0; id < ic->count; id++) {
            I = &ic->ximages[id];
            if (I->ident && !strcmp(ident, I->ident) && I->reserved) {
                reserved = 1;
                break;
            }
        }
    }

    if (!reserved) {
        id = ic->count;
        if (ic->count >= ic->capacity) {
            ic->capacity += 16;
            ic->ximages   = RENEW(ic->ximages, ic->capacity, pdf_ximage);
        }
        I = &ic->ximages[id];
        pdf_init_ximage_struct(I);

        if (ident) {
            I->ident = NEW(strlen(ident)+1, char);
            strcpy(I->ident, ident);
        }
        ic->count++;
    }

    switch (subtype) {
    case PDF_XOBJECT_TYPE_IMAGE:
        pdf_ximage_set_image(I, info, resource);
        sprintf(I->res_name, "Im%d", id);
        break;
    case PDF_XOBJECT_TYPE_FORM:
        pdf_ximage_set_form (I, info, resource);
        sprintf(I->res_name, "Fm%d", id);
        break;
    default:
        _tt_abort("Unknown XObject subtype: %d", subtype);
    }

    return  id;
}

int
pdf_ximage_reserve (const char *ident)
{
    struct ic_ *ic = &_ic;
    int         id;
    pdf_ximage *I;

    for (id = 0; id < ic->count; id++) {
        I = &ic->ximages[id];
        if (I->ident && !strcmp(ident, I->ident)) {
            dpx_warning("XObject ID \"%s\" already used!", ident);
            return -1;
        }
    }

    id = ic->count;
    if (ic->count >= ic->capacity) {
        ic->capacity += 16;
        ic->ximages   = RENEW(ic->ximages, ic->capacity, pdf_ximage);
    }

    I = &ic->ximages[id];

    pdf_init_ximage_struct(I);

    if (ident) {
        I->ident = NEW(strlen(ident)+1, char);
        strcpy(I->ident, ident);
    }
    I->reference  = pdf_names_reserve(global_names, ident, strlen(ident));
    sprintf(I->res_name, "Fm%d", id);
    I->reserved = 1;
    ic->count++;

    return id;
}

char *
pdf_ximage_get_resname (int id)
{
    struct ic_ *ic = &_ic;
    pdf_ximage *I;

    CHECK_ID(ic, id);

    I = GET_IMAGE(ic, id);

    return I->res_name;
}

int
pdf_ximage_get_subtype (int id)
{
    struct ic_ *ic = &_ic;
    pdf_ximage *I;

    CHECK_ID(ic, id);

    I = GET_IMAGE(ic, id);

    return I->subtype;
}

void
pdf_ximage_set_attr (int id, int width, int height, double xdensity, double ydensity, double llx, double lly, double urx, double ury)
{
    struct ic_ *ic = &_ic;
    pdf_ximage *I;

    CHECK_ID(ic, id);

    I = GET_IMAGE(ic, id);
    I->attr.width = width;
    I->attr.height = height;
    I->attr.xdensity = xdensity;
    I->attr.ydensity = ydensity;
    I->attr.bbox.llx = llx;
    I->attr.bbox.lly = lly;
    I->attr.bbox.urx = urx;
    I->attr.bbox.ury = ury;
}

/* depth...
 * Dvipdfm treat "depth" as "yoffset" for pdf:image and pdf:uxobj
 * not as vertical dimension of scaled image. (And there are bugs.)
 * This part contains incompatibile behaviour than dvipdfm!
 */
#define EBB_DPI 72

static void
scale_to_fit_I (pdf_tmatrix    *T,
                transform_info *p,
                pdf_ximage     *I)
{
    double  s_x, s_y, d_x, d_y;
    double  wd0, ht0, dp, xscale, yscale;

    if (p->flags & INFO_HAS_USER_BBOX) {
        wd0 =  p->bbox.urx - p->bbox.llx;
        ht0 =  p->bbox.ury - p->bbox.lly;
        xscale = I->attr.width * I->attr.xdensity / wd0;
        yscale = I->attr.height * I->attr.ydensity / ht0;
        d_x = -p->bbox.llx / wd0;
        d_y = -p->bbox.lly / ht0;
    } else {
        wd0 = I->attr.width * I->attr.xdensity;
        ht0 = I->attr.height * I->attr.ydensity;
        xscale = yscale = 1.0;
        d_x = 0.0;
        d_y = 0.0;
    }

    if (wd0 == 0.0) {
        dpx_warning("Image width=0.0!");
        wd0 = 1.0;
    }
    if (ht0 == 0.0) {
        dpx_warning("Image height=0.0!");
        ht0 = 1.0;
    }

    if ( (p->flags & INFO_HAS_WIDTH ) &&
         (p->flags & INFO_HAS_HEIGHT) ) {
        s_x = p->width * xscale;
        s_y = (p->height + p->depth) * yscale;
        dp  = p->depth * yscale;
    } else if ( p->flags & INFO_HAS_WIDTH ) {
        s_x = p->width * xscale;
        s_y = s_x * ((double)I->attr.height / I->attr.width);
        dp  = 0.0;
    } else if ( p->flags & INFO_HAS_HEIGHT) {
        s_y = (p->height + p->depth) * yscale;
        s_x = s_y * ((double)I->attr.width / I->attr.height);
        dp  = p->depth * yscale;
    } else {
        s_x = wd0;
        s_y = ht0;
        dp  = 0.0;
    }
    T->a = s_x; T->c = 0.0;
    T->b = 0.0; T->d = s_y;
    T->e = d_x * s_x / xscale; T->f = d_y * s_y / yscale - dp;

    return;
}


static void
scale_to_fit_F (pdf_tmatrix    *T,
                transform_info *p,
                pdf_ximage     *I)
{
    double  s_x, s_y, d_x, d_y;
    double  wd0, ht0, dp;

    if (p->flags & INFO_HAS_USER_BBOX) {
        wd0 =  p->bbox.urx - p->bbox.llx;
        ht0 =  p->bbox.ury - p->bbox.lly;
        d_x = -p->bbox.llx;
        d_y = -p->bbox.lly;
    } else {
        wd0 = I->attr.bbox.urx - I->attr.bbox.llx;
        ht0 = I->attr.bbox.ury - I->attr.bbox.lly;
        d_x = 0.0;
        d_y = 0.0;
    }

    if (wd0 == 0.0) {
        dpx_warning("Image width=0.0!");
        wd0 = 1.0;
    }
    if (ht0 == 0.0) {
        dpx_warning("Image height=0.0!");
        ht0 = 1.0;
    }

    if ( (p->flags & INFO_HAS_WIDTH ) &&
         (p->flags & INFO_HAS_HEIGHT) ) {
        s_x = p->width  / wd0;
        s_y = (p->height + p->depth) / ht0;
        dp  = p->depth;
    } else if ( p->flags & INFO_HAS_WIDTH ) {
        s_x = p->width  / wd0;
        s_y = s_x;
        dp  = 0.0;
    } else if ( p->flags & INFO_HAS_HEIGHT) {
        s_y = (p->height + p->depth) / ht0;
        s_x = s_y;
        dp  = p->depth;
    } else {
        s_x = s_y = 1.0;
        dp  = 0.0;
    }

    T->a = s_x; T->c = 0.0;
    T->b = 0.0; T->d = s_y;
    T->e = s_x * d_x; T->f = s_y * d_y - dp;

    return;
}


/* called from pdfdev.c and spc_html.c */
int
pdf_ximage_scale_image (int            id,
                        pdf_tmatrix    *M, /* return value for trans matrix */
                        pdf_rect       *r, /* return value for clipping */
                        transform_info *p  /* argument from specials */
    )
{
    struct ic_ *ic = &_ic;
    pdf_ximage *I;

    CHECK_ID(ic, id);

    I = GET_IMAGE(ic, id);

    pdf_setmatrix(M, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);

    switch (I->subtype) {
        /* Reference: PDF Reference 1.5 v6, p.302
         *
         * An image can be placed on the output page in any desired position,
         * orientation, and size by using the cm operator to modify the current
         * transformation matrix (CTM) so as to map the unit square of user space
         * to the rectangle or parallelogram in which the image is to be painted.
         *
         * There is neither BBox nor Matrix key in the image XObject.
         * Everything must be controlled by the cm operator.
         *
         * The argument [p] contains the user-defined bounding box, the scailing
         * factor of which is bp as EPS and PDF. On the other hand, I->attr
         * contains the (sampling) width and the (sampling) height of the image.
         *
         * There is no problem if a bitmap image has density information.
         * Otherwise, DVIPDFM's ebb generates bounding box as 100px = 72bp = 1in.
         * In this case, screen captured images look bad. Moreover, DVIPDFM's ebb
         * ignores all density information and use just 100px = 72bp = 1in.
         *
         * On the other hand, pdfTeX uses 100px = 100bp to get a better quality
         * for screen captured images.
         *
         * DVIPDFMx's xbb generates bounding box as 100px = 100bp in the same
         * way as pdfTeX. Furthermore, it takes care of density information too.
         */
    case PDF_XOBJECT_TYPE_IMAGE:
        scale_to_fit_I(M, p, I);
        if (p->flags & INFO_HAS_USER_BBOX) {
            r->llx = p->bbox.llx / (I->attr.width * I->attr.xdensity);
            r->lly = p->bbox.lly / (I->attr.height * I->attr.ydensity);
            r->urx = p->bbox.urx / (I->attr.width * I->attr.xdensity);
            r->ury = p->bbox.ury / (I->attr.height * I->attr.ydensity);
        } else {
            r->llx = 0.0;
            r->lly = 0.0;
            r->urx = 1.0;
            r->ury = 1.0;
        }
        break;
        /* User-defined transformation and clipping are controlled by
         * the cm operator and W operator, explicitly */
    case PDF_XOBJECT_TYPE_FORM:
        scale_to_fit_F(M, p, I);
        if (p->flags & INFO_HAS_USER_BBOX) {
            r->llx = p->bbox.llx;
            r->lly = p->bbox.lly;
            r->urx = p->bbox.urx;
            r->ury = p->bbox.ury;
        } else { /* I->attr.bbox from the image bounding box */
            r->llx = I->attr.bbox.llx;
            r->lly = I->attr.bbox.lly;
            r->urx = I->attr.bbox.urx;
            r->ury = I->attr.bbox.ury;
        }
        break;
    default: /* maybe reserved */
        if (p->flags & INFO_HAS_USER_BBOX) {
            r->llx = p->bbox.llx;
            r->lly = p->bbox.lly;
            r->urx = p->bbox.urx;
            r->ury = p->bbox.ury;
        } else { /* I->attr.bbox from the image bounding box */
            r->llx = 0.0;
            r->lly = 0.0;
            r->urx = 1.0;
            r->ury = 1.0;
        }
    }

    return  0;
}


/* Migrated from psimage.c */

void set_distiller_template (char *s)
{
    free(_opts.cmdtmpl);
    if (!s || *s == '\0')
        _opts.cmdtmpl = NULL;
    else {
        _opts.cmdtmpl = NEW(strlen(s) + 1, char);
        strcpy(_opts.cmdtmpl, s);
    }
    return;
}

char *get_distiller_template (void)
{
    return _opts.cmdtmpl;
}

static int
check_for_ps (rust_input_handle_t handle)
{
    ttstub_input_seek(handle, 0, SEEK_SET);
    tt_mfgets (work_buffer, WORK_BUFFER_SIZE, handle);
    if (strstartswith(work_buffer, "%!"))
        return 1;
    return 0;
}

/* Cannot use _tt_abort() (dpx: "ERROR()") here */
void
pdf_error_cleanup_cache (void)
{
  struct ic_ *ic = &_ic;
  int         i;
  pdf_ximage *I;

  for (i = 0; i < ic->count; i++) {
    I = &ic->ximages[i];
    if (I->attr.tempfile) {
      dpx_delete_temp_file(I->fullname, false); /* temporary filename freed here */
    }
  }
}
