// Ocr.NET, Copyright (c) 2013-2019 WINSOFT

// use TRIAL directive to specify trial version
// use DESIGN directive to specify design-time version

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
#if DESIGN
using System.Windows.Forms.Design;
using System.Drawing.Design;
#endif // DESIGN

namespace Winsoft.Ocr
{
	/// <summary>
	/// Tesseract OCR API interface
	/// </summary>
	internal sealed class OcrAPI
	{
		[StructLayout(LayoutKind.Sequential)]
		internal struct Box
		{
			public int Left, Top, Width, Height;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct WordItem
		{
			public int Left, Top, Right, Bottom, PointSize;
			public float Confidence;
			public IntPtr Text, FontName;
			public byte IsBold, IsItalic, IsUnderlined, IsMonospace, IsSerif, IsSmallCaps, IsDropCap, IsSubscript, IsSuperscript, IsNumeric, IsFromDictionary, Padding1, Padding2, Padding3, Padding4, Padding5;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct ParagraphItem
		{
			public int Left, Top, Right, Bottom;
			public IntPtr Text;
			public float Confidence;
			public int FirstLineIndent;
			public float DeskewAngle;
			public int Justification;
			public int Orientation;
			public int WritingDirection;
			public int TextLineOrder;
			public byte IsListItem;
			public byte IsCrown;
			public byte IsLeftToRight;
			public byte Padding1;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct TextLineItem
		{
			public int Left, Top, Right, Bottom;
			public IntPtr Text;
			public float Confidence;
			public float DeskewAngle;
			public int Orientation;
			public int WritingDirection;
			public int TextLineOrder;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct SymbolItem
		{
			public int Left, Top, Right, Bottom;
			public IntPtr Text;
			public float Confidence;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct BlockItem
		{
			public int Left, Top, Right, Bottom;
			public IntPtr Text;
			public float Confidence;
			public float DeskewAngle;
			public int Orientation;
			public int WritingDirection;
			public int TextLineOrder;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct ChoiceItem
		{
			public IntPtr Text;
			public float Confidence;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct SymbolChoiceItem
		{
			public int Left, Top, Right, Bottom;
			public IntPtr Text;
			public float Confidence;
			public int ChoiceCount;
			public IntPtr Choices;
		}

		internal const string Ocr = "ocr.dll";

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr New(out int version);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void Delete(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetTesseractVersion();

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetLeptonicaVersion();

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetImagelibVersions();

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool Recognize(IntPtr api, Progress progress, Ocr ocr);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetInputName(IntPtr api, byte[] name);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetOutputName(IntPtr api, byte[] name);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool SetVariable(IntPtr api, byte[] name, byte[] value);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool GetIntVariable(IntPtr api, byte[] name, out int value);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool GetBoolVariable(IntPtr api, byte[] name, out bool value);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool GetDoubleVariable(IntPtr api, byte[] name, out double value);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetStringVariable(IntPtr api, byte[] name);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool PrintVariables(IntPtr api, byte[] fileName);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int Init(IntPtr api, byte[] datapath, byte[] language, int engineMode, int variableCount, IntPtr variableNames, IntPtr variableValues);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void ReadConfigFile(IntPtr api, byte[] filename);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetPageSegMode(IntPtr api, int mode);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetPageSegMode(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr Rect(IntPtr api, IntPtr imageData, int bytesPerPixel, int bytesPerLine, int left, int top, int width, int height);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteString(IntPtr str);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void ClearAdaptiveClassifier(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetImage(IntPtr api, IntPtr imageData, int width, int height, int bytesPerPixel, int bytesPerLine);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool SetImageFile(IntPtr api, byte[] fileName, int page);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetRectangle(IntPtr api, int left, int top, int width, int height);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetUTF8Text(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetHOCRText(IntPtr api, int pageNumber);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetBoxText(IntPtr api, int pageNumber);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetTSVText(IntPtr api, int pageNumber);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetUNLVText(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetAltoText(IntPtr api, int pageNumber);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int MeanTextConf(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool AdaptToWordStr(IntPtr api, int mode, byte[] word);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void Clear(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void End(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int IsValidWord(IntPtr api, byte[] word);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool GetTextDirection(IntPtr api, out int offset, out float slope);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetUnichar(IntPtr api, int unicharId);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr GetInitLanguages(IntPtr api);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetMinOrientationMargin(IntPtr api, double margin);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetRegions(IntPtr api, out IntPtr regions);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetWords(IntPtr api, out IntPtr words);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetTextLines(IntPtr api, out IntPtr textLines);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetStrips(IntPtr api, out IntPtr strips);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetConnectedComponents(IntPtr api, out IntPtr connectedComponents);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteBoxes(IntPtr boxes);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetLoadedLanguages(IntPtr api, out IntPtr languages);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetAvailableLanguages(IntPtr api, out IntPtr languages);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteStringArray(IntPtr stringArray, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetWordItems(IntPtr api, out IntPtr items);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteWordItems(IntPtr items, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool AnalyseLayout(IntPtr api, out int orientation, out int direction, out int order, out float deskewAngle);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int PageCount(byte[] fileName);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void ClearPersistentCache();

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void SetSourceResolution(IntPtr api, int ppi);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool IsValidCharacter(IntPtr api, byte[] utf8Character);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetParagraphItems(IntPtr ap, out IntPtr items);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteParagraphItems(IntPtr items, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetTextLineItems(IntPtr api, out IntPtr items);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteTextLineItems(IntPtr items, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetSymbolItems(IntPtr api, out IntPtr items);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteSymbolItems(IntPtr items, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetBlockItems(IntPtr api, out IntPtr items);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteBlockItems(IntPtr items, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern int GetSymbolChoiceItems(IntPtr api, out IntPtr items);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeleteSymbolChoiceItems(IntPtr items, int size);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern IntPtr CreatePdfRenderer(IntPtr api, byte[] outputBase, bool textOnly);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern void DeletePdfRenderer(IntPtr pdfRenderer);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool BeginDocument(IntPtr pdfRenderer, byte[] title);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool EndDocument(IntPtr pdfRenderer);

		[DllImport(Ocr, CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool AddImage(IntPtr pdfRenderer, IntPtr api);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate bool Progress(Ocr ocr, int progress, int wordCount);
	}

	public enum Language
	{
		Default, Custom,
		Afrikaans, Amharic, Arabic, Assamese, Azerbaijani, AzerbaijaniCyrilic,
		Belarusian, Bengali, Tibetan, Bosnian, Breton, Bulgarian,
		CatalanValencian, Cebuano, Czech, ChineseSimplified, ChineseTraditional,
		Cherokee, Welsh, Danish, German, Dzongkha, GreekModern,
		English, EnglishMiddle, Esperanto, Math, Estonian, Basque,
		Persian, Finnish, French, Frankish, FrenchMiddle, Irish,
		Galician, GreekAncient, Gujarati, HaitianCreole, Hebrew, Hindi,
		Croatian, Hungarian, Inuktitut, Indonesian, Icelandic, Italian,
		ItalianOld, Javanese, Japanese, Kannada, Georgian, GeorgianOld,
		Kazakh, CentralKhmer, Kirghiz, Korean, KoreanVertical, Kurdish,
		KurdishArabic, Lao, Latin, Latvian, Lithuanian, Luxembourgish,
		Malayalam, Marathi, Macedonian, Maltese, Mongolian, Maori,
		Malay, Burmese, Nepali, DutchFlemish, Norwegian, Occitan,
		Oriya, OSD, Panjabi, Polish, Portuguese, Pushto,
		Quechua, Romanian, Russian, Sanskrit, Sinhala, Slovak,
		Slovenian, Sindhi, Spanish, SpanishOld, Albanian, Serbian,
		SerbianLatin, Sundanese, Swahili, Swedish, Syriac, Tamil,
		Tatar, Telugu, Tajik, Tagalog, Thai, Tigrinya, Tonga,
		Turkish, Uighur, Ukrainian, Urdu, Uzbek, UzbekCyrilic,
		Vietnamese, Yiddish, Yoruba
	}

	public enum EngineMode
	{
		TesseractOnly = 0, LSTMOnly = 1, Combined = 2, Default = 3
	}

	public enum PageSegmentation
	{
		OSDOnly = 0, AutoOSD = 1, AutoOnly = 2, Auto = 3,
		SingleColumn = 4, SingleVerticalBlock = 5, SingleBlock = 6, SingleLine = 7,
		SingleWord = 8, CircleWord = 9, SingleChar = 10, SparseText = 11, SparseTextOSD = 12,
		RawLine = 13
	}

	public enum PixelFormat
	{
		Auto, Pixel8bit, /*Pixel16bit,*/ Pixel24bit, Pixel32bit
	}

	public enum PageOrientation
	{
		Up = 0, Right = 1, Down = 2, Left = 3
	}

	public enum WritingDirection
	{
		LeftToRight = 0, RightToLeft = 1, TopToBottom = 2
	}

	public enum TextLineOrder
	{
		LeftToRight = 0, RightToLeft = 1, TopToBottom = 2
	}

	[Flags]
	public enum WordFlag
	{
		Bold = 1, Italic = 2, Underlined = 4, Monospace = 8, Serif = 16, SmallCaps = 32, DropCap = 64, Subscript = 128, Superscript = 256, Numeric = 512, FromDictionary = 1024
	}

	public enum ParagraphJustification
	{
		Unknown = 0, Left = 1, Center = 2, Right = 3
	}

	public class WordDetail
	{
		public Rectangle Location { get; private set; }
		public string Text { get; private set; }
		public float Confidence { get; private set; }
		public WordFlag Flags { get; private set; }
		public string FontName { get; private set; }
		public int PointSize { get; private set; }

		public WordDetail(Rectangle location, string text, float confidence, WordFlag flags, string fontName, int pointSize)
		{
			Location = location;
			Text = text;
			Confidence = confidence;
			Flags = flags;
			FontName = fontName;
			PointSize = pointSize;
		}
	}

	public class TextLine
	{
		public Rectangle Location { get; private set; }
		public string Text { get; private set; }
		public float Confidence { get; private set; }
		public float DeskewAngle { get; private set; }
		public PageOrientation Orientation { get; private set; }
		public WritingDirection WritingDirection { get; private set; }
		public TextLineOrder TextLineOrder { get; private set; }

		public TextLine(Rectangle location, string text, float confidence, float deskewAngle, PageOrientation orientation, WritingDirection writingDirection, TextLineOrder textLineOrder)
		{
			Location = location;
			Text = text;
			Confidence = confidence;
			DeskewAngle = deskewAngle;
			Orientation = orientation;
			WritingDirection = writingDirection;
			TextLineOrder = textLineOrder;
		}
	}

	public class Paragraph
	{
		public Rectangle Location { get; private set; }
		public string Text { get; private set; }
		public float Confidence { get; private set; }
		public int FirstLineIndent { get; private set; }
		public float DeskewAngle { get; private set; }
		public ParagraphJustification Justification { get; private set; }
		public PageOrientation Orientation { get; private set; }
		public WritingDirection WritingDirection { get; private set; }
		public TextLineOrder TextLineOrder { get; private set; }
		public bool IsListItem { get; private set; }
		public bool IsCrown { get; private set; }
		public bool IsLeftToRight { get; private set; }

		public Paragraph(Rectangle location, string text, float confidence, int firstLineIndent, float deskewAngle, ParagraphJustification justification, PageOrientation orientation, WritingDirection writingDirection, TextLineOrder textLineOrder, bool isListItem, bool isCrown, bool isLeftToRight)
		{
			Location = location;
			Text = text;
			Confidence = confidence;
			FirstLineIndent = firstLineIndent;
			DeskewAngle = deskewAngle;
			Justification = justification;
			Orientation = orientation;
			WritingDirection = writingDirection;
			TextLineOrder = textLineOrder;
			IsListItem = isListItem;
			IsCrown = isCrown;
			IsLeftToRight = isLeftToRight;
		}
	}

	public class Region
	{
		public Rectangle Location { get; private set; }
		public string Text { get; private set; }
		public float Confidence { get; private set; }
		public float DeskewAngle { get; private set; }
		public PageOrientation Orientation { get; private set; }
		public WritingDirection WritingDirection { get; private set; }
		public TextLineOrder TextLineOrder { get; private set; }

		public Region(Rectangle location, string text, float confidence, float deskewAngle, PageOrientation orientation, WritingDirection writingDirection, TextLineOrder textLineOrder)
		{
			Location = location;
			Text = text;
			Confidence = confidence;
			DeskewAngle = deskewAngle;
			Orientation = orientation;
			WritingDirection = writingDirection;
			TextLineOrder = textLineOrder;
		}
	}

	public class Symbol
	{
		public Rectangle Location { get; private set; }
		public string Text { get; private set; }
		public float Confidence { get; private set; }

		public Symbol(Rectangle location, string text, float confidence)
		{
			Location = location;
			Text = text;
			Confidence = confidence;
		}
	}

	public class Choice
	{
		public string Text { get; private set; }
		public float Confidence { get; private set; }

		public Choice(string text, float confidence)
		{
			Text = text;
			Confidence = confidence;
		}
	}

	public class SymbolChoice
	{
		public Rectangle Location { get; private set; }
		public string Text { get; private set; }
		public float Confidence { get; private set; }
		public Choice[] Choices { get; private set; }

		public SymbolChoice(Rectangle location, string text, float confidence, Choice[] choices)
		{
			Location = location;
			Text = text;
			Confidence = confidence;
			Choices = choices;
		}
	}

	[Serializable]
	public class OcrException : Exception
	{
		public OcrException(string message)
			: base(message)
		{
		}
	}

	[Serializable]
	public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);

	public class ProgressEventArgs : EventArgs
	{
		public ProgressEventArgs(int progress, int wordCount)
		{
			Cancel = false;
			Progress = progress;
			WordCount = wordCount;
		}

		public bool Cancel { get; set; }

		public int Progress { get; private set; }

		public int WordCount { get; private set; }
	}

	/// <summary>
	/// Ocr component
	/// </summary>
#if DESIGN
	[ToolboxBitmap(typeof(Ocr), "Ocr.png")]
#endif // DESIGN
	public class Ocr : Component, ISupportInitialize
	{
		public Ocr()
		{
			dataPath = "";
			PdfFileName = "";
			PdfTitle = "";
			engineMode = EngineMode.Default;
			language = Language.Default;
			PageSegmentation = PageSegmentation.Auto;
			pageNumber = 1;
		}

		public Ocr(IContainer container)
			: this()
		{
			container.Add(this);
		}

		protected override void Dispose(bool disposing)
		{
			Active = false;

			if (disposing)
			{
				// Release managed resources
			}

			// Release unmanaged resources

			// Set large fields to null

			// Call Dispose on your base class
			base.Dispose(disposing);
		}

		#region ISupportInitialize

		private bool initialize;

		public void BeginInit()
		{
			initialize = true;
		}

		public void EndInit()
		{
			initialize = false;
			Active = active;
		}

		#endregion ISupportInitialize

		private IntPtr handle;

		[Browsable(false)]
		public IntPtr Handle
		{
			get { return handle; }
		}

		private void Check(bool condition, string message)
		{
			if (!condition)
				throw new OcrException(message);
		}

		private void CheckActive()
		{
			Check(Active, "Cannot perform this operation on an inactive component");
		}

		private void CheckInactive()
		{
			if (!DesignMode)
				Check(!Active, "Cannot perform this operation on an active component");
		}

		private void CheckPageNumber()
		{
			int maxPageNumber = PageCount;
			if (maxPageNumber == 0)
				maxPageNumber = 1;
			Check((PageNumber >= 1) && (PageNumber <= maxPageNumber), "PageNumber has to be 1 to " + maxPageNumber);
		}

#if TRIAL
		private bool wasTrial;
#endif // TRIAL

		private bool active;

		[Browsable(true), Category("Action"), DefaultValue(false), Description("Set Active property to true to initialize Tesseract ocr.dll library.")]
		public bool Active
		{
			get
			{
				return DesignMode ? active : Handle != IntPtr.Zero;
			}

			set
			{
				if (Active != value)
					if (!DesignMode)
						if (!initialize)
							if (value)
							{
#if TRIAL
								if (!wasTrial)
								{
									wasTrial = true;
									MessageBox.Show(
										"A trial version of OCR.NET component started.\n\n" +
										"Please note that trial version is supposed to be used for evaluation only. " +
										"If you wish to distribute OCR.NET component as part of your " +
										"application, you must register from website at https://www.winsoft.sk.\n\n" +
										"Thank you for trialing OCR.NET component.",
										"OCR.NET, Copyright © 2013-2019 WINSOFT",
										MessageBoxButtons.OK, MessageBoxIcon.Information);
								}
#endif // TRIAL

								int version;
								handle = OcrAPI.New(out version);
								Check(Handle != IntPtr.Zero, "Cannot initialize Tesseract library");
								Check(version == 0x79, "Incorrect version of ocr.dll library");

								string tesseractDataPath = string.IsNullOrEmpty(DataPath) ? null : IncludeTrailingPathDelimiter(DataPath);
								string tesseractLanguageCode = string.IsNullOrEmpty(LanguageCode) ? null : LanguageCode;

								IntPtr[] names = null;
								IntPtr[] values = null;

								GCHandle namesHandle = new GCHandle();
								GCHandle valuesHandle = new GCHandle();

								IntPtr namesPtr = IntPtr.Zero;
								IntPtr valuesPtr = IntPtr.Zero;

								try
								{
									if (parameterNames.Count > 0)
									{
										names = new IntPtr[parameterNames.Count];
										for (int i = 0; i < names.Length; ++i)
											names[i] = parameterNames[i] == null ? IntPtr.Zero : AllocGlobal(StringToUtf8(parameterNames[i]));

										namesHandle = GCHandle.Alloc(names, GCHandleType.Pinned);
										namesPtr = namesHandle.AddrOfPinnedObject();
									}

									if (parameterValues.Count > 0)
									{
										values = new IntPtr[parameterValues.Count];
										for (int i = 0; i < values.Length; ++i)
											values[i] = parameterValues[i] == null ? IntPtr.Zero : AllocGlobal(StringToUtf8(parameterValues[i]));

										valuesHandle = GCHandle.Alloc(values, GCHandleType.Pinned);
										valuesPtr = valuesHandle.AddrOfPinnedObject();
									}

									if (OcrAPI.Init(Handle, StringToUtf8(tesseractDataPath), tesseractLanguageCode == null ? null : StringToUtf8(tesseractLanguageCode), (int)EngineMode, parameterNames.Count, namesPtr, valuesPtr) != 0)
									{
										Active = false;
										Check(false, "Cannot initialize Tesseract library");
									}
								}
								finally
								{
									if (namesHandle.IsAllocated)
										namesHandle.Free();

									if (names != null)
										for (int i = 0; i < names.Length; ++i)
											FreeGlobal(ref names[i]);

									if (valuesHandle.IsAllocated)
										valuesHandle.Free();

									if (values != null)
										for (int i = 0; i < values.Length; ++i)
											FreeGlobal(ref values[i]);
								}
							}
							else
							{
								Recognized = false;
								OcrAPI.End(Handle);
								OcrAPI.Delete(Handle);
								handle = IntPtr.Zero;
								FreeGlobal(ref pictureData);
							}

				active = value;
			}
		}

		private string IncludeTrailingPathDelimiter(string s)
		{
			if (string.IsNullOrEmpty(s))
				return s;
			return s[s.Length - 1] == Path.DirectorySeparatorChar ? s : s + Path.DirectorySeparatorChar;
		}

		private string[] languageCodes = {
			"", "",
			"afr", "amh", "ara", "asm", "aze", "aze_cyrl",
			"bel", "ben", "bod", "bos", "bre", "bul",
			"cat", "ceb", "ces", "chi_sim", "chi_tra",
			"chr", "cym", "dan", "deu", "dzo", "ell",
			"eng", "enm", "epo", "equ", "est", "eus",
			"fas", "fin", "fra", "frk", "frm", "gle",
			"glg", "grc", "guj", "hat", "heb", "hin",
			"hrv", "hun", "iku", "ind", "isl", "ita",
			"ita_old", "jav", "jpn", "kan", "kat", "kat_old",
			"kaz", "khm", "kir", "kor", "kor_vert", "kur",
			"kur_ara", "lao", "lat", "lav", "lit", "ltz",
			"mal", "mar", "mkd", "mlt", "mon", "mri",
			"msa", "mya", "nep", "nld", "nor", "oci",
			"ori", "osd", "pan", "pol", "por", "pus",
			"que", "ron", "rus", "san", "sin", "slk",
			"slv", "snd", "spa", "spa_old", "sqi", "srp",
			"srp_latn", "sun", "swa", "swe", "syr", "tam",
			"tat", "tel", "tgk", "tgl", "tha", "tir", "ton",
			"tur", "uig", "ukr", "urd", "uzb", "uzb_cyrl",
			"vie", "yid", "yor"
		};

		private string GetLanguageCode()
		{
			return language == Language.Custom ? languageCode : languageCodes[(int)language];
		}

		private void SetLanguageCode(string value)
		{
			if (value != languageCode)
			{
				CheckInactive();
				language = Language.Custom;
				languageCode = value;

				for (int i = 0; i < languageCodes.Length; ++i)
					if (value == languageCodes[i])
					{
						language = (Language)i;
						break;
					}
			}
		}

		private bool recognized;

		protected bool Recognized
		{
			get { return recognized; }

			set
			{
				recognized = value;
				if (!recognized)
					if (Active)
						if (!DesignMode)
							OcrAPI.Clear(Handle);
			}
		}

		public void Recognize()
		{
			DoRecognize(false);
		}

		private void DoRecognize(bool prepareOnly)
		{
			CheckActive();
			Recognized = false;
			layoutAnalysed = false;
			connectedComponents = null;
			regions = null;
			regionDetails = null;
			strips = null;
			textLines = null;
			words = null;
			wordDetails = null;
			textLineDetails = null;
			paragraphs = null;
			symbols = null;
			symbolChoices = null;

			const string CannotCreatePdfRenderer = "Cannot create PDF renderer";
			const string PdfRenderingError = "Cannot render to PDF file";
			const string UnsupportedOSD = "OSD page segmentation modes are not supported on LSTM engine";

			if (EngineMode != EngineMode.TesseractOnly)
				Check((PageSegmentation != PageSegmentation.OSDOnly) && (PageSegmentation != PageSegmentation.AutoOSD) && (PageSegmentation != PageSegmentation.SparseTextOSD), UnsupportedOSD);

			if (!string.IsNullOrEmpty(PictureFileName))
			{
				OcrAPI.SetPageSegMode(Handle, (int)PageSegmentation);
				CheckPageNumber();
				Check(OcrAPI.SetImageFile(Handle, StringToUtf8(PictureFileName), PageNumber - 1), "Image file cannot be used: " + PictureFileName);
				if ((PictureWidth != 0) && (PictureHeight != 0))
					OcrAPI.SetRectangle(Handle, PictureLeft, PictureTop, PictureWidth, PictureHeight);

				if (resolution != 0)
					OcrAPI.SetSourceResolution(Handle, Resolution);

				if (!prepareOnly)
				{
					IntPtr pdfRenderer = IntPtr.Zero;
					string pdfFile = Path.ChangeExtension(PdfFileName, null);
					if (!string.IsNullOrEmpty(pdfFile))
					{
						pdfRenderer = OcrAPI.CreatePdfRenderer(Handle, StringToUtf8(pdfFile), false);
						Check(pdfRenderer != IntPtr.Zero, CannotCreatePdfRenderer);
					}

					try
					{
						if (pdfRenderer != IntPtr.Zero)
							Check(OcrAPI.BeginDocument(pdfRenderer, StringToUtf8(PdfTitle)), PdfRenderingError);

						if (Progress != null)
							recognized = OcrAPI.Recognize(Handle, ProgressCallback, this);
						else
							recognized = OcrAPI.Recognize(Handle, null, this);

						if (pdfRenderer != IntPtr.Zero)
						{
							Check(OcrAPI.AddImage(pdfRenderer, Handle), PdfRenderingError);
							Check(OcrAPI.EndDocument(pdfRenderer), PdfRenderingError);
						}
					}
					finally
					{
						if (pdfRenderer != IntPtr.Zero)
							OcrAPI.DeletePdfRenderer(pdfRenderer);
					}
				}
			}
			else
			{
				FreeGlobal(ref pictureData);
				pictureData = AllocGlobal(PictureData(Picture));
				if (pictureData != IntPtr.Zero)
				{
					OcrAPI.SetPageSegMode(Handle, (int)PageSegmentation);
					OcrAPI.SetImage(Handle, pictureData, Picture.Width, Picture.Height, 4 /* bytes per pixel */, 4 * Picture.Width);
					if ((PictureWidth != 0) && (PictureHeight != 0))
						OcrAPI.SetRectangle(Handle, PictureLeft, PictureTop, PictureWidth, PictureHeight);

					if (resolution != 0)
						OcrAPI.SetSourceResolution(Handle, Resolution);

					if (!prepareOnly)
					{
						IntPtr pdfRenderer = IntPtr.Zero;
						string pdfFile = Path.ChangeExtension(PdfFileName, null);
						if (!string.IsNullOrEmpty(pdfFile))
						{
							pdfRenderer = OcrAPI.CreatePdfRenderer(Handle, StringToUtf8(pdfFile), false);
							Check(pdfRenderer != IntPtr.Zero, CannotCreatePdfRenderer);
						}

						try
						{
							if (pdfRenderer != IntPtr.Zero)
								Check(OcrAPI.BeginDocument(pdfRenderer, StringToUtf8(PdfTitle)), PdfRenderingError);

							if (Progress != null)
								recognized = OcrAPI.Recognize(Handle, ProgressCallback, this);
							else
								recognized = OcrAPI.Recognize(Handle, null, this);

							if (pdfRenderer != IntPtr.Zero)
							{
								Check(OcrAPI.AddImage(pdfRenderer, Handle), PdfRenderingError);
								Check(OcrAPI.EndDocument(pdfRenderer), PdfRenderingError);
							}
						}
						finally
						{
							if (pdfRenderer != IntPtr.Zero)
								OcrAPI.DeletePdfRenderer(pdfRenderer);
						}
					}
				}
			}
		}

		private static bool ProgressCallback(Ocr ocr, int progress, int wordCount)
		{
			ProgressEventArgs args = new ProgressEventArgs(progress, wordCount);
			ocr.Progress(ocr, args);
			return args.Cancel;
		}

		public bool IsValidWord(string word)
		{
			CheckActive();
			return OcrAPI.IsValidWord(Handle, StringToUtf8(word)) != 0;
		}

		public bool IsValidCharacter(char character)
		{
			CheckActive();
			return OcrAPI.IsValidCharacter(Handle, StringToUtf8(character.ToString()));
		}

		public bool PrintVariables(string fileName)
		{
			CheckActive();
			return OcrAPI.PrintVariables(Handle, StringToUtf8(fileName));
		}

		public void ReadConfigFile(string fileName)
		{
			CheckActive();
			OcrAPI.ReadConfigFile(Handle, StringToUtf8(fileName));
		}

		[Browsable(false)]
		public string BoxText
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr data = IntPtr.Zero;
				if (Recognized)
					try
					{
						data = OcrAPI.GetBoxText(Handle, PageNumber - 1);
						return PtrToUtf8String(data);
					}
					finally
					{
						if (data != IntPtr.Zero)
							OcrAPI.DeleteString(data);
					}
				return null;
			}
		}

		[Browsable(false)]
		public string TsvText
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr data = IntPtr.Zero;
				if (Recognized)
					try
					{
						data = OcrAPI.GetTSVText(Handle, PageNumber - 1);
						return PtrToUtf8String(data);
					}
					finally
					{
						if (data != IntPtr.Zero)
							OcrAPI.DeleteString(data);
					}
				return null;
			}
		}

		[Browsable(false)]
		public string HtmlText
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr data = IntPtr.Zero;
				if (Recognized)
					try
					{
						data = OcrAPI.GetHOCRText(Handle, PageNumber - 1);
						return PtrToUtf8String(data);
					}
					finally
					{
						if (data != IntPtr.Zero)
							OcrAPI.DeleteString(data);
					}
				return null;
			}
		}

		[Browsable(false)]
		public string AltoText
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr data = IntPtr.Zero;
				if (Recognized)
					try
					{
						data = OcrAPI.GetAltoText(Handle, PageNumber - 1);
						return PtrToUtf8String(data);
					}
					finally
					{
						if (data != IntPtr.Zero)
							OcrAPI.DeleteString(data);
					}
				return null;
			}
		}

		public bool GetTextDirection(out int offset, out float slope)
		{
			CheckActive();
			if (!Recognized)
				Recognize();
			offset = 0;
			slope = 0.0f;
			return Recognized ? OcrAPI.GetTextDirection(Handle, out offset, out slope) : false;
		}

		public bool AdaptToWord(PageSegmentation pageSegmentation, string word)
		{
			CheckActive();
			return OcrAPI.AdaptToWordStr(Handle, (int)pageSegmentation, StringToUtf8(word));
		}

		public void ClearAdaptiveClassifier()
		{
			CheckActive();
			OcrAPI.ClearAdaptiveClassifier(Handle);
		}

		public void ClearPersistentCache()
		{
			OcrAPI.ClearPersistentCache();
		}

		private Rectangle[] connectedComponents;

		[Browsable(false)]
		public Rectangle[] ConnectedComponents
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (connectedComponents == null))
					try
					{
						int count = OcrAPI.GetConnectedComponents(Handle, out items);
						if (count > 0)
						{
							connectedComponents = new Rectangle[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.Box));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.Box item = (OcrAPI.Box)Marshal.PtrToStructure(ptr, typeof(OcrAPI.Box));
								connectedComponents[i] = new Rectangle(item.Left, item.Top, item.Width, item.Height);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteBoxes(items);
					}

				return connectedComponents;
			}
		}

		private Rectangle[] regions;

		[Browsable(false)]
		public Rectangle[] Regions
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (regions == null))
					try
					{
						int count = OcrAPI.GetRegions(Handle, out items);
						if (count > 0)
						{
							regions = new Rectangle[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.Box));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.Box item = (OcrAPI.Box)Marshal.PtrToStructure(ptr, typeof(OcrAPI.Box));
								regions[i] = new Rectangle(item.Left, item.Top, item.Width, item.Height);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteBoxes(items);
					}
				return regions;
			}
		}

		private Region[] regionDetails;

		[Browsable(false)]
		public Region[] RegionDetails
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (regionDetails == null))
					try
					{
						int count = OcrAPI.GetBlockItems(Handle, out items);
						if (count > 0)
						{
							regionDetails = new Region[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.BlockItem));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.BlockItem item = (OcrAPI.BlockItem)Marshal.PtrToStructure(ptr, typeof(OcrAPI.BlockItem));
								regionDetails[i] = new Region(new Rectangle(item.Left, item.Top, item.Right - item.Left, item.Bottom - item.Top),
									PtrToUtf8String(item.Text), item.Confidence, item.DeskewAngle,
									(PageOrientation)item.Orientation, (WritingDirection)item.WritingDirection, (TextLineOrder)item.TextLineOrder);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteBlockItems(items, regionDetails != null ? regionDetails.Length : 0);
					}
				return regionDetails;
			}
		}

		private Rectangle[] strips;

		[Browsable(false)]
		public Rectangle[] Strips
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (strips == null))
					try
					{
						int count = OcrAPI.GetStrips(Handle, out items);
						if (count > 0)
						{
							strips = new Rectangle[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.Box));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.Box item = (OcrAPI.Box)Marshal.PtrToStructure(ptr, typeof(OcrAPI.Box));
								strips[i] = new Rectangle(item.Left, item.Top, item.Width, item.Height);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteBoxes(items);
					}
				return strips;
			}
		}

		private Rectangle[] textLines;

		[Browsable(false)]
		public Rectangle[] TextLines
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (textLines == null))
					try
					{
						int count = OcrAPI.GetTextLines(Handle, out items);
						int itemSize = Marshal.SizeOf(typeof(OcrAPI.Box));
						if (count > 0)
						{
							textLines = new Rectangle[count];
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.Box item = (OcrAPI.Box)Marshal.PtrToStructure(ptr, typeof(OcrAPI.Box));
								textLines[i] = new Rectangle(item.Left, item.Top, item.Width, item.Height);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteBoxes(items);
					}
				return textLines;
			}
		}

		private TextLine[] textLineDetails;

		[Browsable(false)]
		public TextLine[] TextLineDetails
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (textLineDetails == null))
					try
					{
						int count = OcrAPI.GetTextLineItems(Handle, out items);
						if (count > 0)
						{
							textLineDetails = new TextLine[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.TextLineItem));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.TextLineItem item = (OcrAPI.TextLineItem)Marshal.PtrToStructure(ptr, typeof(OcrAPI.TextLineItem));
								textLineDetails[i] = new TextLine(new Rectangle(item.Left, item.Top, item.Right - item.Left, item.Bottom - item.Top),
									PtrToUtf8String(item.Text), item.Confidence, item.DeskewAngle,
									(PageOrientation)item.Orientation, (WritingDirection)item.WritingDirection, (TextLineOrder)item.TextLineOrder);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteTextLineItems(items, textLineDetails != null ? textLineDetails.Length : 0);
					}
				return textLineDetails;
			}
		}

		private Rectangle[] words;

		[Browsable(false)]
		public Rectangle[] Words
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (words == null))
					try
					{
						int count = OcrAPI.GetWords(Handle, out items);
						if (count > 0)
						{
							words = new Rectangle[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.Box));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.Box item = (OcrAPI.Box)Marshal.PtrToStructure(ptr, typeof(OcrAPI.Box));
								words[i] = new Rectangle(item.Left, item.Top, item.Width, item.Height);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteBoxes(items);
					}
				return words;
			}
		}

		private WordDetail[] wordDetails;

		[Browsable(false)]
		public WordDetail[] WordDetails
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (wordDetails == null))
				{
					int count = OcrAPI.GetWordItems(Handle, out items);
					if (count > 0)
						try
						{
							wordDetails = new WordDetail[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.WordItem));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.WordItem item = (OcrAPI.WordItem)Marshal.PtrToStructure(ptr, typeof(OcrAPI.WordItem));

								WordFlag flags = new WordFlag();
								if (item.IsBold != 0)
									flags |= WordFlag.Bold;
								if (item.IsItalic != 0)
									flags |= WordFlag.Italic;
								if (item.IsUnderlined != 0)
									flags |= WordFlag.Underlined;
								if (item.IsMonospace != 0)
									flags |= WordFlag.Monospace;
								if (item.IsSerif != 0)
									flags |= WordFlag.Serif;
								if (item.IsSmallCaps != 0)
									flags |= WordFlag.SmallCaps;
								if (item.IsDropCap != 0)
									flags |= WordFlag.DropCap;
								if (item.IsSubscript != 0)
									flags |= WordFlag.Subscript;
								if (item.IsSuperscript != 0)
									flags |= WordFlag.Superscript;
								if (item.IsNumeric != 0)
									flags |= WordFlag.Numeric;
								if (item.IsFromDictionary != 0)
									flags |= WordFlag.FromDictionary;

								wordDetails[i] = new WordDetail(new Rectangle(item.Left, item.Top, item.Right - item.Left, item.Bottom - item.Top),
									PtrToUtf8String(item.Text), item.Confidence, flags, PtrToUtf8String(item.FontName), item.PointSize);
							}
						}
						finally
						{
							if (items != IntPtr.Zero)
								OcrAPI.DeleteWordItems(items, count);
						}
				}
				return wordDetails;
			}
		}

		private Paragraph[] paragraphs;

		[Browsable(false)]
		public Paragraph[] Paragraphs
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (paragraphs == null))
					try
					{
						int count = OcrAPI.GetParagraphItems(Handle, out items);
						if (count > 0)
						{
							paragraphs = new Paragraph[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.ParagraphItem));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.ParagraphItem item = (OcrAPI.ParagraphItem)Marshal.PtrToStructure(ptr, typeof(OcrAPI.ParagraphItem));
								paragraphs[i] = new Paragraph(new Rectangle(item.Left, item.Top, item.Right - item.Left, item.Bottom - item.Top),
									PtrToUtf8String(item.Text), item.Confidence, item.FirstLineIndent, item.DeskewAngle,
									(ParagraphJustification)item.Justification, (PageOrientation)item.Orientation, (WritingDirection)item.WritingDirection, (TextLineOrder)item.TextLineOrder,
									item.IsListItem != 0, item.IsCrown != 0, item.IsLeftToRight != 0);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteParagraphItems(items, paragraphs != null ? paragraphs.Length : 0);
					}
				return paragraphs;
			}
		}

		private Symbol[] symbols;

		[Browsable(false)]
		public Symbol[] Symbols
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (symbols == null))
					try
					{
						int count = OcrAPI.GetSymbolItems(Handle, out items);
						if (count > 0)
						{
							symbols = new Symbol[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.SymbolItem));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.SymbolItem item = (OcrAPI.SymbolItem)Marshal.PtrToStructure(ptr, typeof(OcrAPI.SymbolItem));
								symbols[i] = new Symbol(new Rectangle(item.Left, item.Top, item.Right - item.Left, item.Bottom - item.Top),
									PtrToUtf8String(item.Text), item.Confidence);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteSymbolItems(items, symbols != null ? symbols.Length : 0);
					}
				return symbols;
			}
		}

		private SymbolChoice[] symbolChoices;

		[Browsable(false)]
		public SymbolChoice[] SymbolChoices
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr items = IntPtr.Zero;

				if (Recognized && (symbolChoices == null))
					try
					{
						int count = OcrAPI.GetSymbolChoiceItems(Handle, out items);
						if (count > 0)
						{
							symbolChoices = new SymbolChoice[count];
							int itemSize = Marshal.SizeOf(typeof(OcrAPI.SymbolChoiceItem));
							for (int i = 0; i < count; ++i)
							{
								IntPtr ptr = new IntPtr(items.ToInt64() + i * itemSize);
								OcrAPI.SymbolChoiceItem item = (OcrAPI.SymbolChoiceItem)Marshal.PtrToStructure(ptr, typeof(OcrAPI.SymbolChoiceItem));
								Choice[] choices = null;
								if (item.ChoiceCount > 0)
								{
									choices = new Choice[item.ChoiceCount];
									int choiceItemSize = Marshal.SizeOf(typeof(OcrAPI.ChoiceItem));
									for (int j = 0; j < item.ChoiceCount; ++j)
									{
										IntPtr choicePtr = new IntPtr(item.Choices.ToInt64() + j * choiceItemSize);
										OcrAPI.ChoiceItem choiceItem = (OcrAPI.ChoiceItem)Marshal.PtrToStructure(choicePtr, typeof(OcrAPI.ChoiceItem));
										choices[j] = new Choice(PtrToUtf8String(choiceItem.Text), choiceItem.Confidence);
									}
								}
								symbolChoices[i] = new SymbolChoice(new Rectangle(item.Left, item.Top, item.Right - item.Left, item.Bottom - item.Top),
									PtrToUtf8String(item.Text), item.Confidence, choices);
							}
						}
					}
					finally
					{
						if (items != IntPtr.Zero)
							OcrAPI.DeleteSymbolChoiceItems(items, symbolChoices != null ? symbolChoices.Length : 0);
					}
				return symbolChoices;
			}
		}

		[Browsable(false)]
		public string InitLanguages
		{
			get
			{
				CheckActive();
				return PtrToUtf8String(OcrAPI.GetInitLanguages(Handle));
			}
		}

		[Browsable(false)]
		public string[] LoadedLanguages
		{
			get
			{
				CheckActive();
				IntPtr items;
				int count = OcrAPI.GetLoadedLanguages(Handle, out items);
				if (count > 0)
					try
					{
						string[] result = new string[count];
						for (int i = 0; i < count; ++i)
							result[i] = PtrToUtf8String(Marshal.ReadIntPtr(items, i * IntPtr.Size));
						return result;
					}
					finally
					{
						OcrAPI.DeleteStringArray(items, count);
					}
				return null;
			}
		}

		[Browsable(false)]
		public string[] AvailableLanguages
		{
			get
			{
				CheckActive();
				IntPtr items;
				int count = OcrAPI.GetAvailableLanguages(Handle, out items);
				if (count > 0)
					try
					{
						string[] result = new string[count];
						for (int i = 0; i < count; ++i)
							result[i] = PtrToUtf8String(Marshal.ReadIntPtr(items, i * IntPtr.Size));
						return result;
					}
					finally
					{
						OcrAPI.DeleteStringArray(items, count);
					}
				return null;
			}
		}

		private PageOrientation pageOrientation;

		[Browsable(false)]
		public PageOrientation PageOrientation
		{
			get
			{
				AnalyseLayout();
				return pageOrientation;
			}
		}

		private WritingDirection writingDirection;

		[Browsable(false)]
		public WritingDirection WritingDirection
		{
			get
			{
				AnalyseLayout();
				return writingDirection;
			}
		}

		private TextLineOrder textLineOrder;

		[Browsable(false)]
		public TextLineOrder TextLineOrder
		{
			get
			{
				AnalyseLayout();
				return textLineOrder;
			}
		}

		private float deskewAngle;

		[Browsable(false)]
		public float DeskewAngle
		{
			get
			{
				AnalyseLayout();
				return deskewAngle;
			}
		}

		private int resolution;

		[Browsable(true), Category("Behavior"), DefaultValue(0), Description("Resolution of the source image in pixels per inch, so font size information can be calculated in results.")]
		public int Resolution
		{
			get
			{
				return resolution;
			}

			set
			{
				if (resolution != value)
				{
					resolution = value;
					Recognized = false;
				}
			}
		}

		private bool layoutAnalysed;

		private void AnalyseLayout()
		{
			int aPageOrientation = 0, aWritingDirection = 0, aTextLineOrder = 0;

			CheckActive();
			if (!layoutAnalysed)
			{
				DoRecognize(true);
				Check(OcrAPI.AnalyseLayout(Handle, out aPageOrientation, out aWritingDirection, out aTextLineOrder, out deskewAngle), "Cannot analyse layout");
				pageOrientation = (PageOrientation)aPageOrientation;
				writingDirection = (WritingDirection)aWritingDirection;
				textLineOrder = (TextLineOrder)aTextLineOrder;
				layoutAnalysed = true;
			}
		}

		public static string ImagelibVersions
		{
			get
			{
				IntPtr data = OcrAPI.GetImagelibVersions();
				try
				{
					return PtrToUtf8String(data);
				}
				finally
				{
					if (data != IntPtr.Zero)
						OcrAPI.DeleteString(data);
				}
			}
		}

		public static string LeptonicaVersion
		{
			get
			{
				IntPtr data = OcrAPI.GetLeptonicaVersion();
				try
				{
					return PtrToUtf8String(data);
				}
				finally
				{
					if (data != IntPtr.Zero)
						OcrAPI.DeleteString(data);
				}
			}
		}

		public static string Version
		{
			get
			{
				return PtrToUtf8String(OcrAPI.GetTesseractVersion());
			}
		}

		[Browsable(false)]
		public string Text
		{
			get
			{
				return Encoding.UTF8.GetString(Utf8Text);
			}
		}

		[Browsable(false)]
		public byte[] Utf8Text
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr data = IntPtr.Zero;
				if (Recognized)
					try
					{
						data = OcrAPI.GetUTF8Text(Handle);
						return PtrToBytes(data);
					}
					finally
					{
						if (data != IntPtr.Zero)
							OcrAPI.DeleteString(data);
					}
				return null;
			}
		}

		[Browsable(false)]
		public string UnlvText
		{
			get
			{
				return Encoding.UTF8.GetString(Unlv);
			}
		}

		[Browsable(false)]
		public byte[] Unlv
		{
			get
			{
				CheckActive();
				if (!Recognized)
					Recognize();

				IntPtr data = IntPtr.Zero;
				if (Recognized)
					try
					{
						data = OcrAPI.GetUNLVText(Handle);
						return PtrToBytes(data);
					}
					finally
					{
						if (data != IntPtr.Zero)
							OcrAPI.DeleteString(data);
					}

				return null;
			}
		}

		[Browsable(false)]
		public int Confidence
		{
			get
			{
				CheckActive();
				return OcrAPI.MeanTextConf(Handle);
			}
		}

		public bool GetBoolParameter(string name)
		{
			CheckActive();
			bool result;
			Check(OcrAPI.GetBoolVariable(Handle, StringToUtf8(name), out result), "Cannot get parameter '" + name + "'");
			return result;
		}

		public void SetBoolParameter(string name, bool value)
		{
			CheckActive();
			string stringValue = value ? "T" : "F";
			Check(OcrAPI.SetVariable(Handle, StringToUtf8(name), StringToUtf8(stringValue)), "Cannot set parameter '" + name + "'");
		}

		public int GetIntParameter(string name)
		{
			CheckActive();
			int result;
			Check(OcrAPI.GetIntVariable(Handle, StringToUtf8(name), out result), "Cannot get parameter '" + name + "'");
			return result;
		}

		public void SetIntParameter(string name, int value)
		{
			CheckActive();
			Check(OcrAPI.SetVariable(Handle, StringToUtf8(name), StringToUtf8(value.ToString())), "Cannot set parameter '" + name + "'");
		}

		public double GetDoubleParameter(string name)
		{
			CheckActive();
			double result;
			Check(OcrAPI.GetDoubleVariable(Handle, StringToUtf8(name), out result), "Cannot get parameter '" + name + "'");
			return result;
		}

		public void SetDoubleParameter(string name, double value)
		{
			CheckActive();
			Check(OcrAPI.SetVariable(Handle, StringToUtf8(name), StringToUtf8(value.ToString())), "Cannot set parameter '" + name + "'");
		}

		public string GetStringParameter(string name)
		{
			CheckActive();
			return PtrToUtf8String(OcrAPI.GetStringVariable(Handle, StringToUtf8(name)));
		}

		public void SetStringParameter(string name, string value)
		{
			CheckActive();
			Check(OcrAPI.SetVariable(Handle, StringToUtf8(name), StringToUtf8(value)), "Cannot set parameter '" + name + "'");
		}

		public string GetUniChar(int id)
		{
			CheckActive();
			return PtrToUtf8String(OcrAPI.GetUnichar(Handle, id));
		}

		private int GetBytesPerPixel(PixelFormat pixelFormat)
		{
			switch (pixelFormat)
			{
				case PixelFormat.Pixel8bit: return 1;
				case PixelFormat.Pixel24bit: return 3;
				case PixelFormat.Pixel32bit: return 4;
				default: return 0;
			}
		}

		private string dataPath;

		[Browsable(true), Category("Behavior"), Description("Path to tessdata folder containing Tesseract language data.")]
		public string DataPath
		{
			get
			{
				return dataPath;
			}

			set
			{
				if (value != dataPath)
				{
					CheckInactive();
					dataPath = value;
				}
			}
		}

		[Browsable(true), Category("Behavior"), Description("Name of the PDF file that will be created by PDF renderer.")]
#if DESIGN
		[Editor(typeof(PdfFileNameEditor), typeof(UITypeEditor))]
#endif // DESIGN
		public string PdfFileName { get; set; }

		[Browsable(true), Category("Behavior"), Description("Title of the PDF file created by PDF renderer.")]
		public string PdfTitle { get; set; }

		private EngineMode engineMode;

		[Browsable(true), Category("Behavior"), DefaultValue(EngineMode.Default), Description("Chooses OCR engine.")]
		public EngineMode EngineMode
		{
			get
			{
				return engineMode;
			}

			set
			{
				if (value != engineMode)
				{
					CheckInactive();
					engineMode = value;
				}
			}
		}

		private Language language;

		[Browsable(true), Category("Behavior"), DefaultValue(Language.Default), Description("Language used by Tesseract engine. Language data are available at https://github.com/tesseract-ocr/tessdata_fast.")]
		public Language Language
		{
			get
			{
				return language;
			}

			set
			{
				if (value != language)
				{
					CheckInactive();
					language = value;
				}
			}
		}

		private string languageCode;

		[Browsable(true), Category("Behavior"), Description("Custom language code or multiple language codes use by Tesseract engine. To specify multiple languages, separate language codes by '+' character: eng+slk+rus.")]
		public string LanguageCode
		{
			get
			{
				return GetLanguageCode();
			}

			set
			{
				SetLanguageCode(value);
			}
		}

		[Browsable(true), Category("Behavior"), DefaultValue(Winsoft.Ocr.PageSegmentation.Auto), Description("Selects page layout analysis mode.")]
		public PageSegmentation PageSegmentation { get; set; }

		private string pictureFileName;

		[Browsable(true), Category("Data"), Description("Picture file used for OCR. GIF, JPEG, PNG and TIFF image formats are supported.")]
#if DESIGN
		[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
#endif // DESIGN
		public string PictureFileName
		{
			get
			{
				return pictureFileName;
			}

			set
			{
				if (value != pictureFileName)
				{
					if (!String.IsNullOrEmpty(value))
						if (!initialize)
						{
							Picture = null;
							FreeGlobal(ref pictureData);
						}
					pictureFileName = value;
					Recognized = false;
				}
			}
		}

		private int pictureHeight;

		[Browsable(true), Category("Data"), DefaultValue(0), Description("Height of the part of picture used for OCR.")]
		public int PictureHeight
		{
			get
			{
				return pictureHeight;
			}

			set
			{
				if (value != pictureHeight)
				{
					if (value < 0)
						pictureHeight = 0;
					else
						pictureHeight = value;
					Recognized = false;
				}
			}
		}

		private int pictureLeft;

		[Browsable(true), Category("Data"), DefaultValue(0), Description("Left origin of the part of picture used for OCR.")]
		public int PictureLeft
		{
			get
			{
				return pictureLeft;
			}

			set
			{
				if (value != pictureLeft)
				{
					if (value < 0)
						pictureLeft = 0;
					else
						pictureLeft = value;
					Recognized = false;
				}
			}
		}

		private int pictureTop;

		[Browsable(true), Category("Data"), DefaultValue(0), Description("Top origin of the part of picture used for OCR.")]
		public int PictureTop
		{
			get
			{
				return pictureTop;
			}

			set
			{
				if (value != pictureTop)
				{
					if (value < 0)
						pictureTop = 0;
					else
						pictureTop = value;
					Recognized = false;
				}
			}
		}

		private int pictureWidth;

		[Browsable(true), Category("Data"), DefaultValue(0), Description("Width of the part of picture used for OCR.")]
		public int PictureWidth
		{
			get
			{
				return pictureWidth;
			}

			set
			{
				if (value != pictureWidth)
				{
					if (value < 0)
						pictureWidth = 0;
					else
						pictureWidth = value;
					Recognized = false;
				}
			}
		}

		[Browsable(false)]
		public int PageCount
		{
			get
			{
				CheckActive();
				return OcrAPI.PageCount(StringToUtf8(PictureFileName));
			}
		}

		private int pageNumber;

		[Browsable(true), Category("Behavior"), DefaultValue(1), Description("Page number in multipage TIFF image, that will be used for OCR.")]
		public int PageNumber
		{
			get
			{
				return pageNumber;
			}

			set
			{
				if (pageNumber != value)
				{
					pageNumber = value;
					Recognized = false;
				}
			}
		}

		private Image picture;

		[Browsable(true), Category("Data"), DefaultValue(null), Bindable(true), Description("The picture used for OCR.")]
		public Image Picture
		{
			get
			{
				return picture;
			}

			set
			{
				picture = value;
				if (value != null)
					if (!initialize)
					{
						FreeGlobal(ref pictureData);
						PictureFileName = string.Empty;
						PictureLeft = 0;
						PictureTop = 0;
						PictureWidth = picture.Width;
						PictureHeight = picture.Height;
					}
				Recognized = false;
			}
		}

		private IntPtr pictureData;

		private byte[] PictureData(Image image)
		{
			byte[] result = new byte[4 * image.Width * image.Height];

			using (Bitmap bitmap = new Bitmap(image))
			{
				int index = 0;
				for (int y = 0; y < image.Height; ++y)
					for (int x = 0; x < image.Width; ++x)
					{
						Color color = bitmap.GetPixel(x, y);
						result[index++] = color.R;
						result[index++] = color.G;
						result[index++] = color.B;
						result[index++] = color.A;
					}
			}

			return result;
		}

		private static IntPtr AllocGlobal(byte[] array)
		{
			IntPtr result = Marshal.AllocHGlobal(array.Length);
			if (result != IntPtr.Zero)
				Marshal.Copy(array, 0, result, array.Length);
			return result;
		}

		private static void FreeGlobal(ref IntPtr pointer)
		{
			if (pointer != IntPtr.Zero)
			{
				Marshal.FreeHGlobal(pointer);
				pointer = IntPtr.Zero;
			}
		}

		private static byte[] PtrToBytes(IntPtr ptr)
		{
			int length = 0;
			if (ptr != IntPtr.Zero)
				while (Marshal.ReadByte(ptr, length) != 0)
					++length;

			byte[] bytes = new byte[length];
			for (int i = 0; i < length; ++i)
				bytes[i] = Marshal.ReadByte(ptr, i);
			return bytes;
		}

		private static string PtrToUtf8String(IntPtr ptr)
		{
			return Encoding.UTF8.GetString(PtrToBytes(ptr));
		}

		private static byte[] StringToUtf8(string text)
		{
			return (text == null) ? null : Encoding.UTF8.GetBytes(text + '\0');
		}

		[Category("Action"), Description("Progress notification.")]
		public event ProgressEventHandler Progress;

		private List<string> parameterNames = new List<string>();

		private List<string> parameterValues = new List<string>();

		public void ClearInitParameters()
		{
			parameterNames.Clear();
			parameterValues.Clear();
		}

		public void AddInitParameter(string name, string value)
		{
			parameterNames.Add(name);
			parameterValues.Add(value);
		}
	}

#if DESIGN
	class PdfFileNameEditor : FileNameEditor
	{
		protected override void InitializeDialog(OpenFileDialog openFileDialog)
		{
			base.InitializeDialog(openFileDialog);
			openFileDialog.Filter = "PDF files (*.pdf)|*.pdf|All files (*.*)|*.*";
			openFileDialog.DefaultExt = "pdf";
		}
	}
#endif // DESIGN
}