/***************************************************************************
  
  CPdfDocument.cpp
  
  (C) 2005-2007 Daniel Campos Fernández <dcamposf@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 2, 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., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.
  
***************************************************************************/

#define __CPDFDOCUMENT_C

#include "CPdfDocument.h"

#include "gambas.h"
#include "main.h"

#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <memory>

#include <PDFDoc.h>
#include <Stream.h>
#include <ErrorCodes.h>
#include <Page.h>
#include <Catalog.h>
#include <TextOutputDev.h>
#include <SplashOutputDev.h>
#include <splash/SplashBitmap.h>

#include <Outline.h>
#include <Link.h>
#include <Gfx.h>
#include <GlobalParams.h>
#include <UnicodeMap.h>

#if CHECK_VERSION(007200)
#define getCString c_str
#endif

#if CHECK_VERSION(240500)
#include "UTF.h"
#endif

#if CHECK_VERSION(251100)
#define getLength size
#endif

#if CHECK_VERSION(260600)
#include <Annot.h>
#endif

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

static CPDFRECT *create_rect(void)
{
	return (CPDFRECT *)GB.New(GB.FindClass("PdfRect"), NULL, NULL);
}

BEGIN_PROPERTY(PdfRect_X)

	GB.ReturnFloat(THIS_RECT->x);

END_PROPERTY

BEGIN_PROPERTY(PdfRect_Y)

	GB.ReturnFloat(THIS_RECT->y);

END_PROPERTY

BEGIN_PROPERTY(PdfRect_Width)

	GB.ReturnFloat(THIS_RECT->w);

END_PROPERTY

BEGIN_PROPERTY(PdfRect_Height)

	GB.ReturnFloat(THIS_RECT->h);

END_PROPERTY

BEGIN_PROPERTY(PdfRect_Right)

	GB.ReturnFloat(THIS_RECT->x + THIS_RECT->w);

END_PROPERTY

BEGIN_PROPERTY(PdfRect_Bottom)

	GB.ReturnFloat(THIS_RECT->y + THIS_RECT->h);

END_PROPERTY



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

 Translations from Poppler universe to Gambas universe

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

#if CHECK_VERSION(240200)
static void return_unicode_string(const std::vector<Unicode> &unicode)
{
	GooString gstr;
	char buf[8]; /* 8 is enough for mapping an unicode char to a string */
	int n;

	const UnicodeMap *uMap = globalParams->getUtf8Map();
	for (const auto &c : unicode) {
		n = uMap->mapUnicode(c, buf, sizeof(buf));
		gstr.append(buf, n);
	}

	GB.ReturnNewZeroString(gstr.getCString());
}
#else
static void return_unicode_string(const Unicode *unicode, int len)
{
	GooString gstr;
	char buf[8]; /* 8 is enough for mapping an unicode char to a string */
	int i, n;

#if CHECK_VERSION(008500)
	const UnicodeMap *uMap = globalParams->getUtf8Map();
#else
	static UnicodeMap *uMap = NULL;
	if (uMap == NULL) 
	{
		GooString *enc = new GooString("UTF-8");
		uMap = globalParams->getUnicodeMap(enc);
		uMap->incRefCnt();
		delete enc;
	}
#endif
		
	for (i = 0; i < len; ++i) {
		n = uMap->mapUnicode(unicode[i], buf, sizeof(buf));
		gstr.append(buf, n);
	}

	GB.ReturnNewZeroString(gstr.getCString());
}
#endif


static void aux_return_string_info(void *_object, const char *key)
{
	Object obj;
	Object dst;
	Dict *info_dict;
	char *tmpstr;

#if CHECK_VERSION(005800)
	obj = THIS->doc->getDocInfo ();
#else
	THIS->doc->getDocInfo (&obj);
#endif
	if (!obj.isDict())
	{
		GB.ReturnNewZeroString("");
#if ! CHECK_VERSION(005800)
		obj.free();
#endif
		return;
	}
		
	info_dict = obj.getDict();

#if CHECK_VERSION(005800)
	dst = info_dict->lookup ((char *)key);
#else
	info_dict->lookup ((char *)key, &dst);
#endif

	if (!dst.isString ())
	{
		GB.ReturnNewZeroString("");
	}
	else
	{
#if CHECK_VERSION(260400)
		GooString goo_str(dst.getString());
		const_GooString *goo_value = &goo_str;
#else
		const_GooString *goo_value = dst.getString();
#endif

#if CHECK_VERSION(240500)
		if (hasUnicodeByteOrderMark(goo_value->toStr()))
#else
		if (goo_value->hasUnicodeMarker())
#endif
		{
			GB.ConvString (&tmpstr,goo_value->getCString()+2,goo_value->getLength()-2,"UTF-16BE","UTF-8");
			GB.ReturnNewZeroString(tmpstr);		
		}		
		else
			GB.ReturnNewString(goo_value->getCString(),goo_value->getLength());		
	}

#if ! CHECK_VERSION(005800)
	dst.free();
	obj.free();		
#endif
}

static void aux_return_date_info(void *_object, const char *key)
{
	// TODO: Y2K effect
	GB_DATE_SERIAL ds;
	GB_DATE ret;
	Object obj;
	Object dst;
	Dict *info_dict;
	char *datestr=NULL,*tofree=NULL;
	int nnum;

	GB.ReturnDate(NULL);
	
	#if CHECK_VERSION(005800)
	obj = THIS->doc->getDocInfo ();
	#else
	THIS->doc->getDocInfo (&obj);
	#endif
	if (!obj.isDict()) return;

	info_dict=obj.getDict();
	#if CHECK_VERSION(005800)
	dst = info_dict->lookup ((char *)key);
	#else
	info_dict->lookup ((char *)key, &dst);
	#endif
	if (dst.isString ())
	{
#if CHECK_VERSION(260400)
		GooString goo_str(dst.getString());
		const_GooString *goo = &goo_str;
#else
		const_GooString *goo = dst.getString();
#endif
#if CHECK_VERSION(240500)
		if (hasUnicodeByteOrderMark(goo->toStr()))
#else
		if (goo->hasUnicodeMarker())
#endif
			GB.ConvString (&datestr,goo->getCString()+2,goo->getLength()-2,"UTF-16BE","UTF-8");
		else
		{
			datestr = GB.NewString(goo->getCString(),goo->getLength());
			tofree=datestr;		
		}

		if (datestr)
		{
			if (datestr[0] == 'D' && datestr[1] == ':') datestr += 2;
			nnum=sscanf(datestr, "%4d%2d%2d%2d%2d%2d",&ds.year, &ds.month, &ds.day, &ds.hour, &ds.min, &ds.sec);
			if (nnum == 6)
			{
				if (!GB.MakeDate(&ds, &ret, true))
					GB.ReturnDate(&ret);
			}		
		}
		
	}

	if (tofree) GB.FreeString(&tofree);
	#if ! CHECK_VERSION(005800)
	dst.free();
	obj.free();
	#endif
}

static const_LinkDest *get_dest(const_LinkAction *act)
{
	if (!act)
		return 0;
	
	switch (act->getKind())
	{
		case actionGoTo: return ((LinkGoTo*)act)->getDest();
		case actionGoToR: return ((LinkGoToR*)act)->getDest();
		default: return 0;
	}
}

static uint32_t aux_get_page_from_action(void *_object, const_LinkAction *act)
{
	Ref pref;       
	const_LinkDest *dest = get_dest(act);
	const_GooString *name;

	if (!dest)
	{
		// try to use NamedDest to get dest
		if (!act)
			return 0;
		if (act->getKind () == actionGoTo)
		{
			name = ((LinkGoTo*)act)->getNamedDest();
			if (name) {
			#if CHECK_VERSION(008600)
				dest = THIS->doc->findDest(name).get();
			#elif CHECK_VERSION(006400)
				dest = THIS->doc->findDest(name);
			#else
				dest = THIS->doc->findDest((GooString *) name);
			#endif
			}
		}
	}

	if (!dest)
		return 0;

	if (dest->isPageRef() )
	{
		pref= dest->getPageRef();
#if CHECK_VERSION(007600)
		return THIS->doc->findPage(pref);
#else
		return THIS->doc->findPage(pref.num, pref.gen);
#endif
	}
	else
		return dest->getPageNum();
}


static void aux_get_dimensions_from_action(const_LinkAction *act, CPDFRECT *rect)
{
	const_LinkDest *dest = get_dest(act);
	if (!dest)
		return;
	
	rect->x = dest->getLeft();
	rect->w = dest->getRight() - rect->x;
	rect->y = dest->getTop();
	rect->h = dest->getBottom() - rect->y;
}

static double aux_get_zoom_from_action(const_LinkAction *act)
{
	const_LinkDest *dest = get_dest(act);
	if (dest)
		return dest->getZoom();
	else
		return 1;
}

static char* aux_get_target_from_action(const_LinkAction *act)
{
	char *vl = NULL;
	char *uni = NULL;	
	const_GooString *tmp = NULL;
#if CHECK_VERSION(008600)
	GooString gstr;
#endif

	switch (act->getKind())
	{
		case actionGoToR:
			tmp=((LinkGoToR*)act)->getFileName(); break;

		case actionLaunch:
			tmp=((LinkLaunch*)act)->getFileName(); break;

		case actionURI:
#if CHECK_VERSION(008600)
			gstr = GooString(((LinkURI*)act)->getURI());
			tmp = &gstr;
#else
			tmp = ((LinkURI*)act)->getURI(); 
#endif
			break;
			
		case actionNamed:
#if CHECK_VERSION(008600)
			gstr = GooString(((LinkNamed*)act)->getName());
			tmp = &gstr;
#else
			tmp = ((LinkNamed*)act)->getName(); 
#endif
			break;

		case actionMovie:
#if CHECK_VERSION(008600)
			gstr = GooString(((LinkMovie*)act)->getAnnotTitle());
			tmp = &gstr;
#else
			tmp = ((LinkMovie*)act)->getAnnotTitle();
#endif
			break;

		default:
			break;
	}

	if (!tmp) return NULL;

#if CHECK_VERSION(240500)
	if (hasUnicodeByteOrderMark(tmp->toStr()))
#else
	if (tmp->hasUnicodeMarker())
#endif
	{
			GB.ConvString (&uni,tmp->getCString()+2,tmp->getLength()-2,"UTF-16BE","UTF-8");
			vl = GB.AddString(vl, uni, 0);	
	}	
	else
			vl = GB.AddString(vl,tmp->getCString(),tmp->getLength());
	

	return vl;

}

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

 PDF document

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


static void free_all(void *_object)
{
	if (THIS->doc)
	{
		delete THIS->doc;
		THIS->doc=NULL;
	}

	if (THIS->dev)
	{
		delete THIS->dev;
		THIS->dev=NULL;
	}

	if (THIS->buf)
	{
		GB.ReleaseFile(THIS->buf,THIS->len);
		THIS->buf=NULL;
	}

	if (THIS->Found)
	{		
		GB.FreeArray(POINTER(&THIS->Found));
		THIS->Found=NULL;
	}

	if (THIS->links)
	{
		delete THIS->links;	
		THIS->links=NULL;
	}

	if (THIS->pindex)
	{		
		GB.FreeArray(POINTER(&THIS->pindex));
		GB.FreeArray(POINTER(&THIS->oldindex));
		THIS->pindex=NULL;
		THIS->oldindex=NULL;
	}

	THIS->index=NULL;
	THIS->currpage=-1;
}

BEGIN_METHOD_VOID (PDFDOCUMENT_free)

	free_all(_object);

END_METHOD

BEGIN_PROPERTY(PDFDOCUMENT_scale)

	if (READ_PROPERTY){ GB.ReturnFloat(THIS->scale); return; }
	
	if (VPROP(GB_FLOAT)>0) { THIS->scale = VPROP(GB_FLOAT); return; }

	GB.Error("Zoom must be a positive value");

END_PROPERTY

BEGIN_PROPERTY(PDFDOCUMENT_rotation)

	int32_t rot;

	if (READ_PROPERTY)
	{
		GB.ReturnInteger(THIS->rotation);
		return;
	}
	
	rot=VPROP(GB_INTEGER);

	while (rot<0) rot+=360;
	while (rot>=360) rot-=360;

	switch (rot)
	{
		case 0:
		case 90:
		case 180:
		case 270: 
			THIS->rotation = VPROP(GB_INTEGER);
			break;
	}

END_PROPERTY


int32_t open_document (void *_object, char *sfile, int32_t lfile)
{
	SplashColor white;
	PDFDoc *test;
	MemStream *stream;
	Object obj;
	Outline *outline;
	char *buf=NULL;
	int32_t len=0;
	int32_t ret;


	if ( GB.LoadFile(sfile,lfile,&buf,&len) ) return -1;

#if CHECK_VERSION(005800)
	stream = new MemStream(buf,0,(uint)len,std::move(obj));
#else
	obj.initNull();
	stream = new MemStream(buf,0,(uint)len,&obj);
#endif

#if CHECK_VERSION(260200)
	test = new PDFDoc(std::unique_ptr<BaseStream>(stream));
#else
	test = new PDFDoc(stream);
#endif

	if (!test->isOk())
	{
		GB.ReleaseFile(buf,len);
		ret=test->getErrorCode();
		delete test;
		test=NULL;
		if (ret == errEncrypted) return -2;
		return -3;
	}

	free_all(_object);

	THIS->doc=test;
	THIS->buf=buf;
	THIS->len=len;

	white[0] = 0xFF; white[1] = 0xFF; white[2] = 0xFF;
#if CHECK_VERSION(260200)
	THIS->dev=new SplashOutputDev(splashModeRGB8, 3, white, false);
#else
	THIS->dev=new SplashOutputDev(splashModeRGB8, 3, false, white);
#endif
	THIS->dev->startDoc(THIS->doc);
	outline=THIS->doc->getOutline();
	if (outline) THIS->index=outline->getItems();
	
	//if (THIS->index)
	//	if (!THIS->index->getLength()) THIS->index=NULL;

	THIS->currindex=0;
	THIS->currpage=-1;

	return 0;

}


BEGIN_METHOD(PDFDOCUMENT_new, GB_STRING File)

	THIS->scale = 1;
	THIS->rotation = 0;

	if (!MISSING(File))
	{
		switch (open_document( _object, STRING(File), LENGTH(File)) )
		{
			case -1: GB.Error("File not found"); return;
			case -2: GB.Error("PDF is encrypted"); return;
			case -3: GB.Error("Bad PDF File"); return;
		}
	}

END_METHOD

BEGIN_METHOD (PDFDOCUMENT_open, GB_STRING File;)

	switch (open_document( _object, STRING(File), LENGTH(File)) )
	{
		case -1: GB.Error("File not found"); return;
		case -2: GB.Error("PDF is encrypted"); return;
		case -3: GB.Error("Bad PDF File"); return;
	}

END_METHOD

BEGIN_METHOD_VOID(PDFDOCUMENT_close)

	free_all(_object);

END_METHOD

BEGIN_METHOD(PDFDOCUMENT_get,GB_INTEGER index;)

	if (!THIS->doc || (VARG(index)<1) || ( VARG(index)>THIS->doc->getNumPages() ) )
	{
		GB.Error("Invalid page number");
		return;
	}

	if (THIS->currpage != (uint32_t)VARG(index) )
	{
		if (THIS->Found)
		{		
			GB.FreeArray(POINTER(&THIS->Found));
			THIS->Found=NULL;
		}

		if (THIS->links)
		{
			delete THIS->links;	
			THIS->links=NULL;
		}

		THIS->page=THIS->doc->getCatalog()->getPage(VARG(index));
		THIS->currpage=VARG(index);
	}
		
	RETURN_SELF();

END_METHOD

BEGIN_PROPERTY(PDFDOCUMENT_ready)

	GB.ReturnBoolean( (bool)THIS->doc );

END_PROPERTY

BEGIN_PROPERTY(PDFDOCUMENT_count)

	GB.ReturnInteger( (int32_t) (THIS->doc ? THIS->doc->getNumPages() : 0));

END_PROPERTY

BEGIN_PROPERTY(PDFDOCUMENT_info)

	if (THIS->doc) RETURN_SELF();
	else GB.ReturnNull();

END_PROPERTY

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

PDF document information

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

BEGIN_PROPERTY(PDFINFO_title)

	aux_return_string_info(_object,"Title");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_format)

	char ctx[16];
	snprintf(ctx, sizeof(ctx), "%.2g", THIS->doc->getPDFMajorVersion () + THIS->doc->getPDFMinorVersion() / 10.0);
	GB.ReturnNewZeroString(ctx);

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_author)

	aux_return_string_info(_object,"Author");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_subject)

	aux_return_string_info(_object,"Subject");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_keywords)

	aux_return_string_info(_object,"Keywords");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_creator)

	aux_return_string_info(_object,"Creator");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_producer)

	aux_return_string_info(_object,"Producer");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_linearized)

	GB.ReturnBoolean(THIS->doc->isLinearized());

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_layout)

	Catalog *catalog;

	catalog=THIS->doc->getCatalog();
	if (!catalog) { GB.ReturnInteger(Catalog::pageLayoutNone); return; }
	if (!catalog->isOk())  { GB.ReturnInteger(Catalog::pageLayoutNone); return; }

	GB.ReturnInteger(catalog->getPageLayout());

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_mode)

	Catalog *catalog;

	catalog=THIS->doc->getCatalog();
	if (!catalog) { GB.ReturnInteger(Catalog::pageModeNone); return; }
	if (!catalog->isOk())  { GB.ReturnInteger(Catalog::pageModeNone); return; }

	GB.ReturnInteger(catalog->getPageMode());


END_PROPERTY

BEGIN_PROPERTY(PDFINFO_canprint)
	
	GB.ReturnBoolean(THIS->doc->okToPrint());      

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_canmodify)

	GB.ReturnBoolean(THIS->doc->okToChange());

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_cancopy)

	GB.ReturnBoolean(THIS->doc->okToCopy());

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_canaddnotes)

	GB.ReturnBoolean(THIS->doc->okToAddNotes());

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_creation)

	aux_return_date_info(_object,"CreationDate");

END_PROPERTY

BEGIN_PROPERTY(PDFINFO_modification)

	aux_return_date_info(_object,"ModDate");

END_PROPERTY


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

PDF document index

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


BEGIN_PROPERTY(PDFDOCUMENT_has_index)

	GB.ReturnBoolean(THIS->index && CPDF_index_count());

END_PROPERTY

BEGIN_PROPERTY(PDFDOCUMENT_index)

	if (!THIS->index) { GB.ReturnNull(); return; }
	
	THIS->action=(CPDF_index_get(THIS->currindex))->getAction();
	RETURN_SELF();

END_PROPERTY

BEGIN_PROPERTY(PDFINDEX_count)

	GB.ReturnInteger(CPDF_index_count());

END_PROPERTY

BEGIN_PROPERTY(PDFINDEX_has_children)

	OutlineItem *item = CPDF_index_get(THIS->currindex);

	GB.ReturnBoolean(item->getKids() && CPDF_list_count(item->getKids()));

END_PROPERTY

BEGIN_PROPERTY(PDFINDEX_is_open)

	OutlineItem *item = CPDF_index_get(THIS->currindex);

	if (READ_PROPERTY)
	{
		GB.ReturnBoolean(item->isOpen()); 
		return;
	}

	if (VPROP(GB_BOOLEAN))
		item->open();
#if CHECK_VERSION(210600)
#else
	else
		item->close();
#endif

END_PROPERTY

BEGIN_PROPERTY(PDFINDEX_title)

	OutlineItem *item = CPDF_index_get(THIS->currindex);
#if CHECK_VERSION(240200)
	return_unicode_string(item->getTitle());
#else
	return_unicode_string(item->getTitle(), item->getTitleLength());
#endif

END_PROPERTY


BEGIN_METHOD_VOID(PDFINDEX_root)

	Outline *outline;

	outline=THIS->doc->getOutline();
	if (outline) THIS->index=outline->getItems();
	THIS->currindex=0;
	if (THIS->pindex) { GB.FreeArray(POINTER(&THIS->pindex)); THIS->pindex=NULL; }
	if (THIS->oldindex) { GB.FreeArray(POINTER(&THIS->oldindex)); THIS->oldindex=NULL; }

END_METHOD

BEGIN_METHOD_VOID(PDFINDEX_prev)

	if (!THIS->currindex) { GB.ReturnBoolean(true); return; }

	THIS->currindex--;
	GB.ReturnBoolean(false);

END_METHOD

BEGIN_METHOD_VOID(PDFINDEX_next)

	if ((THIS->currindex + 1) >= (uint)CPDF_index_count())
	{
		GB.ReturnBoolean(true); 
		return;
	}

	THIS->currindex++;
	GB.ReturnBoolean(false);

END_METHOD

BEGIN_METHOD_VOID(PDFINDEX_child)

	OutlineItem *item = CPDF_index_get(THIS->currindex);

	if (!item->hasKids() || CPDF_list_count(item->getKids()) == 0) { GB.ReturnBoolean(true); return; }

	if (THIS->pindex)
	{
		GB.Add(POINTER(&THIS->pindex));
		GB.Add(POINTER(&THIS->oldindex));
	}
	else
	{
		GB.NewArray(POINTER(&THIS->pindex),sizeof(void*),1);
		GB.NewArray(POINTER(&THIS->oldindex),sizeof(uint32_t),1);
	}	

	if (!item->isOpen()) item->open(); 
	THIS->pindex[GB.Count(POINTER(THIS->pindex))-1]=(void*)THIS->index;
	THIS->oldindex[GB.Count(POINTER(THIS->pindex))-1]=THIS->currindex;
	THIS->index=item->getKids();	
	THIS->currindex=0;

	GB.ReturnBoolean(false);

END_METHOD

BEGIN_METHOD_VOID(PDFINDEX_parent)

	if (!THIS->pindex) { GB.ReturnBoolean(true); return; }

	THIS->index=(GooList*)THIS->pindex[GB.Count(POINTER(THIS->pindex))-1];
	THIS->currindex=THIS->oldindex[GB.Count(POINTER(THIS->pindex))-1];
	if (GB.Count(POINTER(THIS->pindex))==1)
	{
		GB.FreeArray(POINTER(&THIS->pindex));
		GB.FreeArray(POINTER(&THIS->oldindex));
		THIS->oldindex=NULL;
		THIS->pindex=NULL;
	}
	else
	{
		GB.Remove(POINTER(&THIS->pindex),GB.Count(POINTER(THIS->pindex))-1,1);
		GB.Remove(POINTER(&THIS->oldindex),GB.Count(POINTER(THIS->oldindex))-1,1);
	}

	GB.ReturnBoolean(false);

END_METHOD

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

 PDF pages

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

static int get_rotation(void *_object)
{
	return (THIS->rotation + THIS->page->getRotate() + 720) % 360;
}

static void get_page_size(void *_object, int *w, int *h)
{
	int rotation = get_rotation(THIS);

	if (rotation == 90 || rotation == 270)
	{
		if (w) *w =  (int)(THIS->page->getMediaHeight() * THIS->scale);
		if (h) *h = (int)(THIS->page->getMediaWidth() * THIS->scale);
	}
	else
	{
		if (w) *w = (int)(THIS->page->getMediaWidth() * THIS->scale);
		if (h) *h =  (int)(THIS->page->getMediaHeight() * THIS->scale);
	}
}

BEGIN_PROPERTY (PDFPAGE_width)

	int w;
	get_page_size(THIS, &w, NULL);
	GB.ReturnInteger(w);

END_PROPERTY

BEGIN_PROPERTY (PDFPAGE_height)

	int h;
	get_page_size(THIS, NULL, &h);
	GB.ReturnInteger(h);

END_PROPERTY

static uint32_t *get_page_data(CPDFDOCUMENT *_object, int32_t x, int32_t y, int32_t *width, int32_t *height, double scale, int32_t rotation)
{
	SplashBitmap *map;
	uint32_t *data;
	int32_t w, h;
	int rw;
	int rh;

	get_page_size(THIS, &rw, &rh);

	w = *width;
	h = *height;

	if (w < 0) w = rw;
	if (h < 0) h = rh;

	if (x<0) x=0;
	if (y<0) y=0;
	if (w<1) w=1;
	if (h<1) h=1;


	if ( (x+w) > rw ) w=rw-x;
	if ( (y+h) > rh ) h=rh-y;

	if ( (w<0) || (h<0) ) return NULL;

	THIS->page->displaySlice(THIS->dev,72.0*scale,72.0*scale,
			   rotation,
			   false,
			   true,
			   x,y,w,h,
			   false);
	
	map=THIS->dev->getBitmap();
	
	data=(uint32_t*)map->getDataPtr();


	*width = w;
	*height = h;

	return data;
}

BEGIN_METHOD(PDFPAGE_image, GB_INTEGER x; GB_INTEGER y; GB_INTEGER w; GB_INTEGER h)

	uint32_t *data;
	int32_t x,y, w, h;

	x = VARGOPT(x, 0);
	y = VARGOPT(y, 0);
	w = VARGOPT(w, -1);
	h = VARGOPT(h, -1);

	data = get_page_data(THIS, x, y, &w, &h, THIS->scale, THIS->rotation);
	if (!data) { GB.ReturnNull(); return; }
	/*GB.Image.Create(&img, data, w, h, GB_IMAGE_RGB);
	GB.ReturnObject(img);*/

	GB.ReturnObject(IMAGE.Create(w, h, GB_IMAGE_RGB, (unsigned char *)data));

END_METHOD

BEGIN_PROPERTY (PDFPAGE_property_image)

	int32_t w=-1;
	int32_t h=-1;
	uint32_t *data;

	data = get_page_data(THIS, 0, 0, &w, &h, THIS->scale, THIS->rotation);
	if (!data) { GB.ReturnNull(); return; }
	/*GB.Image.Create(&img, data, w, h, GB_IMAGE_RGB);
	GB.ReturnObject(img);*/

	GB.ReturnObject(IMAGE.Create(w, h, GB_IMAGE_RGB, (unsigned char *)data));

END_PROPERTY

BEGIN_METHOD(PDFPAGE_select, GB_INTEGER X; GB_INTEGER Y; GB_INTEGER W; GB_INTEGER H)

	TextOutputDev *dev;
#if CHECK_VERSION(250100)
	GooString str;
# else
	GooString *str;
#endif
#if CHECK_VERSION(241200)
	std::unique_ptr<Gfx> _gfx;
#endif
	Gfx *gfx;
	int32_t x,y,w,h;

	x = VARGOPT(X, 0);
	y = VARGOPT(Y, 0);
	w = VARGOPT(W, (int32_t)THIS->page->getMediaWidth());
	h = VARGOPT(H, (int32_t)THIS->page->getMediaHeight());

	dev = new TextOutputDev (NULL, true, 0, false, false);
#if CHECK_VERSION(241200)
	_gfx = THIS->page->createGfx(dev,72.0,72.0,0,false,true,-1, -1, -1, -1, NULL, NULL);
	gfx = _gfx.get();
#elif CHECK_VERSION(241100)
	gfx = THIS->page->createGfx(dev,72.0,72.0,0,false,true,-1, -1, -1, -1, NULL, NULL);
#else
	gfx = THIS->page->createGfx(dev,72.0,72.0,0,false,true,-1, -1, -1, -1, false, NULL, NULL);
#endif

	THIS->page->display(gfx);
	dev->endPage();

#if CHECK_VERSION(260100)
	str = dev->getText(PDFRectangle((double)x,(double)y,(double)(w+x),(double)(h+y)));
#else
	str = dev->getText((double)x,(double)y,(double)(w+x),(double)(h+y));
#endif

#if ! CHECK_VERSION(241200)
	delete gfx;
#endif
	delete dev;

#if CHECK_VERSION(250100)
	GB.ReturnNewString(str.getCString(),str.getLength());
#else
	if (!str)
	{
		GB.ReturnNewZeroString("");
		return;
	}
	
	GB.ReturnNewString(str->getCString(),str->getLength());	
	delete str;
#endif

END_METHOD

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

 Bookmarks of a PDF page

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

void aux_fill_links(void *_object)
{
	THIS->links = new Links (THIS->page->getAnnots ());
}

BEGIN_PROPERTY (PDFPAGELINKS_count)

	if (!THIS->links) aux_fill_links(_object);
	if (!THIS->links) { GB.ReturnInteger(0); return; }

#if CHECK_VERSION(220600)
	GB.ReturnInteger(THIS->links->getLinks().size());
#else
	GB.ReturnInteger(THIS->links->getNumLinks());
#endif

END_PROPERTY

BEGIN_METHOD (PDFPAGELINKS_get,GB_INTEGER ind;)

#if CHECK_VERSION(220600)

 	if (!THIS->links) aux_fill_links(_object);
	if (THIS->links && VARG(ind)>0)
 	{
		int i = 0;
		for (auto link : THIS->links->getLinks()) {
			if (VARG(ind)==i) {
				THIS->action=link->getAction();
				RETURN_SELF();
			}
		}
 	}
	GB.Error("Out of bounds");
    return;

#else

	bool pok = true;

	if (!THIS->links) aux_fill_links(_object);
	if (!THIS->links) pok=false;
	else
	{
		if (VARG(ind)<0) pok=false;
		else
		{
			if (VARG(ind)>=THIS->links->getNumLinks()) pok=false;
		}
	}

	if (!pok) { GB.Error("Out of bounds"); return; }

	THIS->lcurrent=VARG(ind);
	THIS->action=THIS->links->getLink(THIS->lcurrent)->getAction();

	RETURN_SELF();

#endif

END_METHOD

BEGIN_PROPERTY (PDFPAGELINKDATA_parameters)

	if (THIS->action->getKind() != actionLaunch )
	{
		GB.ReturnNewZeroString("");
		return;	
	}

	GB.ReturnNewZeroString(((LinkLaunch*)THIS->action)->getParams()->getCString());

END_PROPERTY

BEGIN_PROPERTY (PDFPAGELINKDATA_uri)

	char *uri;

	uri=aux_get_target_from_action(THIS->action);

	GB.ReturnNewZeroString(uri);
	if (uri) GB.FreeString(&uri);

END_PROPERTY

BEGIN_PROPERTY(PdfPageLinkData_Rect)

	CPDFRECT *rect = create_rect();
	aux_get_dimensions_from_action(THIS->action, rect);
	GB.ReturnObject(rect);

END_PROPERTY

BEGIN_PROPERTY(PDFPAGELINKDATA_zoom)

	GB.ReturnFloat(aux_get_zoom_from_action(THIS->action));

END_PROPERTY

BEGIN_PROPERTY(PDFPAGELINKDATA_page)

	GB.ReturnInteger(aux_get_page_from_action(_object,THIS->action));

END_PROPERTY

BEGIN_PROPERTY(PDFPAGELINKDATA_type)

	GB.ReturnInteger ( (int32_t)THIS->action->getKind() );

END_PROPERTY

BEGIN_PROPERTY(PDFPAGELINKDATA_check)

	if (THIS->action)
		RETURN_SELF();
	else
		GB.ReturnNull();

END_PROPERTY

static void aux_get_link_dimensions(void *_object, CPDFRECT *rect)
{
	double l,t,w,h;
	double pw,ph;

	pw=THIS->page->getMediaWidth();	
	ph=THIS->page->getMediaHeight();

#if CHECK_VERSION(220600)

	uint i = 0;
	l = t = w = h = 0;
	for (auto link : THIS->links->getLinks()) {
		if (i == THIS->lcurrent) {
			link->getRect(&l, &t, &w, &h);
			break;
		}
		i++;
	}

#else

	THIS->links->getLink(THIS->lcurrent)->getRect(&l, &t, &w, &h);

#endif

	w -= l;
	h -= t;

	switch (get_rotation(THIS))
	{
		case 0:
			rect->x = (l*THIS->scale);
			rect->y = ((ph-t-h)*THIS->scale);
			rect->w = (w*THIS->scale);
			rect->h = (h*THIS->scale);
			break;
	
		case 90:
			rect->y = (l*THIS->scale);
			rect->x = (t*THIS->scale);
			rect->h = (w*THIS->scale);
			rect->w = (h*THIS->scale);
			break;

		case 180:
			rect->x = ((l-w)*THIS->scale);
			rect->y = (t*THIS->scale);
			rect->w = (w*THIS->scale);
			rect->h = (h*THIS->scale);
			break;

		case 270:
			rect->y = ((pw-l-w)*THIS->scale);
			rect->x = ((ph-t-h)*THIS->scale);
			rect->h = (w*THIS->scale);
			rect->w = (h*THIS->scale);
			break;
	}
}

BEGIN_PROPERTY(PdfPageLink_rect)

	CPDFRECT *rect = create_rect();
	aux_get_link_dimensions(THIS, rect);
	GB.ReturnObject(rect);

END_PROPERTY


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

 Finding a text in a PDF page

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

BEGIN_METHOD (PDFPAGE_find,GB_STRING Text; GB_BOOLEAN Sensitive;)

	TextOutputDev *textdev;
	double x0=0, y0=0;
	double x1, y1;
	CPDFFIND *el;	
	Unicode *block=NULL;
	int nlen=0;
	bool sensitive=false;
	int count;
	double x, y, w, h, wp, hp;
	int rotation;

	// TODO: Use UCS-4BE on big endian systems?
	if (GB.ConvString ((char **)(void *)&block,STRING(Text),LENGTH(Text),"UTF-8",GB_SC_UNICODE))
	{	
		GB.Error("Invalid UTF-8 string");
		return;
	}

	nlen=GB.StringLength((char*)block)/sizeof(Unicode);

	if (!MISSING(Sensitive)) sensitive=VARG(Sensitive);

	textdev = new TextOutputDev (NULL, true, 0, false, false);
	THIS->page->display (textdev, 72, 72, 0, false, false, false);

	if (THIS->Found) { GB.FreeArray(POINTER(&THIS->Found)); THIS->Found=NULL; }

	count = 0;
	while (textdev->findText (block,nlen,false,true,true,false,sensitive,false,false,&x0,&y0,&x1,&y1))
	{
		if (!THIS->Found)
			GB.NewArray(POINTER(&THIS->Found),sizeof(CPDFFIND),1);
		else
			GB.Add(POINTER(&THIS->Found));

		el = &(THIS->Found[count++]); //(CPDFFIND*)&((CPDFFIND*)THIS->Found)[GB.Count(POINTER(THIS->Found))-1];
		
		x = x0;
		y = y0;
		w = x1 - x0;
		h = y1 - y0;

		wp = THIS->page->getMediaWidth();
		hp = THIS->page->getMediaHeight();
		rotation = THIS->page->getRotate();
		if (rotation == 90 || rotation == 270)
		{
			x0 = wp; wp = hp; hp = x0;
		}

		rotation = THIS->rotation; //get_rotation(THIS);
		while (rotation > 0)
		{
			x0 = wp; wp = hp; hp = x0;

			x0 = wp - y - h;
			y0 = x;

			x = w; w = h; h = x;

			x = x0;
			y = y0;

			rotation -= 90;
		}

		el->x0 = x * THIS->scale;
		el->y0 = y * THIS->scale;
		el->x1 = w * THIS->scale;
		el->y1 = h * THIS->scale;
	}

	delete textdev;

	GB.ReturnBoolean(count == 0);

END_METHOD


BEGIN_METHOD(PDFPAGERESULT_get,GB_INTEGER Index)

	CPDFRECT *rect;
	CPDFFIND *el;
	int index;

	index = VARG(Index);
	
	if (!THIS->Found || index < 0 || index >= GB.Count(THIS->Found))
	{
		GB.Error("Out of bounds");
		return;
	}

	el = &(THIS->Found[index]);
	rect = create_rect();
	
	rect->x = el->x0;
	rect->y = el->y0;
	rect->w = el->x1;
	rect->h = el->y1;
	
	GB.ReturnObject(rect);

END_METHOD

BEGIN_PROPERTY (PDFPAGERESULT_count)

	if (!THIS->Found) { GB.ReturnInteger(0); return; } 
	GB.ReturnInteger( GB.Count(POINTER(THIS->Found)) );

END_PROPERTY


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

Gambas Interface

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

GB_DESC PdfRectDesc[] =
{
	GB_DECLARE("PdfRect", sizeof(CPDFRECT)), GB_NOT_CREATABLE(),
	
	GB_PROPERTY_READ("X", "f", PdfRect_X),
	GB_PROPERTY_READ("Y", "f", PdfRect_Y),
	GB_PROPERTY_READ("Width", "f", PdfRect_Width),
	GB_PROPERTY_READ("Height", "f", PdfRect_Height),
	GB_PROPERTY_READ("W", "f", PdfRect_Width),
	GB_PROPERTY_READ("H", "f", PdfRect_Height),
	GB_PROPERTY_READ("Left", "f", PdfRect_X),
	GB_PROPERTY_READ("Top", "f", PdfRect_Y),
	GB_PROPERTY_READ("Right", "f", PdfRect_Right),
	GB_PROPERTY_READ("Bottom", "f", PdfRect_Bottom),
	
	GB_END_DECLARE
};


GB_DESC PdfResultDesc[]=
{
	GB_DECLARE(".PdfDocumentPage.Result",0), GB_VIRTUAL_CLASS(),

	GB_METHOD("_get","PdfRect",PDFPAGERESULT_get,"(Index)i"),
	GB_PROPERTY_READ("Count","i",PDFPAGERESULT_count),

	GB_END_DECLARE
};


GB_DESC PdfLinkDataDesc[]=
{
	GB_DECLARE(".PdfDocumentPage.Link.Data",0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY_READ("Type","i",PDFPAGELINKDATA_type),
	GB_PROPERTY_READ("Target","s",PDFPAGELINKDATA_uri),
	GB_PROPERTY_READ("Parameters","s",PDFPAGELINKDATA_parameters),
	GB_PROPERTY_READ("Page","i",PDFPAGELINKDATA_page),
	GB_PROPERTY_READ("Zoom","f",PDFPAGELINKDATA_zoom),
	GB_PROPERTY_READ("Rect", "PdfRect", PdfPageLinkData_Rect),

	GB_END_DECLARE
};


GB_DESC PdfLinkDesc[]=
{
	GB_DECLARE(".PdfDocumentPage.Link",0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY_READ("Rect", "PdfRect", PdfPageLink_rect),
	GB_PROPERTY_READ("Data",".PdfDocumentPage.Link.Data", PDFPAGELINKDATA_check),

	GB_END_DECLARE
};


GB_DESC PdfIndexDesc[]=
{
	GB_DECLARE(".PdfDocument.Index",0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY("Expanded","b",PDFINDEX_is_open),
	GB_PROPERTY_READ("Count","i",PDFINDEX_count),
	GB_PROPERTY_READ("HasChildren","b",PDFINDEX_has_children),
	GB_PROPERTY_READ("Title","s",PDFINDEX_title),
	GB_PROPERTY_READ("Text","s",PDFINDEX_title),

	GB_PROPERTY_READ("Data", ".PdfDocumentPage.Link.Data", PDFPAGELINKDATA_check),
	GB_METHOD("MovePrevious","b",PDFINDEX_prev,0),
	GB_METHOD("MoveNext","b",PDFINDEX_next,0),
	GB_METHOD("MoveChild","b",PDFINDEX_child,0),
	GB_METHOD("MoveParent","b",PDFINDEX_parent,0),
	GB_METHOD("MoveRoot",0,PDFINDEX_root,0),

	GB_END_DECLARE
};


GB_DESC PdfPageDesc[]=
{
	GB_DECLARE(".PdfDocumentPage",0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY_READ("W","f",PDFPAGE_width),
	GB_PROPERTY_READ("H","f",PDFPAGE_height),
	GB_PROPERTY_READ("Width","f",PDFPAGE_width),
	GB_PROPERTY_READ("Height","f",PDFPAGE_height),
	
	GB_PROPERTY_READ("Image","Image",PDFPAGE_property_image),
	GB_PROPERTY_SELF("Result",".PdfDocumentPage.Result"),

	GB_METHOD("GetImage","Image",PDFPAGE_image,"[(X)i(Y)i(Width)i(Height)i]"),
	GB_METHOD("Find","b",PDFPAGE_find,"(Text)s[(CaseSensitive)b]"),
	GB_METHOD("Select","s",PDFPAGE_select,"[(X)i(Y)i(W)i(H)i]"),

	GB_METHOD("_get",".PdfDocumentPage.Link",PDFPAGELINKS_get,"(Index)i"),
	GB_PROPERTY_READ("Count","i",PDFPAGELINKS_count),

	GB_END_DECLARE
};


GB_DESC PdfDocumentInfo[] =
{
	GB_DECLARE(".PdfDocument.Info",0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY_READ("Title","s",PDFINFO_title),
	GB_PROPERTY_READ("Format","s",PDFINFO_format),
	GB_PROPERTY_READ("Author","s",PDFINFO_author),
	GB_PROPERTY_READ("Subject","s",PDFINFO_subject),
	GB_PROPERTY_READ("Keywords","s",PDFINFO_keywords),
	GB_PROPERTY_READ("Creator","s",PDFINFO_creator),
	GB_PROPERTY_READ("Producer","s",PDFINFO_producer),
	GB_PROPERTY_READ("CreationDate","d",PDFINFO_creation),
	GB_PROPERTY_READ("ModificationDate","d",PDFINFO_modification),
	GB_PROPERTY_READ("Linearized","b",PDFINFO_linearized),
	GB_PROPERTY_READ("Layout","i",PDFINFO_layout),
	GB_PROPERTY_READ("Mode","i",PDFINFO_mode),
	GB_PROPERTY_READ("CanCopy","b",PDFINFO_cancopy),
	GB_PROPERTY_READ("CanModify","b",PDFINFO_canmodify),
	GB_PROPERTY_READ("CanPrint","b",PDFINFO_canprint),
	GB_PROPERTY_READ("CanAddNotes","b",PDFINFO_canaddnotes),

	GB_END_DECLARE
};


GB_DESC PdfLayoutDesc[] =
{
  GB_DECLARE("PdfLayout", 0), GB_NOT_CREATABLE(),

  GB_CONSTANT("Unset","i",Catalog::pageLayoutNone),
  GB_CONSTANT("SinglePage","i",Catalog::pageLayoutSinglePage),
  GB_CONSTANT("OneColumn","i",Catalog::pageLayoutOneColumn),
  GB_CONSTANT("TwoColumnLeft","i",Catalog::pageLayoutTwoColumnLeft),
  GB_CONSTANT("TwoColumnRight","i",Catalog::pageLayoutTwoColumnRight),
  GB_CONSTANT("TwoPageLeft","i",Catalog::pageLayoutTwoPageLeft),
  GB_CONSTANT("TwoPageRight","i",Catalog::pageLayoutTwoPageRight),

  GB_END_DECLARE
};


GB_DESC PdfModeDesc[] =
{
	GB_DECLARE("PdfPageMode",0), GB_NOT_CREATABLE(),

	GB_CONSTANT("Unset","i",Catalog::pageModeNone),
	GB_CONSTANT("UseOutlines","i",Catalog::pageModeOutlines),
	GB_CONSTANT("UseThumbs","i",Catalog::pageModeThumbs),
	GB_CONSTANT("FullScreen","i",Catalog::pageModeFullScreen),
	GB_CONSTANT("UseOC","i",Catalog::pageModeOC),
	GB_CONSTANT("UseAttachments","i",Catalog::pageModeAttach),

	GB_END_DECLARE
};

GB_DESC PdfDocumentDesc[] =
{
  GB_DECLARE("PdfDocument", sizeof(CPDFDOCUMENT)),

  GB_CONSTANT("Unknown","i",actionUnknown),  /* unknown action */
  GB_CONSTANT("Goto","i",actionGoTo),        /* go to destination */
  GB_CONSTANT("GotoRemote","i",actionGoToR), /* go to destination in new file */
  GB_CONSTANT("Launch","i",actionLaunch),    /* launch app or open doc. */
  GB_CONSTANT("Uri","i",actionURI),          /* URI */
  GB_CONSTANT("Named","i",actionNamed),      /* named action*/
  GB_CONSTANT("Movie","i",actionMovie),      /* movie action */

  GB_CONSTANT("Normal","i",0),
  GB_CONSTANT("Sideways","i",90),
  GB_CONSTANT("Inverted","i",180),
  GB_CONSTANT("SidewaysInverted","i",270),

  GB_METHOD("_new", 0, PDFDOCUMENT_new, "[(File)s]"),
  GB_METHOD("_free", 0, PDFDOCUMENT_free, 0),

  GB_METHOD("Open",0,PDFDOCUMENT_open,"(File)s"),
  GB_METHOD("Close",0,PDFDOCUMENT_close,0),
  GB_METHOD("_get",".PdfDocumentPage",PDFDOCUMENT_get,"(Index)i"),

  GB_PROPERTY("Zoom", "f", PDFDOCUMENT_scale),
  GB_PROPERTY("Orientation", "i", PDFDOCUMENT_rotation),

  GB_PROPERTY_READ("Ready","b",PDFDOCUMENT_ready),
  GB_PROPERTY_READ("Count","i",PDFDOCUMENT_count),
  GB_PROPERTY_READ("HasIndex","b",PDFDOCUMENT_has_index),
  GB_PROPERTY_READ("Index",".PdfDocument.Index",PDFDOCUMENT_index),
  GB_PROPERTY_READ("Info",".PdfDocument.Info",PDFDOCUMENT_info),

  GB_END_DECLARE
};


