// BarCode.cpp

#include "stdafx.h"

#include "aplPdf.h"
#include "BarCode.h"

//********************************************************************************************
//********************************************************************************************
//********************************************************************************************
 
static const unsigned char CODE128A[]=
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";

static const unsigned char CODE128B[]=
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
"abcdefghijklmnopqrstuvwxyz{|}~\x7f";


#define BAR_BUF_LEN 128

#define SWAP(a,b) {int t=a; a=b; b=t;}
//#define SETRECT(r,a,b,c,d) {r.left=a; r.top=b; r.right=c; r.bottom=d;}

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

// Функции преобразования строки в данные методами A (bModeA==true) или B (bModeA==false)
// Если удалось - возвращает true, если встретился непреобразуемый символ - возвращает false
bool bEncode128AB(bool bModeA, LPCSTR str, unsigned char *data, int &out_len)
{
	out_len=0;
	if(0==str) return false;
	if('\0'==str[0]) return false;


	static unsigned char invCODE128A[128];
	static unsigned char invCODE128B[128];

	static bool bTableInitialized=false;
	if(!bTableInitialized)
	{
		// Инициализируем таблицу переобразования символ -> код для штихкода 
		int i; 
		for(i=0; i<sizeof(invCODE128A); i++) {invCODE128A[i]=0xff; invCODE128B[i]=0xff;}
		for(i=0; i<sizeof(CODE128A); i++) invCODE128A[CODE128A[i]]=i;
		for(i=0; i<sizeof(CODE128B); i++) invCODE128B[CODE128B[i]]=i;
		bTableInitialized=true;
	}

	unsigned int i=0, k=0;
	unsigned int sum=0;
	
	if(bModeA) data[0]=(unsigned char)103;
	else data[0]=(unsigned char)104;
	sum=data[0];
	k=1;

	unsigned char c=0,c2=0;
	while (true)
	{
		if(k >(BAR_BUF_LEN-3)) return false;
		c=str[k-1];
		if(0==c) break;
		
		if(bModeA) c2=invCODE128A[c];
		else c2=invCODE128B[c];

		if(0xff==c2) return false;

		sum+=k*c2;
		data[k++]=c2;
		
		if(k>(BAR_BUF_LEN-3)) return false;
	}

	unsigned char c_sum=sum%103;
	data[k++]=c_sum;
	data[k++]=106;

	out_len=k;
	return true;
}

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

// Функции преобразования строки в данные методами C 
// Если удалось - возвращает true, если встретился непреобразуемый символ - возвращает false
bool bEncode128C(LPCSTR str, unsigned char *data, int &out_len)//{return false;};
{
	out_len=0;
	if(0==str) return false;
	if('\0'==str[0]) return false;

	unsigned int i=0, k=0;
	
	// Проверяем на символы и считаем их кол-во
	while (true)
	{
		unsigned char c=str[i];
		if(0==c) break;
		if(c < '0' || c >'9')return false;
		i++;
	}	

	unsigned int len=i;
	unsigned int sum=0;
	
	data[0]=(unsigned char)105;
	sum=data[0];
	
	i=0; k=1;
	if(len & 0x1) // число символов = нечетное
	{
		// Обрабатываем только первый байт как 
		unsigned char c=str[0]-'0';
		sum+=c;
		data[k++]=c;
		i=1;
	}

	unsigned char c0=0,c1=0,c2=0;
	for(;i<len;i+=2)
	{
		c0=str[i]-'0';
		c1=str[i+1]-'0';

		c2=c1+(10*c0);
		sum+=k*c2;
		data[k++]=c2;

		if(k>(BAR_BUF_LEN-3)) return false;
	}

	unsigned char c_sum=sum%103;
	data[k++]=c_sum;
	data[k++]=106;

	out_len=k;
	return true;
}

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

// Преобразует строку в строку для отображения штрихкодом шрифтом code128.ttf
// Если удалось - возвращает true, если встретился непреобразуемый символ - возвращает false

bool Str2BarCodeStr(LPCTSTR str, CString &sBarCode)
{
	sBarCode="";

	if(0==str) return false;
	if(_T('\0')==str[0]) return false;

	CStringA stransi(str);

	 unsigned char data[BAR_BUF_LEN];
	 memset(data,0,BAR_BUF_LEN);

	 int data_len=0;

	 // Преобразуем в данные
	 if( !bEncode128C(stransi, data, data_len))
	 {
		 if( !bEncode128AB(false, stransi, data, data_len))
		 {
			 if( !bEncode128AB(true, stransi, data, data_len)) return false;
		 }
	 }

	// Преобразуем в символы шрифта code128.ttf
    // https://www.idautomation.com/barcode-fonts/code-128/user-manual/
	int i;
	CStringA sBarCodeA;
	for(i=0;i<data_len;i++)
	{
		unsigned char c=data[i];
		if(0==c) c= ' '; //(char)194;
		else if(c<=94) c+=32;
		else c+=100;
		sBarCodeA+=c;
	}

	sBarCode=sBarCodeA;

	return true;
}

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

// Проверка работы Str2BarCodeStr
bool Str2BarCodeStr_AutoTest()
{
	CString sBarCode;

	Str2BarCodeStr(_T("1"),sBarCode); // Проверка кодировки C
	if(sBarCode!=_T("Н!#О")) return false;
	Str2BarCodeStr(_T("12"),sBarCode); // Проверка кодировки C
	if(sBarCode!=_T("Н,.О")) return false;
	Str2BarCodeStr(_T("123"),sBarCode); // Проверка кодировки C
	if(sBarCode!=_T("Н!7QО")) return false;

	Str2BarCodeStr(_T("123456789012"),sBarCode); // Проверка кодировки C
	if(sBarCode!=_T("Н,BXnz,VО")) return false;
	Str2BarCodeStr(_T("11231434645757"),sBarCode); // Проверка кодировки C
	if(sBarCode!=_T("Н+7.B`YY^О")) return false;
	Str2BarCodeStr(_T("4294967295"),sBarCode); // Проверка кодировки C
	if(sBarCode!=_T("НJ~ДhГOО")) return false;

	Str2BarCodeStr(_T("ABC"),sBarCode);   // Проверка кодировки A
	if(sBarCode!=_T("МABC!О")) return false;
	Str2BarCodeStr(_T("ABC !01234567_^ZZ"),sBarCode); // Проверка кодировки A
	if(sBarCode!=_T("МABC !01234567_^ZZ#О")) return false;
	Str2BarCodeStr(_T("z}~"),sBarCode); // Проверка кодировки B
	if(sBarCode!=_T("Мz}~LО")) return false;
	Str2BarCodeStr(_T("ABC !01234567_^z}~"),sBarCode); // Проверка кодировки B
	if(sBarCode!=_T("МABC !01234567_^z}~5О")) return false;

	return true;
}

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


//*************************************************************************
// Рисует штрих-код  
// dc - куда рисовать
// rect - Прямоугольник для отрисовки
// rotation - Угол в градусах (кратно 90) по часосой стрелке
// sText - Текст для отображения
// Возвращает true, если отрисовать удалось, и false, если не удалось

bool CLASS_DECL_APL_PDF DrawBarCode(CDC &dc, RECT &rect, int rotation, LPCTSTR sText)
{
	if(0==sText) return false;
	if('\0'==sText[0]) return false;

	static const TCHAR stFontName[]=_T("Code 128");
	//static const TCHAR stFontName[]=_T("Arial");


	// Нужная ширина и высота кода
	int w=rect.right-rect.left;  if (w<0) { w*=-1; SWAP(rect.left, rect.right);}
	int h=rect.bottom-rect.top;  if (h<0) { h*=-1; SWAP(rect.top,  rect.bottom);}

	if(0==w || 0==h) return false;

	int w_rect=w, h_rect=h;  // Запоминаем ширину и высоту исходного прямоугольника

	int iSignsRotation=rotation % 360; // Убираем лишние обороты

	if(90==iSignsRotation || 270==iSignsRotation) SWAP(w,h)


	CString sBarCode;
	if(!Str2BarCodeStr(sText, sBarCode)) return false; // Эту строку нельзя преобразовать в штихкод
	//sBarCode=sText;

	CFont tmpBarFont;
	if(!tmpBarFont.CreateFont(100,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,0,0,0,0,stFontName)) return false;

	CDC comp_dc;
	comp_dc.CreateCompatibleDC(&dc);

	// Определяем нужный размер шрифта
	RECT tmpRect={0,0,10000,10000};

	CFont *oldFont=comp_dc.SelectObject(&tmpBarFont);
	comp_dc.DrawText(sBarCode, &tmpRect, DT_SINGLELINE|DT_NOCLIP|DT_TOP|DT_LEFT|DT_CALCRECT);

	int w1=tmpRect.right-tmpRect.left;
	int h1=tmpRect.bottom-tmpRect.top;

	if(0==w1 || 0==h1) return false;

	int iHighFont= (100*w)/w1; // Размер шрифта чтобы получилась нужная ширина.

	CFont BarFont;
	if(!BarFont.CreateFont(iHighFont,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,0,0,0,0,stFontName)) return false;
	
	// Определяем реальный  размер штрихкода заданным шрифтом
	// ( реально не рисуется т.к. ещё не задан СBitmap)
	comp_dc.SelectObject(&BarFont);
	RECT tmpRect1={0,0,10000,10000};
	comp_dc.DrawText(sBarCode, &tmpRect1, DT_SINGLELINE|DT_NOCLIP|DT_TOP|DT_LEFT|DT_CALCRECT);

	int rtw=tmpRect1.right-tmpRect1.left; // Реальная ширина текста ( как если без поворота)
	int rth=tmpRect1.bottom-tmpRect1.top; // Реальная высота текста ( как если без поворота)

	// делаем повернутый шрифт
	if(0!=iSignsRotation)
	{
		comp_dc.SelectObject(&oldFont);
		BarFont.DeleteObject();
		if(!BarFont.CreateFont(iHighFont,0,-10*iSignsRotation,-10*iSignsRotation,FW_NORMAL,0,0,0,ANSI_CHARSET,0,0,0,0,stFontName)) return false;
		comp_dc.SelectObject(&BarFont);
	}

	RECT barRect={0,0,rtw,rth}; 

	if(180==iSignsRotation)     { RECT tmpret={rtw,rth,0,0}; barRect=tmpret;}
	else if(iSignsRotation==90) { RECT tmpret={rth,0,0,rtw}; barRect=tmpret; }
	else if(iSignsRotation==270){ RECT tmpret={0,rtw,rth,0}; barRect=tmpret; }

	// Определяем размер bmp. Он отличается от ихходного т.к. нужна большая высота чтобы резать вырезать часть шрихкода
	int cx_bmp=w;
	int cy_bmp=rth;
	if(90==iSignsRotation || 270==iSignsRotation) {cx_bmp=rth; cy_bmp=w;}
	
	// Создаем bitmap для отрисовки
	CBitmap bitmap;
	if(!bitmap.CreateCompatibleBitmap(&dc,cx_bmp,cy_bmp)) return false; // получилось выделить память
	comp_dc.SelectObject(bitmap);

	// Рисуем в bitmap
	comp_dc.FillSolidRect(0,0,cx_bmp,cy_bmp,RGB(255,255,255));
	comp_dc.SetTextColor(RGB(0,0,0));
	comp_dc.SetBkMode(TRANSPARENT);
	comp_dc.SelectObject(&BarFont);
	comp_dc.SelectStockObject(NULL_BRUSH);
	
	comp_dc.DrawText(sBarCode, &barRect, DT_SINGLELINE|DT_NOCLIP|DT_TOP|DT_LEFT);

	// кусок для отладки. чтобы понять что куда рисуется
	/*
	{
		RECT debug_rect=barRect;
		debug_rect.left+=200;
		debug_rect.right+=200;
		debug_rect.top+=200;
		debug_rect.bottom+=200;

		dc.SetTextColor(RGB(255,0,0));
		dc.SetBkMode(TRANSPARENT);
		dc.SelectObject(&BarFont);
		dc.DrawText(sBarCode, &debug_rect, DT_SINGLELINE|DT_NOCLIP|DT_TOP|DT_LEFT);

		CPen pen; pen.CreatePen(PS_SOLID,1,RGB(0,0,255));
		dc.SelectObject(&pen);
		dc.MoveTo(0,0);
		dc.LineTo(debug_rect.left,debug_rect.top);
		dc.Draw3dRect(&debug_rect,RGB(0,0,255),RGB(0,128,0));
		RECT r2={0,0,200,200};
		dc.Draw3dRect(&r2,RGB(255,0,0),RGB(255,0,0));
	}*/

	int dh=(int)(rth*0.15); // Верхняя часть кода, которую надо отрезать

	if(dh<3) { dh=rth-4; if(dh<1) dh=1; } // Для очень мелкой отрисовки

	int dx=0, dy=0; // Смещение  в картинке чтобы отрезать 
	
	if(0==iSignsRotation) dy=dh;
	else if(270==iSignsRotation) dx=dh;

	if((rth-dh)>h) // Копируем часть картинки ( rect меньше высоты текста )
		dc.BitBlt(rect.left,rect.top,w_rect,h_rect,&comp_dc,dx,dy,SRCCOPY); //только видимое
	else 
	{
		// Надо растянуть по вертикали ( rect больше высоты текста )
		dc.SetStretchBltMode(BLACKONWHITE);
		int scx = w_rect, scy=3;
		if(90==iSignsRotation || 270==iSignsRotation) {scx = 3, scy=h_rect;}
		dc.StretchBlt(rect.left,rect.top,w_rect,h_rect,&comp_dc,dx,dy,scx,scy,SRCCOPY);
	}
	return true;
}
