/* PDF export Plugin for GIMP - The GNU Image Manipulation Program
* This plugin uses the Cairo and PangoCairo to save pdf documents.
*
* Copyright (C) 2009 Barak Itkin <lightningismyname@gmail.com>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* Revision Log:
* Version 0.0 - April 29th, 2009
* 				A simple proof of concept. This is the first version
* 				of the plugin and right now It's considered an Alpha
* 				release.
* 				Nevertheless, this should be pretty much stable.
*/

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include <cairo.h>
#include <cairo-pdf.h>
#include <pango/pangocairo.h>

#define SAVE_PROC      "file-pdf-save"
#define PLUG_IN_BINARY "file-pdf"

static void run   (const gchar      *name,
				   gint              nparams,
				   const GimpParam  *param,
				   gint             *nreturn_vals,
				   GimpParam       **return_vals);

static void
query (void)
{
	static const GimpParamDef save_args[] =
	{
		{ GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
		{ GIMP_PDB_IMAGE,    "image",        "Input image" },
		{ GIMP_PDB_DRAWABLE, "drawable",     "Drawable to save" },
		{ GIMP_PDB_STRING,   "filename",     "The name of the file to save the PDF in" },
		{ GIMP_PDB_STRING,   "raw-filename", "The name entered" },
	};
	gimp_install_procedure (SAVE_PROC,
		"Save file in PDF format.",
		"Saves files in Portable Document Format (PDF) Format.",
		"Barak Itkin",
		"Barak Itkin <lightningismyname@gmail.com>",
		"2009",
		"Portable Document Format",
		//"INDEXED, GRAY, RGB*",
		"RGB*, GRAY*",
		GIMP_PLUGIN,
		G_N_ELEMENTS (save_args), 0,
		save_args, NULL);

	gimp_register_file_handler_mime (SAVE_PROC, "application/pdf");
	gimp_register_save_handler (SAVE_PROC, "pdf", "");
}

GimpPlugInInfo PLUG_IN_INFO =
{
	NULL,
	NULL,
	query,
	run
};

MAIN()

typedef struct RGBAcolor {
	double red;
	double green;
	double blue;
	double alpha;
}RGBAcolor;

/* This function was taken from the GIMP print plugin
* and modified to use GimpPixelRgn, in order to be able to handle
* large images.
*/

static cairo_surface_t *
get_drawable_image (GimpDrawable *drawable)
{
	cairo_surface_t *surface;
	cairo_format_t   format;
	guchar          *data;
	guchar          *dest;
	const guchar    *src;
	gint             dest_stride;
	gint             y;
	gint             bpp;

	gint32			 drawable_id;
	GimpPixelRgn	 region;
	gint			 width;
	gint			 height;


	drawable_id = drawable->drawable_id;
	width  = drawable->width;
	height = drawable->height;
	gimp_pixel_rgn_init (&region, drawable, 0, 0, width, height, FALSE, FALSE);
	bpp = region.bpp;
	data = g_new(guchar, width*height*bpp);
	gimp_pixel_rgn_get_rect(&region, data, 0, 0, width, height);

	switch (bpp)
	{
	case 1:
	case 3:
		format = CAIRO_FORMAT_RGB24;
		break;

	case 2:
	case 4:
		format = CAIRO_FORMAT_ARGB32;
		break;

	default:
		g_assert_not_reached ();
		break;
	}

	surface = cairo_image_surface_create (format, width, height);

	src = data;

	dest        = cairo_image_surface_get_data (surface);
	dest_stride = cairo_image_surface_get_stride (surface);

	for (y = 0; y < height; y++)
	{
		const guchar *s = src;
		guchar       *d = dest;
		gint          w = width;

		switch (bpp)
		{
		case 1:
			while (w--)
			{
				GIMP_CAIRO_RGB24_SET_PIXEL (d, s[0], s[0], s[0]);
				s += 1;
				d += 4;
			}
			break;

		case 2:
			while (w--)
			{
				GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[0], s[0], s[1]);
				s += 2;
				d += 4;
			}
			break;

		case 3:
			while (w--)
			{
				GIMP_CAIRO_RGB24_SET_PIXEL (d, s[0], s[1], s[2]);
				s += 3;
				d += 4;
			}
			break;

		case 4:
			while (w--)
			{
				GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[1], s[2], s[3]);
				s += 4;
				d += 4;
			}
			break;
		}

		src  += width*bpp;
		dest += dest_stride;
	}

	g_free (data);

	return surface;
}


static void query (void);

/* gimp_text_layer_get_font() may sometime return a font description which Pango doesn't like
* however, I discovered that the font-name (in the way the user sees it) is always at the begining
* of the returned string, and any additional information will be put after it, seperated by ','
* so this function returns a new string which is terminated just before the first ','.
* this string is freed later, using g_free
*/
gchar* fontFix(gchar* name)
{
	gchar* stop = g_strdup(name);
	gint i=0;
	for(i=0;stop[i] && stop[i] != ',';i++);
	if(stop[i])
		stop[i]=NULL;
	return stop;
}

PangoAlignment getTextAlignment(GimpDrawable *layer)
{
	GimpTextJustification j;
	j = gimp_text_layer_get_justification(layer->drawable_id);

	if(j == GIMP_TEXT_JUSTIFY_CENTER)
		return PANGO_ALIGN_CENTER;
	else if(j == GIMP_TEXT_JUSTIFY_LEFT)
		return PANGO_ALIGN_LEFT;
	else if(j == GIMP_TEXT_JUSTIFY_RIGHT)
		return PANGO_ALIGN_RIGHT;
	else { //j == GIMP_TEXT_JUSTIFY_FILL 
		if(gimp_text_layer_get_base_direction(layer->drawable_id) == GIMP_TEXT_DIRECTION_LTR)
			return PANGO_ALIGN_LEFT;
		else
			return PANGO_ALIGN_RIGHT;
	}
}

/*
* FixMe: This has a bug... Find it and fix it so we can optimize pdf outputs by drawing
* single colored rectangles instead of bitmaps where possible
*/
RGBAcolor getColor(GimpDrawable *layer, gboolean *single)
{
	RGBAcolor col;
	gdouble red, green, blue, alpha;
	gdouble dev, devSum;
	gint32 id;

	id = layer->drawable_id;
	devSum = 0;

	gimp_histogram(id, GIMP_HISTOGRAM_RED, 0, 256, &red, &dev, NULL, NULL, NULL, NULL);
	devSum += dev;
	gimp_histogram(id, GIMP_HISTOGRAM_GREEN, 0, 256, &green, &dev, NULL, NULL, NULL, NULL);
	devSum += dev;
	gimp_histogram(id, GIMP_HISTOGRAM_BLUE, 0, 256, &blue, &dev, NULL, NULL, NULL, NULL);
	devSum += dev;
	if(gimp_drawable_has_alpha(id))
		gimp_histogram(id, GIMP_HISTOGRAM_ALPHA, 0, 256, &alpha, &dev, NULL, NULL, NULL, NULL);
	else
		alpha = 255;

	devSum += dev;
	*single = devSum == 0;

	col.red = red/255;
	col.green = green/255;
	col.blue = blue/255;
	col.alpha = alpha/255;

	return col;
}

PangoDirection getTextDirection(GimpDrawable *layer)
{
	if(gimp_text_layer_get_base_direction(layer->drawable_id) == GIMP_TEXT_DIRECTION_LTR)
		return PANGO_DIRECTION_LTR;
	else
		return PANGO_DIRECTION_RTL;
}

RGBAcolor getRGBAcolor(GimpDrawable *layer)
{
	GimpRGB color;
	RGBAcolor colorFix;
	gimp_text_layer_get_color(layer->drawable_id, &color);

	colorFix.red = color.r;
	colorFix.green = color.g;
	colorFix.blue = color.b;
	colorFix.alpha = gimp_layer_get_opacity(layer->drawable_id)/100.0;

	return colorFix;
}

gboolean isJustified(GimpDrawable *layer)
{
	GimpTextJustification j;
	j = gimp_text_layer_get_justification(layer->drawable_id);

	if(j == GIMP_TEXT_JUSTIFY_FILL)
		return TRUE;
	else
		return FALSE;
}

/* A function that uses Pango to render the text to our cairo surface, in the same way it was
* the user saw it inside gimp.
*/
void drawText(cairo_t *cr,
			  gchar* text,
			  gchar* font_family, 
			  gint points,
			  gint x,
			  gint y,
			  // PANGO_ALIGN_LEFT, PANGO_ALIGN_CENTER, PANGO_ALIGN_RIGHT
			  PangoAlignment align,
			  gboolean justify,
			  gboolean rtl,
			  gint width,
			  gint height,
			  RGBAcolor color)
{
	PangoLayout *layout;
	PangoContext *context;
	PangoFontDescription *font_description;

	cairo_move_to(cr, x, y);
	cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha); 


	context = pango_cairo_create_context(cr);
	if (rtl)
		pango_context_set_base_dir(context, PANGO_DIRECTION_RTL);
	else
		pango_context_set_base_dir(context, PANGO_DIRECTION_LTR);

	font_description = pango_font_description_from_string (font_family);

	pango_font_description_set_absolute_size (font_description, points * PANGO_SCALE);

	layout = pango_layout_new (context);
	pango_layout_set_font_description (layout, font_description);
	pango_layout_set_text (layout, text, -1);

	pango_layout_set_width(layout, width * PANGO_SCALE);
	pango_layout_set_height(layout, height * PANGO_SCALE);

	pango_layout_set_justify(layout, justify);
	pango_layout_set_alignment(layout, align);

	pango_cairo_show_layout (cr, layout);

	g_object_unref (layout);
	pango_font_description_free (font_description);

	g_free(font_family);
}

static void
run (const gchar      *name,
	 gint              nparams,
	 const GimpParam  *param,
	 gint             *nreturn_vals,
	 GimpParam       **return_vals)
{
	static GimpParam  values[1];

	GimpUnit p;

	gint32 image_id;
	gint32 drawable_id = param[2].data.d_int32;
	gint32* layers;
	gint32 num_of_layers;
	GimpDrawable* layer;

	cairo_t *cr;
	cairo_surface_t *pdf_file;
	cairo_surface_t *layer_image;

	gint x;
	gint y;
	gint width;
	gint height;

	gint i;
	GimpRGB color;
	gint32 maskID = -1;

	GimpExportReturn  export = GIMP_EXPORT_CANCEL;
	GimpPDBStatusType status = GIMP_PDB_SUCCESS;


	/* Setting mandatory output values */
	*nreturn_vals = 1;
	*return_vals  = values;

	values[0].type = GIMP_PDB_STATUS;
	values[0].data.d_status = status;

	image_id = param[1].data.d_int32;

	gimp_ui_init (PLUG_IN_BINARY, FALSE);
	export = gimp_export_image (&image_id, &drawable_id, "PDF",
		(GIMP_EXPORT_CAN_HANDLE_RGB | GIMP_EXPORT_CAN_HANDLE_ALPHA |
		GIMP_EXPORT_CAN_HANDLE_GRAY | GIMP_EXPORT_CAN_HANDLE_LAYERS |
		GIMP_EXPORT_CAN_HANDLE_INDEXED));

	if (export == GIMP_EXPORT_CANCEL)
	{
		values[0].data.d_status = GIMP_PDB_CANCEL;
		return;
	}

	layers = gimp_image_get_layers(image_id, &num_of_layers);

	pdf_file = cairo_pdf_surface_create(param[3].data.d_string,
		gimp_image_width(image_id),
		gimp_image_height(image_id));

	cr = cairo_create(pdf_file);

	p = GIMP_UNIT_POINT;

	for(i=0;i<num_of_layers;i++)
	{
		layer = gimp_drawable_get(layers[num_of_layers-i-1]);
		width = gimp_drawable_width(layer->drawable_id);
		height = gimp_drawable_height(layer->drawable_id);
		gimp_drawable_offsets(layer->drawable_id, &x, &y);

		if(!gimp_drawable_is_text_layer(layer->drawable_id))
		{
			cairo_rectangle(cr, x, y, width, height);
			layer_image = get_drawable_image(layer);
			cairo_set_source_surface(cr, layer_image, x, y);
			cairo_paint_with_alpha(cr, gimp_layer_get_opacity(layer->drawable_id)/100.0);
			cairo_surface_destroy(layer_image);
		}
		else
		{
			gimp_text_layer_get_color(layer->drawable_id, &color);
			drawText(cr,
				gimp_text_layer_get_text(layer->drawable_id),
				fontFix(gimp_text_layer_get_font(layer->drawable_id)),
				gimp_text_layer_get_font_size(layer->drawable_id, &p),
				x, y,
				getTextAlignment(layer),
				isJustified(layer),
				getTextDirection(layer),
				width, height,
				getRGBAcolor(layer));
		}
	}
	cairo_show_page(cr);

	if(export == GIMP_EXPORT_EXPORT)
		gimp_image_delete(image_id);

	cairo_surface_destroy(pdf_file);
	cairo_destroy(cr);
}
