/* $Header$ */

// base WP image object class implementation

/**************** Changes ******************
Date			Edit				Bug					Description
06-Oct-22	rmm11749		ST/WO/2704	Issues with HTML (docview) when printed to a report.
16-Feb-22	rmm11442								Highlight function parameters in online help descriptions.
22-Jan-21	rmm_svg3								Support for themed fat client SVG icons.
15-Dec-20	rmm_ncs									New Component Store.
16-Jan-20	jmg_bytepacking					Rationalised byte packing macros.
03-Jan-19	rmm9887			ST/HE/1560	Problems viewing and printing Omnis help pages.
19-Aug-16	rmm9010			ST/EC/1416	Added support for large files (files with a size > maximum signed 32 bit integer).
01-Sep-14	rmm8428			ST/EC/1346	HTML control printing issues.
17-Apr-12	rmm64bit1								First set of changes for 64 bit support: 64 bit Omnis data type.
27-Jul-07	rmm6183			ST/BE/161		Noticed some localisation issues in docview.
27-Apr-06	rmm5732			ST/RC/979		HTML object did not print on reports.
06-Dec-05	pkmach_o								pkmacho compiler changes - gStringTable now allocated and not a global static
12-Feb-03	rmmdocgen								Studio 4.0 documentation generator.
20 NOV 01 mpm5023a								Now make copy of data prior to converting for returning to data field
23-Apr-01	AE5204									Added hand cursor to image links
22 MAY 00	mpm4700									Implements printing of document
22-Feb-00	rmm3657			ST/EC/440		Problem with relative HTML links to images.
08 NOV 99 mlr0006									Added Rtf support
********************************************/

#ifndef _extcomp_
#include "extcomp.he" // jmg_includecasing
#endif

#ifndef	_WPIMAGE_HE_
#include "wpimage.he" // jmg_includecasing
#endif

#ifndef	_WPHTML_HE_
#include "wphtml.he" // jmg_includecasing
#endif

#include "htmlctrl.he"	// rmm11749

// These are for the png picture for rtf from colpix.h
// need to keep in step as the size is important

#ifdef iswin32 // jmg_bytepacking? Should we be doing this for macOS too?
	OM_BYTE_PACKING_ON // jmg_bytepacking
#endif

struct MacRect {
  qshort       top;
  qshort       left;
  qshort       bottom;
  qshort       right;
};
 
///////////////////////////////////////////////////////////////////////
// the shared picture header structure.
//   there are mac and windows equivalents for all these- but NOTA BENE
//   bytesPerRow - must always be even, (== width or width+1)
//                 mac upper limit is currently 0x4000. The highest bit
//                 should be ignored anyway. Calculations based on
//                 rowBytes should take into account bitsPerPixel.
///////////////////////////////////////////////////////////////////////
 
struct SharedColorPicture {
 
  qword2       scpSize;       // size of this structure - MUST BE SET
  qbyte        scpSig;        // always 0xCC for ColorpiCture
  qbyte        scpVersion;    // version number from '\x10' to '\xFF'
  qword2       bytesPerRow;   // even number of bytes as used by Mac
 
  MacRect      bounds;
 
  qword2       pixType;       // USE_TABLE or USE_RGBnn
  qword2       bitsPerPixel;  // 1,4,8,16 or 32
  qword2       compCount;     // 1 == indexed pixels, 3 == RGBSpec.
  qword2       compSize;      // indexed:  must be == bitsPerPixel.
                              // RGBSpec:  == 8 for 32bit
                              //           == 5 for 16bit
 
  qword2       ctableSize;    // set to 0 if mono bitmap
                              // else it includes both PaletteTable header and
                              // all the RGBSpec entries
  qword4       mapsize;       // setup as rowBytes*(bottom-top+1)
 
  qword2       mCompression;  // 0 - None, 1 - RLE	// mt40094  

  qword2       reserved2;     // must be 0
  qword4       reserved3;     // must be 0
  qword4       reserved4;     // must be 0
 
  //    color table (if it exists) follows immediately
  //    array of bitmap data follows the color table
};
 
#ifdef iswin32 // jmg_bytepacking? Should we be doing this for macOS too?
	OM_BYTE_PACKING_OFF // jmg_bytepacking
#endif

void swap(qint2& n)
{
  qint1* a=(qint1*)&n, n1=a[0]; a[0]=a[1]; a[1]=n1;
}

// these are from rtf

#define RTF_CR_HEXD 0x0d
#define RTF_LF_HEXA 0x0a

// ###############################################################################
// ################# WPImageClass Public #########################################
// ###############################################################################


WPImageClass::WPImageClass( WPObjectClass* pParent, WPDocClass* pDocumentObj )
						 :WPObjectClass( pParent, pDocumentObj )
{
	mHwnd = 0;
	mLinkInfo = 0;
	mPngType = qfalse;
	mImageHan = NULL;
	mIconApp = 0;						// rmm_ncs
	mIconSize = ePicDef;		// rmm_ncs
}


WPImageClass::~WPImageClass()
{
	if ((mPngType == qtrue) && (mImageHan != NULL) )
		HANglobalFree(mImageHan);
}

// pkmach_o
const str15 typeJPEGx(QTEXT("JPEG"));
const str15	typeGIFx(QTEXT("GIF"));
const str15 typePCXx(QTEXT("PCX"));
const str15 typePNGx(QTEXT("PNG"));
const str15 propSRC(QTEXT("$path"));
const str15 propGIFbackcolor(QTEXT("$::backcolor"));
const str15 JPEGext(QTEXT("JPG"));
const str15 GIFext(QTEXT("GIF"));
const str15 PCXext(QTEXT("PCX"));
const str15 PNGext(QTEXT("PNG")); // mpm4700


WPImageClass*	WPImageClass::make( WPObjectClass* pParent, WPDocClass* pDocumentObj,
																	WPImageInfoStruct* pImageInfo, qchar* pPath, qlong pPathLen )
{
	WPImageClass* obj = new WPImageClass( pParent, pDocumentObj );
	// Start rmm_ncs: If the img has a data-objtype attribute, try and use that to get the icon id of the
	// icon to draw for the image
	obj->mImageInfo.makeFrom(*pImageInfo); 
	if (obj->mImageInfo.mObjType && obj->mImageInfo.mObjType->length())
	{
		obj->mIconId = *obj->mImageInfo.mObjType;
		obj->mIconApp = (qapp) WNDsendMessage(0, WM_CONTROL, DOCVIEW_MAP_OBJTYPE_TO_ICONID, LPARAM(&obj->mIconId));
		if (obj->mIconApp)
		{
			// obj->mIconId is now the icon id; need to apply a size to it
			qlong x;
			if (stringToQlong(obj->mIconId, x))
			{
				// Strip size
				x &= 0xfffffff;
				qlongToString(x, obj->mIconId);
				// Set size based on width and height
				if (pImageInfo->mWidth < 32 && pImageInfo->mHeight < 32)
					obj->mIconSize = ePic16;
				else if (pImageInfo->mWidth < 48 && pImageInfo->mHeight < 48)
					obj->mIconSize = ePic32;
				else
					obj->mIconSize = ePic48;
			}
			else
			{
				EXTfldval fval;
				fval.setChar(obj->mIconId);
				EXTBMPref::addBmpSize(fval, ePicDef, (pImageInfo->mWidth << 16) | pImageInfo->mHeight);
				fval.getChar(obj->mIconId);
			}
			return obj;
		}
	}
	// End rmm_ncs
	str15 pluginType;
	str15 ext;
	str255 path( (qshort)pPathLen, pPath );
	// Start rmm3657: need to search backwards, to avoid problems caused by .. and relative paths.
	for (qshort i = path.length() - 3; i >= 1; --i)
	{
		if ('.' == path[i])
		{
			ext.copy(path, i + 1, 3);
			break;
		}
	}
	if (!ext.length()) return obj;
	ext.upps();
	// End rmm3657
	if ( ext == JPEGext )
	{
		obj->mImageType = typeJPEGx;
	}
	else if ( ext == GIFext )
	{
		obj->mImageType = typeGIFx;
	}
	else if ( ext == PCXext )
	{
		obj->mImageType = typePCXx;
	}
	else if ( ext == PNGext )
	{
		obj->mImageType = typePNGx;
	}
	
	// prepare the source path
	str255 fullPath( (qshort)pPathLen, pPath );
	pDocumentObj->fullPath( fullPath );
	WPHtmlClass::htmlToApiPath( fullPath );
	
	if (FILEexists(fullPath))
	{
		obj->mFullPath = fullPath;
		if (!pDocumentObj->printing()) // mpm4700
		{
			EXTqlist lst(listVlen);
			lst.setFinalRow(1);
			EXTfldval fvalp;

			// assign the path
			lst.getColValRef(1, 1, fvalp, qtrue);
			fvalp.setChar(propSRC);
			lst.getColValRef(1, 2, fvalp, qtrue);
			fvalp.setChar(fullPath);

			if (obj->mImageType == typeGIFx)
			{ // assign the backcolor
				lst.setFinalRow(2);
				lst.getColValRef(2, 1, fvalp, qtrue);
				fvalp.setChar(propGIFbackcolor);
				lst.getColValRef(2, 2, fvalp, qtrue);
				qlongToString<qlong>(obj->getBackColor(), path); // rmm64bit1
				fvalp.setChar(path);
			}
			obj->mHwnd = pDocumentObj->getPlugin(obj->mImageType, lst, obj->mImageInfo.mWidth, obj->mImageInfo.mHeight);
			if (obj->mHwnd)
			{
				qulong style = WNDgetWindowLong(obj->mHwnd, GWL_EXSTYLE);
				style |= WND_TRANSPARENT;
				WNDsetWindowLong(obj->mHwnd, GWL_EXSTYLE, style);
				WNDsendMessage(obj->mHwnd, WM_EXUSER + 2, 1, 0);
			}
		}
	}
	return obj;
}


WPImageClass*	WPImageClass::make(WPObjectClass* pParent, WPDocClass* pDocumentObj,
																 WPImageInfoStruct* pImageInfo, qlong pPictStart, qlong pPictLen)
{
	WPImageClass* obj = new WPImageClass( pParent, pDocumentObj );
	obj->mImageInfo.makeFrom(*pImageInfo); // rmm_ncs
	obj->createPngHandle(pPictStart,pPictLen);
	return obj;
}

// AE5204
HCURSOR WPImageClass::getCursor( WPObjInfoStruct* pObjInfo, qpoint* pPoint )
{
	return (mLinkInfo && !mLinkInfo->mIsBookmark && mLinkInfo->mHRef.length()) ? WND_CURS_FINGER : WND_CURS_DEFAULT; // rmmdocgen: use finger cursor for links // rmm11442: Check length
}

void WPImageClass::createPngHandle(qlong pPictStart, qlong pPictLen )
{
	mPngType = qtrue;
	qHandle pHan = mDocumentObj->docHan( qfalse ); // mpm5023a
	qHandleTextPtr origPtr = qHandleTextPtr(pHan,0);

	// This creates a char string of binary data, needs to be
	// temp as dont know how long it'll be before do this.
	char *tempBinStr = new char[pPictLen];
	qlong charNo=0 ;
	qlong end = pPictStart + pPictLen ;
	for (qlong index = pPictStart ; index < end ; index=index+2 )
	{
		// ignore all cr & line feeds -1+2=+1 so actully move forward 1. 
		if ( (origPtr[index]  == RTF_CR_HEXD )|| ( origPtr[index]  == RTF_LF_HEXA ))
		{
			index-- ;
		}
		else
		{
			tempBinStr[charNo] = char((hexToNibble(origPtr[index]) << 4) + hexToNibble(origPtr[index + 1]));
			charNo++ ;
		}
	}
	
	qlong actPictLen = charNo ;
	// This creates a new handle of length + size of bin data actPictLen 
	// 2 headers tqgpict & SharedColorPicture, fills 
	// the headers then the data for the picture.
	qlong dataSize = actPictLen +( sizeof(tqgpict) + sizeof(SharedColorPicture) );
	// This zeros the contents as well
	mImageHan = HANglobalAlloc(dataSize,qtrue); 	
	qHandlePtr hanPtr = qHandlePtr(mImageHan, 0);
	hanPtr.dataLen(dataSize);
	qbyte* pngData = *hanPtr;
	tqgpict* pictHdr = (tqgpict*)pngData;
	pictHdr->ptyp=ptypColShared24;
			
	// now incease ptr by size of tqgpict header 
	SharedColorPicture *spHdr = (SharedColorPicture *)*qHandlePtr(mImageHan,sizeof(tqgpict));
	spHdr->scpSize = sizeof(SharedColorPicture);
	spHdr->scpSig  = 0xCC;
	spHdr->scpVersion = 0x10;
	spHdr->bounds.bottom = (qshort)mImageInfo.mHeight;
	spHdr->bounds.right  = (qshort)mImageInfo.mWidth;
	#if defined( iswin32)
		swap(spHdr->bounds.bottom );	
		swap(spHdr->bounds.right);
	#endif
	hanPtr = qHandlePtr(mImageHan,sizeof(tqgpict)+sizeof(SharedColorPicture));
	pngData = *hanPtr;
	// movel source, dest length		
	movel(tempBinStr,pngData,actPictLen);
	delete[] tempBinStr;
}

void WPImageClass::initData( WPObjInfoStruct* pObjInfo ,printInfo* pInfo)
{
	pObjInfo->mVAlign = mImageInfo.mAlign;
	if ( !mImageInfo.mHeight ) mImageInfo.mHeight = 16;
	if ( !mImageInfo.mWidth ) mImageInfo.mWidth = 16;

	qlrect& ownerRect = pObjInfo->mOwnerRect;
	qlrect& rowRect = pObjInfo->mRowRect;
	qldim maxWidth = rowRect.right - ownerRect.left + 1;
	qldim width = mImageInfo.mWidth + mImageInfo.mBorderWidth * 2 + mImageInfo.mHorzSpace * 2;
	qldim height = mImageInfo.mHeight + mImageInfo.mBorderWidth * 2 + mImageInfo.mVertSpace * 2;
	if ( width > maxWidth && !pObjInfo->mNoWrap )
	{
		mParent->newRow( pObjInfo );
	}

	ownerRect.right = ownerRect.left + width - 1;
	ownerRect.bottom = ownerRect.top + height - 1;
	if ( mImageInfo.mAlign == eWPVAbaseline ) pObjInfo->mOwnerBaseLine = ownerRect.bottom;
	mParent->addRowInfo( pObjInfo );
	mWidth = width; // mpm4700
}


void WPImageClass::paint( WPObjInfoStruct* pObjInfo, WNDpaintStruct* pPaintStruct, qldim pHorzOff, qldim pVertOff )
{
	if (!pObjInfo) return;
	HDC dc = pPaintStruct->hdc;
	if ((mHwnd) || (mPngType == qtrue) || mIconApp) // rmm_ncs
	{
		qrect frameRect, r;
		qlrect lr = pObjInfo->mOwnerRect; lr.offset(pHorzOff, pVertOff);
		if (mImageInfo.mBorderWidth)
		{
			GDItextSpecStruct ts; mImageInfo.mTextInfo.getTextSpec(mDocumentObj, &ts);
			lr.conv(&r);
			HPEN pen = GDIcreatePen(mImageInfo.mBorderWidth, ts.mTextColor);
			pen = GDIselectObject(dc, pen);
			GDIframeRect(dc, &r);
			pen = GDIselectObject(dc, pen);
			GDIdeleteObject(pen);
		}

		lr.inset(mImageInfo.mHorzSpace + mImageInfo.mBorderWidth, mImageInfo.mVertSpace + mImageInfo.mBorderWidth);
		lr.conv(&r);
		// PKHTML
		// Start rmm_ncs
		if (mIconApp)
		{
			EXTfldval fval;
			fval.setChar(mIconId);
			GDIsetTextColor(dc, GDI_COLOR_WINDOWTEXT);	// rmm_svg3
			EXTBMPref br(fval, mIconApp);
			br.draw(dc, &r);
		}
		else if (mPngType == qfalse)
		{
			// End rmm_ncs
			// Start rmm9887: This code to move the window is redundant, and was causing continuous painting in some cases, due to rounding
			// issues when scaling for high DPI - the window does not need to be moved, as WM_EXUSER+1 just draws the JPEG in the rectangle it is
			// given, in the HDC for the docview object.
			//	WNDgetWindowRect(mHwnd,&frameRect); WNDmapWindowRect( HWND_DESKTOP, WNDgetParent(mHwnd),&frameRect);
			//	if ( frameRect.left!=r.left || frameRect.top!=r.top )
			//		WNDmoveWindow( mHwnd, r.left, r.top, r.width(), r.height(), qtrue );
			// End rmm9887
			WNDpaintStruct ps; ps.rcPaint = r; ps.hdc = dc;
			WNDsendMessage(mHwnd, WM_EXUSER + 1, 0, (LPARAM)&ps);
		}
		else
		{
			qHandlePtr hanPtr = qHandlePtr(mImageHan, 0);
			qlong dataSize = hanPtr.dataLen();
			GDIdrawPicture(dc, *hanPtr, dataSize, jstLeft, &r, qfalse, qfalse);
		}
	}
	else
	{
		HPEN pen = GDIcreatePen( 1 );
		pen = GDIselectObject( dc, pen );
		qlrect lr = pObjInfo->mOwnerRect;
		lr.offset( pHorzOff, pVertOff );
		lr.inset( mImageInfo.mHorzSpace, mImageInfo.mVertSpace );
		qrect r; lr.conv( &r );
		GDIframeRect( dc, &r );
		pen = GDIselectObject( dc, pen );
		GDIdeleteObject( pen );
		
		if (mImageInfo.mText.length())
		{
			GDItextSpecStruct ts; mImageInfo.mTextInfo.getTextSpec(mDocumentObj, &ts);
			HFONT fnt = GDIcreateFont(&ts.mFnt, ts.mSty);
			fnt = GDIselectObject(dc, fnt);

			GDIinsetRect(&r, 1, 1);
			qdim height = GDIfontHeight(dc);
			qdim width = GDItextWidth(dc, &mImageInfo.mText[1], mImageInfo.mText.length());
			qdim top = r.top + (r.height() / 2) - (height / 2);
			qdim left = r.left + (r.width() / 2) - (width / 2);

			GDIsetTextColor(dc, ts.mTextColor);
			GDIsetClipRect(dc, &r);
			GDIdrawText(dc, left, top, &mImageInfo.mText[1], mImageInfo.mText.length(), jstLeft);
			GDIclearClip(dc);

			fnt = GDIselectObject(dc, fnt);
			GDIdeleteObject(fnt);
		}
	}
}


qldim WPImageClass::getMinWidth() // mpm4700
{
	return mWidth;
}


qprierr WPImageClass::addToPrintJob(  printInfo* pInfo ,EXTCompInfo* eci,WPObjInfoStruct* pObjInfo, // mpm4700
																			qldim& pHorzOff, qldim& pVertOff )
{
	qprierr err = PRI_ERR_NONE;
	
	PRIobjectStruct obj = *pInfo->mObj;
	obj.mType = PRI_OBJ_PICTURE;
	obj.mHorzExtend = qfalse;	// rmm5732: image was incorrectly horizontally stretched
	obj.mVertExtend = qfalse;	// rmm9887
	// Start rmm8428: Remove default border from picture
	if (WND_BORD_PLAIN == obj.mBorder.mBorderStyle && 3 == obj.mBorder.mLineStyle.mWidth && !obj.mBorder.mLineStyle.mColor && !obj.mBorder.mLineStyle.mPat)
	{
		#ifdef iswin32
			obj.mBorder.set(WND_BORD_NONE, qpen(0, colBlack, patTransparent));
		#else
			obj.mBorder.set(WND_BORD_NONE, qpen(0, colBlack, patEmpty));
		#endif
	}
	// End rmm8428

	qlrect lr = pObjInfo->mOwnerRect;
	lr.offset( pHorzOff, pVertOff );
	lr.convto( &obj.mPos );
	
	if ( mPngType == qfalse) // ie a file
	{
		EXTfile srcFile; 
		qret fileOpened = srcFile.open( mFullPath, qtrue, qfalse );
		if ( fileOpened == e_ok )
		{
			qlong len = (qlong)srcFile.getLength(); // rmm9010
			qHandlePtr srcPtr ;
			qHandle srcHan = HANglobalAlloc( len );
			srcPtr = qHandlePtr( srcHan, 0 );
			srcFile.read( *srcPtr, 0, len );
			srcPtr.dataLen(len);
			str255 jpegVar = QTEXT("__HtmlImageBuffer");
			EXTfldval instanceVar(jpegVar,qtrue,eci->mLocLocp);
			instanceVar.setHandle( fftPicture, srcHan, qfalse );

			str255 fileName(QTEXT("pictconvto(\"$\",__HtmlImageBuffer,\"CS24\")")); // mpm4700
			fileName.insertStr( mImageType ); // mpm4700
			ECOreplaceSeparators(fileName);	// rmm6183
			
			EXTfldval fcalc, fresult;
			fcalc.setCalculation(eci->mLocLocp,ctyCalculation,&fileName[1],fileName.length());
			if ( fcalc.evalCalculation(fresult, eci->mLocLocp,NULL, qfalse ))
			{
				obj.mData = fresult.getFldVal() ;
				err = tqfWPObject::doPRIaddObject( pInfo->mJob, &obj ); 
			}
			srcFile.close();
		}
	}
	else if ( mImageHan != NULL )
	{
		EXTfldval pictFldval; pictFldval.setHandle( fftPicture, mImageHan, qtrue );
		obj.mData =  pictFldval.getFldVal();
		err = tqfWPObject::doPRIaddObject( pInfo->mJob, &obj );
	}
	return err;
}


qlong WPImageClass::message( WPObjInfoStruct* pObjInfo, eWPMessage pMessage, qldim pHorzOff, qldim pVertOff, LPARAM lParam1, LPARAM lParam2 )
{
	if ( pMessage == eWPMessFindHwnd && HWND(lParam1) == mHwnd )
	{
		pObjInfo->mOwnerRect.offset( pHorzOff, pVertOff );
		pObjInfo->mOwnerBaseLine += pVertOff;
		return 1L;
	}
	return 0L;
}


WPLinkInfoStruct* WPImageClass::getLinkInfo()
{
	return (mLinkInfo && !mLinkInfo->mIsBookmark && mLinkInfo->mHRef.length()) ? mLinkInfo : 0; // rmm11442: Check length
}


void WPImageClass::setLinkInfo( WPLinkInfoStruct* pLinkInfo )
{
	if ( mLinkInfo != pLinkInfo )
	{
		mLinkInfo = new WPLinkInfoStruct;
		*mLinkInfo = *pLinkInfo;
	}
	
	if ( !mLinkInfo->mIsBookmark )
	{
		qlong x;
		eWPdocInfoSwitch infoSwitch = eWPdocInfoLinkColor;
		if ( mLinkInfo->mActive )
			infoSwitch = eWPdocInfoALinkColor;
		else if ( mLinkInfo->mVisited )
			infoSwitch = eWPdocInfoVLinkColor;
		if ( mDocumentObj->getDocumentInfo( infoSwitch, 0, x ) )
		{
			mImageInfo.mTextInfo.mTextColor = qcol(x);
			mImageInfo.mTextInfo.mSet |= WPTEXT_TEXTCOLORSET;
		}
		mImageInfo.mTextInfo.mFontStyUnderline = qtrue;
		mImageInfo.mTextInfo.mSet |= WPTEXT_FONTSTYUSET;
	}
}


/* eof */













