#include "ghostxps.h"

#include <ctype.h>

#define XPS_TEXT_BUFFER_SIZE 300

typedef struct xps_text_buffer_s xps_text_buffer_t;

struct xps_text_buffer_s
{
    int count;
    float x[XPS_TEXT_BUFFER_SIZE + 1];
    float y[XPS_TEXT_BUFFER_SIZE + 1];
    gs_glyph g[XPS_TEXT_BUFFER_SIZE];
};

static inline int unhex(int i)
{
    if (isdigit(i))
	return i - '0';
    return tolower(i) - 'a' + 10;
}

void xps_debug_path(xps_context_t *ctx)
{
    segment *seg;
    curve_segment *cseg;

    seg = (segment*)ctx->pgs->path->first_subpath;
    while (seg)
    {
        switch (seg->type)
        {
        case s_start:
	    dprintf2("%g %g moveto\n", 
                    fixed2float(seg->pt.x) * 0.001,
                    fixed2float(seg->pt.y) * 0.001);
            break;
        case s_line:
	    dprintf2("%g %g lineto\n", 
                    fixed2float(seg->pt.x) * 0.001,
                    fixed2float(seg->pt.y) * 0.001);
            break;
        case s_line_close:
	    dputs("closepath\n");
            break;
        case s_curve:
            cseg = (curve_segment*)seg;
	    dprintf6("%g %g %g %g %g %g curveto\n", 
                    fixed2float(cseg->p1.x) * 0.001,
                    fixed2float(cseg->p1.y) * 0.001,
                    fixed2float(cseg->p2.x) * 0.001,
                    fixed2float(cseg->p2.y) * 0.001,
                    fixed2float(seg->pt.x) * 0.001,
                    fixed2float(seg->pt.y) * 0.001);
            break;
        }
        seg = seg->next;
    }
}

/*
 * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the
 * data with the GUID in the fontname.
 */
int
xps_deobfuscate_font_resource(xps_context_t *ctx, xps_part_t *part)
{
    byte buf[33];
    byte key[16];
    char *p;
    int i;

    dprintf1("deobfuscating font '%s'\n", part->name);

    p = strrchr(part->name, '/');
    if (!p)
	p = part->name;

    for (i = 0; i < 32 && *p; p++)
    {
	if (isxdigit(*p))
	    buf[i++] = *p;
    }
    buf[i] = 0;

    if (i != 32)
	return gs_throw(-1, "cannot extract GUID from part name");

    for (i = 0; i < 16; i++)
	key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]);

    for (i = 0; i < 16; i++)
    {
	part->data[i] ^= key[15-i];
	part->data[i+16] ^= key[15-i];
    }

    if (getenv("XPS_SAVE_FONTS"))
    {
	static int id = 0;
	char buf[25];
	FILE *fp;
	sprintf(buf, "font%d.otf", id++);
	dprintf1("saving font data to %s\n", buf);
	fp = fopen(buf, "wb");
	fwrite(part->data, part->size, 1, fp);
	fclose(fp);
    }

    return 0;
}

int
xps_select_best_font_encoding(xps_font_t *font)
{
    static struct { int pid, eid; } xps_cmap_list[] =
    {
	{ 3, 10 },      /* Unicode with surrogates */
	{ 3, 1 },       /* Unicode without surrogates */
	{ 3, 5 },       /* Wansung */
	{ 3, 4 },       /* Big5 */
	{ 3, 3 },       /* Prc */
	{ 3, 2 },       /* ShiftJis */
	{ 3, 0 },       /* Symbol */
	// { 0, * }, -- Unicode (deprecated)
	{ 1, 0 },
	{ -1, -1 },
    };

    int i, k, n, pid, eid;

    n = xps_count_font_encodings(font);
    for (k = 0; xps_cmap_list[k].pid != -1; k++)
    {
	for (i = 0; i < n; i++)
	{
	    xps_identify_font_encoding(font, i, &pid, &eid);
	    if (pid == xps_cmap_list[k].pid && eid == xps_cmap_list[k].eid)
	    {
		xps_select_font_encoding(font, i);
		return 0;
	    }
	}
    }

    return gs_throw(-1, "could not find a suitable cmap");
}

/*
 * Call text drawing primitives.
 */

int xps_flush_text_buffer(xps_context_t *ctx, xps_font_t *font,
	xps_text_buffer_t *buf, int is_charpath)
{
    gs_text_params_t params;
    gs_text_enum_t *textenum;
    float x = buf->x[0];
    float y = buf->y[0];
    int code;
    int i;

    // dprintf1("flushing text buffer (%d glyphs)\n", buf->count);

#if 0 /* one glyph at a time */

    for (i = 0; i < buf->count; i++)
    {
	gs_moveto(ctx->pgs, buf->x[i], buf->y[i]);

	params.operation = TEXT_FROM_SINGLE_GLYPH;
	if (is_charpath)
	    params.operation |= TEXT_DO_FALSE_CHARPATH;
	else
	    params.operation |= TEXT_DO_DRAW;
	params.data.d_glyph = buf->g[i];
	params.size = 1;

	code = gs_text_begin(ctx->pgs, &params, ctx->memory, &textenum);
	if (code != 0)
	    return gs_throw1(-1, "cannot gs_text_begin() (%d)", code);

	code = gs_text_process(textenum);
	if (code != 0)
	    return gs_throw1(-1, "cannot gs_text_process() (%d)", code);

	gs_text_release(textenum, "gslt font render");
    }

#else

    gs_moveto(ctx->pgs, x, y);

    params.operation = TEXT_FROM_GLYPHS | TEXT_REPLACE_WIDTHS;
    if (is_charpath)
	params.operation |= TEXT_DO_FALSE_CHARPATH;
    else
	params.operation |= TEXT_DO_DRAW;
    params.data.glyphs = buf->g;
    params.size = buf->count;
    params.x_widths = buf->x + 1;
    params.y_widths = buf->y + 1;
    params.widths_size = buf->count;

    for (i = 0; i < buf->count; i++)
    {
	buf->x[i] = buf->x[i] - x;
	buf->y[i] = buf->y[i] - y;
	x += buf->x[i];
	y += buf->y[i];
    }
    buf->x[buf->count] = 0;
    buf->y[buf->count] = 0;

    code = gs_text_begin(ctx->pgs, &params, ctx->memory, &textenum);
    if (code != 0)
	return gs_throw1(-1, "cannot gs_text_begin() (%d)", code);

    code = gs_text_process(textenum);
    if (code != 0)
	return gs_throw1(-1, "cannot gs_text_process() (%d)", code);

    gs_text_release(textenum, "gslt font render");
#endif

    buf->count = 0;

    return 0;
}

/*
 * Parse and draw an XPS <Glyphs> element.
 *
 * Indices syntax:

 GlyphIndices   = GlyphMapping ( ";" GlyphMapping ) 
 GlyphMapping   = ( [ClusterMapping] GlyphIndex ) [GlyphMetrics] 
 ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")" 
 ClusterCodeUnitCount = * DIGIT 
 ClusterGlyphCount    = * DIGIT 
 GlyphIndex     = * DIGIT 
 GlyphMetrics   = "," AdvanceWidth ["," uOffset ["," vOffset]] 
 AdvanceWidth   = ["+"] RealNum 
 uOffset        = ["+" | "-"] RealNum 
 vOffset        = ["+" | "-"] RealNum 
 RealNum        = ((DIGIT ["." DIGIT]) | ("." DIGIT)) [Exponent] 
 Exponent       = ( ("E"|"e") ("+"|"-") DIGIT ) 

 */

static char *
xps_parse_digits(char *s, int *digit)
{
    *digit = 0;
    while (*s >= '0' && *s <= '9')
    {
	*digit = *digit * 10 + (*s - '0');
	s ++;
    }
    return s;
}

static int is_real_num_char(int c)
{
    return (c >= '0' && c <= '9') || c == 'e' || c == 'E' || c == '+' || c == '-' || c == '.';
}

static char *
xps_parse_real_num(char *s, float *number)
{
    char buf[64];
    char *p = buf;
    while (is_real_num_char(*s))
	*p++ = *s++;
    *p = 0;
    if (buf[0])
	*number = atof(buf);
    return s;
}

static char *
xps_parse_cluster_mapping(char *s, int *code_count, int *glyph_count)
{
    if (*s == '(')
	s = xps_parse_digits(s + 1, code_count);
    if (*s == ':')
	s = xps_parse_digits(s + 1, glyph_count);
    if (*s == ')')
	s ++;
    return s;
}

static char *
xps_parse_glyph_index(char *s, int *glyph_index)
{
    if (*s >= '0' && *s <= '9')
	s = xps_parse_digits(s, glyph_index);
    return s;
}

static char *
xps_parse_glyph_metrics(char *s, float *advance, float *uofs, float *vofs)
{
    if (*s == ',')
	s = xps_parse_real_num(s + 1, advance);
    if (*s == ',')
	s = xps_parse_real_num(s + 1, uofs);
    if (*s == ',')
	s = xps_parse_real_num(s + 1, vofs);
    return s;
}

int
xps_parse_glyphs_imp(xps_context_t *ctx, xps_font_t *font, float size,
	float originx, float originy, int is_sideways, int bidi_level,
	char *indices, char *unicode, int is_charpath)
{
    // parse unicode and indices strings and encode glyphs
    // and calculate metrics for positioning

    xps_text_buffer_t buf;
    xps_glyph_metrics_t mtx;
    float x = originx;
    float y = originy;
    char *us = unicode;
    char *is = indices;
    int un;

    // dprintf1("string (%s)\n", us);
    // dprintf1("indices %.50s\n", is);

    buf.count = 0;

    if (!unicode && !indices)
	return gs_throw(-1, "no text in glyphs element");

    if (us)
    {
	if (us[0] == '{' && us[1] == '}')
	    us = us + 2;
	un = strlen(us);
    }

    while ((us && un > 0) || (is && *is))
    {
	int code_count = 1;
	int glyph_count = 1;

	if (is && *is)
	{
	    is = xps_parse_cluster_mapping(is, &code_count, &glyph_count);
	}

	if (code_count < 1)
	    code_count = 1;
	if (glyph_count < 1)
	    glyph_count = 1;

	while (code_count > 0 || glyph_count > 0)
	{
	    int char_code = '?';
	    int glyph_index = -1;
	    float u_offset = 0.0;
	    float v_offset = 0.0;
	    float advance;

	    if (glyph_count)
	    {
		if (is && *is)
		    is = xps_parse_glyph_index(is, &glyph_index);
		glyph_count --;
	    }

	    if (code_count)
	    {
		if (us && un > 0)
		{
		    int t = xps_utf8_to_ucs(&char_code, us, un);
		    if (t < 0)
			return gs_rethrow(-1, "error decoding UTF-8 string");
		    us += t; un -= t;
		}
		code_count --;
	    }

	    if (glyph_index == -1)
		glyph_index = xps_encode_font_char(font, char_code);

	    xps_measure_font_glyph(ctx, font, glyph_index, &mtx);
	    if (is_sideways)
		advance = mtx.vadv * 100.0;
	    else if (bidi_level & 1)
		advance = -mtx.hadv * 100.0;
	    else
		advance = mtx.hadv * 100.0;

	    if (is && *is)
	    {
		is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset);
		if (*is == ';')
		    is ++;
	    }

#if 0
	    dprintf6("glyph mapping (%d:%d)%d,%g,%g,%g\n",
		    code_count, glyph_count, glyph_index,
		    advance, u_offset, v_offset);
#endif

	    if (bidi_level & 1)
		u_offset = -mtx.hadv * 100 - u_offset;

	    u_offset = u_offset * 0.01 * size;
	    v_offset = v_offset * 0.01 * size;

	    if (buf.count < XPS_TEXT_BUFFER_SIZE)
	    {
		if (is_sideways)
		{
		    buf.x[buf.count] = x + u_offset + (mtx.vorg * size);
		    buf.y[buf.count] = y - v_offset + (mtx.hadv * 0.5 * size);
		}
		else
		{
		    buf.x[buf.count] = x + u_offset;
		    buf.y[buf.count] = y - v_offset;
		}
		buf.g[buf.count] = glyph_index;
		buf.count ++;
	    }
	    else
	    {
		xps_flush_text_buffer(ctx, font, &buf, is_charpath);
	    }

	    x += advance * 0.01 * size;
	}
    }

    if (buf.count > 0)
    {
	xps_flush_text_buffer(ctx, font, &buf, is_charpath);
    }

    return 0;
}

int
xps_parse_glyphs(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root)
{
    xps_item_t *node;

    char *bidi_level_att;
    char *caret_stops_att;
    char *fill_att;
    char *font_size_att;
    char *font_uri_att;
    char *origin_x_att;
    char *origin_y_att;
    char *is_sideways_att;
    char *indices_att;
    char *unicode_att;
    char *style_att;
    char *transform_att;
    char *clip_att;
    char *opacity_att;
    char *opacity_mask_att;

    xps_item_t *transform_tag = NULL;
    xps_item_t *clip_tag = NULL;
    xps_item_t *fill_tag = NULL;
    xps_item_t *opacity_mask_tag = NULL;

    char *fill_opacity_att = NULL;

    xps_part_t *part;
    xps_font_t *font;

    char partname[1024];
    char *parttype;
    char *subfont;

    gs_matrix matrix;
    float font_size = 10.0;
    int subfontid = 0;
    int is_sideways = 0;
    int bidi_level = 0;

    gs_rect saved_bounds;

    /*
     * Extract attributes and extended attributes.
     */

    bidi_level_att = xps_att(root, "BidiLevel");
    caret_stops_att = xps_att(root, "CaretStops");
    fill_att = xps_att(root, "Fill");
    font_size_att = xps_att(root, "FontRenderingEmSize");
    font_uri_att = xps_att(root, "FontUri");
    origin_x_att = xps_att(root, "OriginX");
    origin_y_att = xps_att(root, "OriginY");
    is_sideways_att = xps_att(root, "IsSideways");
    indices_att = xps_att(root, "Indices");
    unicode_att = xps_att(root, "UnicodeString");
    style_att = xps_att(root, "StyleSimulations");
    transform_att = xps_att(root, "RenderTransform");
    clip_att = xps_att(root, "Clip");
    opacity_att = xps_att(root, "Opacity");
    opacity_mask_att = xps_att(root, "OpacityMask");

    for (node = xps_down(root); node; node = xps_next(node))
    {
	if (!strcmp(xps_tag(node), "Glyphs.RenderTransform"))
	    transform_tag = xps_down(node);

	if (!strcmp(xps_tag(node), "Glyphs.OpacityMask"))
	    opacity_mask_tag = xps_down(node);

	if (!strcmp(xps_tag(node), "Glyphs.Clip"))
	    clip_tag = xps_down(node);

	if (!strcmp(xps_tag(node), "Glyphs.Fill"))
	    fill_tag = xps_down(node);
    }

    xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag);
    xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag);
    xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag);
    xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag);

    /*
     * Check that we have all the necessary information.
     */

    if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att)
	return gs_throw(-1, "missing attributes in glyphs element");

    if (!indices_att && !unicode_att)
	return 0; /* nothing to draw */

    if (is_sideways_att)
	is_sideways = !strcmp(is_sideways_att, "true");

    if (bidi_level_att)
	bidi_level = atoi(bidi_level_att);

    /*
     * Find and load the font resource
     */

    // TODO: get subfont index from # part of uri

    xps_absolute_path(partname, ctx->pwd, font_uri_att);
    subfont = strrchr(partname, '#');
    if (subfont)
    {
	subfontid = atoi(subfont + 1);
	*subfont = 0;
    }
    part = xps_find_part(ctx, partname);
    if (!part)
	return gs_throw1(-1, "cannot find font resource part '%s'", partname);

    if (!part->deobfuscated)
    {
	/* deobfuscate if necessary */
	parttype = xps_get_content_type(ctx, part->name);
	if (parttype && !strcmp(parttype, "application/vnd.ms-package.obfuscated-opentype"))
	    xps_deobfuscate_font_resource(ctx, part);

	/* stupid XPS files with content-types after the parts */
	if (!parttype && strstr(part->name, ".odttf"))
	    xps_deobfuscate_font_resource(ctx, part);
	if (!parttype && strstr(part->name, ".ODTTF"))
	    xps_deobfuscate_font_resource(ctx, part);

	part->deobfuscated = 1;
    }

    if (!part->font)
    {
	part->font = xps_new_font(ctx, part->data, part->size, subfontid);
	if (!part->font)
	    return gs_rethrow1(-1, "cannot load font resource '%s'", partname);

	xps_select_best_font_encoding(part->font);
    }

    font = part->font;

    /*
     * Set up graphics state.
     */

    gs_gsave(ctx->pgs);

    if (transform_att || transform_tag)
    {
	gs_matrix transform;

	if (transform_att)
	    xps_parse_render_transform(ctx, transform_att, &transform);
	if (transform_tag)
	    xps_parse_matrix_transform(ctx, transform_tag, &transform);

	gs_concat(ctx->pgs, &transform);
    }

    if (clip_att || clip_tag)
    {
	if (clip_att)
	    xps_parse_abbreviated_geometry(ctx, clip_att);
	if (clip_tag)
	    xps_parse_path_geometry(ctx, dict, clip_tag, 0);

	xps_clip(ctx, &saved_bounds);
    }

    font_size = atof(font_size_att);

    gs_setfont(ctx->pgs, font->font);
    gs_make_scaling(font_size, -font_size, &matrix);
    if (is_sideways)
	gs_matrix_rotate(&matrix, 90.0, &matrix);

    gs_setcharmatrix(ctx->pgs, &matrix);

    gs_matrix_multiply(&matrix, &font->font->orig_FontMatrix,
                       &font->font->FontMatrix);

    xps_begin_opacity(ctx, dict, opacity_att, opacity_mask_tag);

    /*
     * If it's a solid color brush fill/stroke do a simple fill
     */

    if (fill_tag && !strcmp(xps_tag(fill_tag), "SolidColorBrush"))
    {
	fill_opacity_att = xps_att(fill_tag, "Opacity");
	fill_att = xps_att(fill_tag, "Color");
	fill_tag = NULL;
    }

    if (fill_att)
    {
	float samples[32];
	gs_color_space *colorspace;
	xps_parse_color(ctx, fill_att, &colorspace, samples);
	if (fill_opacity_att)
	    samples[0] = atof(fill_opacity_att);
	xps_set_color(ctx, colorspace, samples);
	xps_parse_glyphs_imp(ctx, font, font_size,
		atof(origin_x_att), atof(origin_y_att),
		is_sideways, bidi_level,
		indices_att, unicode_att, 0);
    }

    /*
     * If it's a visual brush or image, use the charpath as a clip mask to paint brush
     */

    if (fill_tag)
    {
	ctx->fill_rule = 1; /* always use non-zero winding rule for char paths */
	xps_parse_glyphs_imp(ctx, font, font_size,
		atof(origin_x_att), atof(origin_y_att),
		is_sideways, bidi_level, indices_att, unicode_att, 1);
	xps_parse_brush(ctx, dict, fill_tag);
    }

    xps_end_opacity(ctx, dict, opacity_att, opacity_mask_tag);

    gs_grestore(ctx->pgs);

    if (clip_att || clip_tag)
    {
	xps_unclip(ctx, &saved_bounds);
    }

    return 0;
}

