/****************************************************************************\
 Part of the XeTeX typesetting system
 Copyright (c) 1994-2008 by SIL International
 Copyright (c) 2009 by Jonathan Kew

 SIL Author(s): Jonathan Kew

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the copyright holders
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holders.
\****************************************************************************/

/*
XeTeX_pic.c
   interface between xetex and graphics files
   only needs to get image dimensions, not actually load/process the file
*/

#include "xetex-core.h"
#include "xetex-xetexd.h"
#include "xetex-ext.h"
#include "dpx-dpxutil.h"
#include "dpx-pdfdoc.h"
#include "dpx-pdfdraw.h"
#include "dpx-pdfobj.h"
#include "dpx-pngimage.h"
#include "dpx-jpegimage.h"
#include "dpx-bmpimage.h"


/* load_picture() needs some helper types and functions */

typedef struct {
    float x;
    float y;
} real_point;

typedef struct {
    double a;
    double b;
    double c;
    double d;
    double x;
    double y;
} transform_t;

typedef struct {
    float x;
    float y;
    float wd;
    float ht;
} real_rect;


int
count_pdf_file_pages (void)
{
    int pages;
    rust_input_handle_t handle;
    pdf_file *pf;

    handle = ttstub_input_open (name_of_file, TTBC_FILE_FORMAT_PICT, 0);
    if (handle == INVALID_HANDLE)
        return 0;

    if ((pf = pdf_open(name_of_file, handle)) == NULL) {
        /* TODO: issue warning */
        ttstub_input_close(handle);
        return 0;
    }

    pages = pdf_doc_get_page_count(pf);
    pdf_close(pf);
    ttstub_input_close(handle);
    return pages;
}


static int
pdf_get_rect (char *filename, rust_input_handle_t handle, int page_num, int pdf_box, real_rect* box)
{
    int pages, dpx_options;
    pdf_file *pf;
    pdf_obj *page;
    pdf_rect bbox;
    pdf_tmatrix matrix;
    pdf_coord p1, p2, p3, p4;

    if ((pf = pdf_open(filename, handle)) == NULL) {
        /* TODO: issue warning */
        return -1;
    }

    pages = pdf_doc_get_page_count(pf);

    if (page_num > pages)
        page_num = pages;
    if (page_num < 0)
        page_num = pages + 1 + page_num;
    if (page_num < 1)
        page_num = 1;

    /* OMG, magic numbers specifying page bound types do not agree between
     * xdvipdfmx code (dpx-pdfdoc.c:pdf_doc_get_page) and XeTeX/Apple's
     * pdfbox_* definitions (xetex-ext.h). */

    switch (pdf_box) {
    case pdfbox_media:
        dpx_options = 2;
        break;
    case pdfbox_bleed:
        dpx_options = 5;
        break;
    case pdfbox_trim:
        dpx_options = 4;
        break;
    case pdfbox_art:
        dpx_options = 3;
        break;
    case pdfbox_crop:
    default:
        dpx_options = 1;
        break;
    }

    page = pdf_doc_get_page(pf, page_num, dpx_options, &bbox, &matrix, NULL);
    pdf_close(pf);

    if (page == NULL) {
        /* TODO: issue warning */
        return -1;
    }

    pdf_release_obj(page);

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

    p2.x = bbox.urx;
    p2.y = bbox.lly;
    pdf_dev_transform(&p2, &matrix);

    p3.x = bbox.urx;
    p3.y = bbox.ury;
    pdf_dev_transform(&p3, &matrix);

    p4.x = bbox.llx;
    p4.y = bbox.ury;
    pdf_dev_transform(&p4, &matrix);

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

    box->x = 72.27 / 72 * bbox.llx;
    box->y = 72.27 / 72 * bbox.lly;
    box->wd = 72.27 / 72 * (bbox.urx - bbox.llx);
    box->ht = 72.27 / 72 * (bbox.ury - bbox.lly);

    return 0;
}


/* Tectonic customization: use double-precision (see call site for explanation) */
static int
get_image_size_in_inches (rust_input_handle_t handle, double *width, double *height)
{
    int err = 1;
    unsigned int width_pix, height_pix;
    double xdensity, ydensity;

    if (check_for_jpeg(handle))
        err = jpeg_get_bbox(handle, &width_pix, &height_pix, &xdensity, &ydensity);
    else if (check_for_bmp(handle))
        err = bmp_get_bbox(handle, &width_pix, &height_pix, &xdensity, &ydensity);
    else if (check_for_png(handle))
        err = png_get_bbox(handle, &width_pix, &height_pix, &xdensity, &ydensity);

    if (err) {
        *width = -1;
        *height = -1;
        return err;
    }

    /* xdvipdfmx defines density = 72 / dpi, so ... */
    *width = width_pix * xdensity / 72;
    *height = height_pix * ydensity / 72;
    return 0;
}

/*
  pdfBoxType indicates which pdf bounding box to use (0 for \XeTeXpicfile)
  page indicates which page is wanted (0-based)
  return 0 for success, or non-zero error code for failure
  return full path in *path
  return bounds (tex points) in *bounds
*/
static int
find_pic_file (char **path, real_rect *bounds, int pdfBoxType, int page)
{
    int err = -1;
    rust_input_handle_t handle;

    handle = ttstub_input_open (name_of_file, TTBC_FILE_FORMAT_PICT, 0);
    bounds->x = bounds->y = bounds->wd = bounds->ht = 0.0;

    if (handle == INVALID_HANDLE)
        return 1;

    if (pdfBoxType != 0) {
        /* if cmd was \XeTeXpdffile, use xpdflib to read it */
        err = pdf_get_rect (name_of_file, handle, page, pdfBoxType, bounds);
    } else {
        /* Tectonic customization: if we use single-precision math, we can
         * sometimes get numerical results that vary depending on whether we're
         * compiled in debug or release mode (?!) */
        double wd, ht;
        err = get_image_size_in_inches (handle, &wd, &ht);
        bounds->wd = wd * 72.27;
        bounds->ht = ht * 72.27;
    }

    if (err == 0)
        *path = xstrdup(name_of_file);

    ttstub_input_close (handle);

    return err;
}


static void
transform_point(real_point* p, const transform_t* t)
{
    real_point r;

    r.x = t->a * p->x + t->c * p->y + t->x;
    r.y = t->b * p->x + t->d * p->y + t->y;

    *p = r;
}


static void
make_identity(transform_t* t)
{
    t->a = 1.0;
    t->b = 0.0;
    t->c = 0.0;
    t->d = 1.0;
    t->x = 0.0;
    t->y = 0.0;
}


static void
make_scale(transform_t* t, double xscale, double yscale)
{
    t->a = xscale;
    t->b = 0.0;
    t->c = 0.0;
    t->d = yscale;
    t->x = 0.0;
    t->y = 0.0;
}


static void
make_translation(transform_t* t, double dx, double dy)
{
    t->a = 1.0;
    t->b = 0.0;
    t->c = 0.0;
    t->d = 1.0;
    t->x = dx;
    t->y = dy;
}


static void
make_rotation(transform_t* t, double a)
{
    t->a = cos(a);
    t->b = sin(a);
    t->c = -sin(a);
    t->d = cos(a);
    t->x = 0.0;
    t->y = 0.0;
}


static void
transform_concat(transform_t* t1, const transform_t* t2)
{
    transform_t r;

    r.a = t1->a * t2->a + t1->b * t2->c + 0.0 * t2->x;
    r.b = t1->a * t2->b + t1->b * t2->d + 0.0 * t2->y;
    r.c = t1->c * t2->a + t1->d * t2->c + 0.0 * t2->x;
    r.d = t1->c * t2->b + t1->d * t2->d + 0.0 * t2->y;
    r.x = t1->x * t2->a + t1->y * t2->c + 1.0 * t2->x;
    r.y = t1->x * t2->b + t1->y * t2->d + 1.0 * t2->y;

    *t1 = r;
}


#define SET_POINT(P,X,Y) do { (P).x = (X); (P).y = (Y); } while (0)

void
load_picture(bool is_pdf)
{
    char *pic_path;
    real_rect bounds;
    transform_t t, t2;
    real_point corners[4];
    double x_size_req, y_size_req;
    bool check_keywords;
    double xmin, xmax, ymin, ymax;
    small_number i;
    int32_t page;
    int32_t pdf_box_type;
    int32_t result;
    scan_file_name();
    pack_file_name(cur_name, cur_area, cur_ext);
    pdf_box_type = 0;
    page = 0;
    if (is_pdf) {
        if (scan_keyword("page")) {
            scan_int();
            page = cur_val;
        }
        pdf_box_type = pdfbox_none;
        if (scan_keyword("crop"))
            pdf_box_type = pdfbox_crop;
        else if (scan_keyword("media"))
            pdf_box_type = pdfbox_media;
        else if (scan_keyword("bleed"))
            pdf_box_type = pdfbox_bleed;
        else if (scan_keyword("trim"))
            pdf_box_type = pdfbox_trim;
        else if (scan_keyword("art"))
            pdf_box_type = pdfbox_art;
    }
    if (pdf_box_type == pdfbox_none)
        result = find_pic_file(&pic_path, &bounds, pdfbox_crop, page);
    else
        result = find_pic_file(&pic_path, &bounds, pdf_box_type, page);
    SET_POINT(corners[0], bounds.x, bounds.y);
    SET_POINT(corners[1], corners[0].x, bounds.y + bounds.ht);
    SET_POINT(corners[2], bounds.x + bounds.wd, corners[1].y);
    SET_POINT(corners[3], corners[2].x, corners[0].y);
    x_size_req = 0.0;
    y_size_req = 0.0;
    make_identity(&t);
    check_keywords = true;
    while (check_keywords) {

        if (scan_keyword("scaled")) {
            scan_int();
            if ((x_size_req == 0.0) && (y_size_req == 0.0)) {
                make_scale(&t2, cur_val / ((double)1000.0), cur_val / ((double)1000.0));
                {
                    register int32_t for_end;
                    i = 0;
                    for_end = 3;
                    if (i <= for_end)
                        do
                            transform_point(&corners[i], &t2);
                        while (i++ < for_end);
                }
                transform_concat(&t, &t2);
            }
        } else if (scan_keyword("xscaled")) {
            scan_int();
            if ((x_size_req == 0.0) && (y_size_req == 0.0)) {
                make_scale(&t2, cur_val / ((double)1000.0), 1.0);
                {
                    register int32_t for_end;
                    i = 0;
                    for_end = 3;
                    if (i <= for_end)
                        do
                            transform_point(&corners[i], &t2);
                        while (i++ < for_end);
                }
                transform_concat(&t, &t2);
            }
        } else if (scan_keyword("yscaled")) {
            scan_int();
            if ((x_size_req == 0.0) && (y_size_req == 0.0)) {
                make_scale(&t2, 1.0, cur_val / ((double)1000.0));
                {
                    register int32_t for_end;
                    i = 0;
                    for_end = 3;
                    if (i <= for_end)
                        do
                            transform_point(&corners[i], &t2);
                        while (i++ < for_end);
                }
                transform_concat(&t, &t2);
            }
        } else if (scan_keyword("width")) {
            scan_dimen(false, false, false);
            if (cur_val <= 0) {
                error_here_with_diagnostic("Improper image ");
                print_cstr("size (");
                print_scaled(cur_val);
                print_cstr("pt) will be ignored");
                capture_to_diagnostic(NULL);
                {
                    help_ptr = 2;
                    help_line[1] = "I can't scale images to zero or negative sizes,";
                    help_line[0] = "so I'm ignoring this.";
                }
                error();
            } else
                x_size_req = Fix2D(cur_val);
        } else if (scan_keyword("height")) {
            scan_dimen(false, false, false);
            if (cur_val <= 0) {
                error_here_with_diagnostic("Improper image ");
                print_cstr("size (");
                print_scaled(cur_val);
                print_cstr("pt) will be ignored");
                capture_to_diagnostic(NULL);
                {
                    help_ptr = 2;
                    help_line[1] = "I can't scale images to zero or negative sizes,";
                    help_line[0] = "so I'm ignoring this.";
                }
                error();
            } else
                y_size_req = Fix2D(cur_val);
        } else if (scan_keyword("rotated")) {
            scan_decimal();
            if ((x_size_req != 0.0) || (y_size_req != 0.0)) {
                {
                    xmin = 1000000.0;
                    xmax = -(int32_t) xmin;
                    ymin = xmin;
                    ymax = xmax;
                    {
                        register int32_t for_end;
                        i = 0;
                        for_end = 3;
                        if (i <= for_end)
                            do {
                                if (corners[i].x < xmin)
                                    xmin = corners[i].x;
                                if (corners[i].x > xmax)
                                    xmax = corners[i].x;
                                if (corners[i].y < ymin)
                                    ymin = corners[i].y;
                                if (corners[i].y > ymax)
                                    ymax = corners[i].y;
                            }
                            while (i++ < for_end);
                    }
                }
                if (x_size_req == 0.0) {
                    make_scale(&t2, y_size_req / ((double)(ymax - ymin)),
                               y_size_req / ((double)(ymax - ymin)));
                } else if (y_size_req == 0.0) {
                    make_scale(&t2, x_size_req / ((double)(xmax - xmin)),
                               x_size_req / ((double)(xmax - xmin)));
                } else {

                    make_scale(&t2, x_size_req / ((double)(xmax - xmin)),
                               y_size_req / ((double)(ymax - ymin)));
                }
                {
                    register int32_t for_end;
                    i = 0;
                    for_end = 3;
                    if (i <= for_end)
                        do
                            transform_point(&corners[i], &t2);
                        while (i++ < for_end);
                }
                x_size_req = 0.0;
                y_size_req = 0.0;
                transform_concat(&t, &t2);
            }
            make_rotation(&t2, Fix2D(cur_val) * M_PI / ((double)180.0));
            {
                register int32_t for_end;
                i = 0;
                for_end = 3;
                if (i <= for_end)
                    do
                        transform_point(&corners[i], &t2);
                    while (i++ < for_end);
            }
            {
                xmin = 1000000.0;
                xmax = -(int32_t) xmin;
                ymin = xmin;
                ymax = xmax;
                {
                    register int32_t for_end;
                    i = 0;
                    for_end = 3;
                    if (i <= for_end)
                        do {
                            if (corners[i].x < xmin)
                                xmin = corners[i].x;
                            if (corners[i].x > xmax)
                                xmax = corners[i].x;
                            if (corners[i].y < ymin)
                                ymin = corners[i].y;
                            if (corners[i].y > ymax)
                                ymax = corners[i].y;
                        }
                        while (i++ < for_end);
                }
            }
            SET_POINT(corners[0], xmin, ymin);
            SET_POINT(corners[1], xmin, ymax);
            SET_POINT(corners[2], xmax, ymax);
            SET_POINT(corners[3], xmax, ymin);
            transform_concat(&t, &t2);
        } else
            check_keywords = false;
    }
    if ((x_size_req != 0.0) || (y_size_req != 0.0)) {
        {
            xmin = 1000000.0;
            xmax = -(int32_t) xmin;
            ymin = xmin;
            ymax = xmax;
            {
                register int32_t for_end;
                i = 0;
                for_end = 3;
                if (i <= for_end)
                    do {
                        if (corners[i].x < xmin)
                            xmin = corners[i].x;
                        if (corners[i].x > xmax)
                            xmax = corners[i].x;
                        if (corners[i].y < ymin)
                            ymin = corners[i].y;
                        if (corners[i].y > ymax)
                            ymax = corners[i].y;
                    }
                    while (i++ < for_end);
            }
        }
        if (x_size_req == 0.0) {
            make_scale(&t2, y_size_req / ((double)(ymax - ymin)), y_size_req / ((double)(ymax - ymin)));
        } else if (y_size_req == 0.0) {
            make_scale(&t2, x_size_req / ((double)(xmax - xmin)), x_size_req / ((double)(xmax - xmin)));
        } else {

            make_scale(&t2, x_size_req / ((double)(xmax - xmin)), y_size_req / ((double)(ymax - ymin)));
        }
        {
            register int32_t for_end;
            i = 0;
            for_end = 3;
            if (i <= for_end)
                do
                    transform_point(&corners[i], &t2);
                while (i++ < for_end);
        }
        x_size_req = 0.0;
        y_size_req = 0.0;
        transform_concat(&t, &t2);
    }
    {
        xmin = 1000000.0;
        xmax = -(int32_t) xmin;
        ymin = xmin;
        ymax = xmax;
        {
            register int32_t for_end;
            i = 0;
            for_end = 3;
            if (i <= for_end)
                do {
                    if (corners[i].x < xmin)
                        xmin = corners[i].x;
                    if (corners[i].x > xmax)
                        xmax = corners[i].x;
                    if (corners[i].y < ymin)
                        ymin = corners[i].y;
                    if (corners[i].y > ymax)
                        ymax = corners[i].y;
                }
                while (i++ < for_end);
        }
    }
    make_translation(&t2, -(int32_t) xmin * 72 / ((double)72.27), -(int32_t) ymin * 72 / ((double)72.27));
    transform_concat(&t, &t2);
    if (result == 0) {
        new_whatsit(PIC_NODE,
                    PIC_NODE_SIZE + (strlen(pic_path) + sizeof(memory_word) - 1) / sizeof(memory_word));
        if (is_pdf) {
            mem[cur_list.tail].b16.s0 = PDF_NODE;
        }
        PIC_NODE_path_len(cur_list.tail) = strlen(pic_path);
        mem[cur_list.tail + 4].b16.s0 = page;
        mem[cur_list.tail + 8].b16.s1 = pdf_box_type;
        mem[cur_list.tail + 1].b32.s1 = D2Fix(xmax - xmin);
        mem[cur_list.tail + 3].b32.s1 = D2Fix(ymax - ymin);
        mem[cur_list.tail + 2].b32.s1 = 0;
        mem[cur_list.tail + 5].b32.s0 = D2Fix(t.a);
        mem[cur_list.tail + 5].b32.s1 = D2Fix(t.b);
        mem[cur_list.tail + 6].b32.s0 = D2Fix(t.c);
        mem[cur_list.tail + 6].b32.s1 = D2Fix(t.d);
        mem[cur_list.tail + 7].b32.s0 = D2Fix(t.x);
        mem[cur_list.tail + 7].b32.s1 = D2Fix(t.y);
        memcpy(PIC_NODE_path(cur_list.tail), pic_path, strlen(pic_path));
        free(pic_path);
    } else {

        error_here_with_diagnostic("Unable to load picture or PDF file '");
        print_file_name(cur_name, cur_area, cur_ext);
        print('\'');
        capture_to_diagnostic(NULL);
        if (result == -43) {
            {
                help_ptr = 2;
                help_line[1] = "The requested image couldn't be read because";
                help_line[0] = "the file was not found.";
            }
        } else {

            {
                help_ptr = 2;
                help_line[1] = "The requested image couldn't be read because";
                help_line[0] = "it was not a recognized image format.";
            }
        }
        error();
    }
}
