using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms;
using System.ComponentModel;
using System.IO;
using System.Windows.Xps.Packaging;
using System.Printing;
using System.Windows.Markup;
using System.Runtime.InteropServices;
using Microsoft.Win32; /* For registry */
using System.Reflection;
using System.Diagnostics;


public enum AA_t
{
	HIGH = 8,
	MEDHIGH = 6,
	MED = 4,
	LOW = 2,
	NONE = 0
}

enum PDFType_t
{
	PDFX,
	PDFA
}

enum AppBar_t
{
	TEXT_SEARCH,
	STANDARD
}

enum NotifyType_t
{
	MESS_STATUS,
	MESS_ERROR
};

enum RenderingStatus_t
{
	REN_AVAILABLE,
	REN_THUMBS,
	REN_UPDATE_THUMB_CANVAS,
	REN_PAGE			/* Used to ignore value when source based setting */
};

public enum status_t
{
	S_ISOK,
	E_FAILURE,
	E_OUTOFMEM,
	E_NEEDPASSWORD
};

public enum textout_t
{
	HTML = 0,
	XML,
	TEXT
}

enum zoom_t
{
	NO_ZOOM,
	ZOOM_IN,
	ZOOM_OUT
}

enum view_t
{
	VIEW_WEB,
	VIEW_CONTENT,
	VIEW_PAGE,
	VIEW_PASSWORD,
	VIEW_TEXTSEARCH
};

public enum Page_Content_t
{
	FULL_RESOLUTION = 0,
	THUMBNAIL,
	OLD_RESOLUTION,
	NOTSET
};

/* Put all the PDF types first to make the switch statment shorter 
   Save_Type_t.PDF is the test */
public enum Save_Type_t
{
	PDF13,
	LINEAR_PDF,
	PDFA1_RGB,
	PDFA1_CMYK,
	PDFA2_RGB,
	PDFA2_CMYK,
	PDFX3_GRAY,
	PDFX3_CMYK,
	PDF,
	PCLXL,
	XPS,
	SVG,
	TEXT,
	HTML,
	XML
}

public enum Extract_Type_t
{
	PDF,
	EPS,
	PS,
	SVG
}

/* C# has no defines.... */
static class Constants
{
	public const int SCROLL_STEPSIZE = 48;
	public const int INIT_LOOK_AHEAD = 2;  /* A + count on the pages to pre-render */
	public const int THUMB_PREADD = 10;
	public const double MIN_SCALE = 0.5;
	public const double SCALE_THUMB = 0.05;
	public const int BLANK_WIDTH = 17;
	public const int BLANK_HEIGHT = 22;
	public const double ZOOM_STEP = 0.25;
	public const int ZOOM_MAX = 4;
	public const double ZOOM_MIN = 0.25;
	public const int KEY_PLUS = 0xbb;
	public const int KEY_MINUS = 0xbd;
	public const int ZOOM_IN = 0;
	public const int ZOOM_OUT = 1;
	public const double SCREEN_SCALE = 1;
	public const int HEADER_SIZE = 54;
	public const int SEARCH_FORWARD = 1;
	public const int SEARCH_BACKWARD = -1;
	public const int TEXT_NOT_FOUND = -1;
	public const int DEFAULT_GS_RES = 300;
	public const int DISPATCH_TIME = 50;
	public const int SCROLL_STEP = 10;
	public const int SCROLL_EDGE_BUFFER = 90;
	public const int VERT_SCROLL_STEP = 48;
	public const int PAGE_MARGIN = 1;
	public const int MAX_PRINT_PREVIEW_LENGTH = 250;
}

public static class DocumentTypes
{
	public const string PDF = "Portable Document Format";
	public const string PS = "PostScript";
	public const string XPS = "XPS";
	public const string EPS = "Encapsulated PostScript";
	public const string CBZ = "Comic Book Archive";
	public const string PNG = "Portable Network Graphics Image";
	public const string JPG = "Joint Photographic Experts Group Image";
	public const string UNKNOWN = "Unknown";
}

namespace gsview
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	/// 

	public struct pageprogress_t
	{
		public Byte[] bitmap;
		public BlocksText charlist;
		public int pagenum;
		public Point size;
		public Annotate_t annot;
	}

	public struct ContextMenu_t
	{
		public int page_num;
		public Point mouse_position;
	}

	public struct thumb_t
	{
		public int page_num;
		public Byte[] bitmap;
		public Point size;
	}

	public struct searchResults_t
	{
		public String needle;
		public bool done;
		public int page_found;
		public List<Rect> rectangles;
		public int num_rects;
	}

	public struct printPreviewPage_t
	{
		public Byte[] bitmap;
		public int width;
		public int height;
		public double width_inches;
		public double height_inches;
	}

	public struct textSelectInfo_t
	{
		public int pagenum;
		public bool first_line_full;
		public bool last_line_full;
	}

	public static class ScrollBarExtensions
	{
		public static double GetThumbCenter(this System.Windows.Controls.Primitives.ScrollBar s)
		{
			double thumbLength = GetThumbLength(s);
			double trackLength = s.Maximum - s.Minimum;

			return thumbLength / 2 + s.Minimum + (s.Value - s.Minimum) *
				(trackLength - thumbLength) / trackLength;
		}

		public static void SetThumbCenter(this System.Windows.Controls.Primitives.ScrollBar s, double thumbCenter)
		{
			double thumbLength = GetThumbLength(s);
			double trackLength = s.Maximum - s.Minimum;

			if (thumbCenter >= s.Maximum - thumbLength / 2)
			{
				s.Value = s.Maximum;
			}
			else if (thumbCenter <= s.Minimum + thumbLength / 2)
			{
				s.Value = s.Minimum;
			}
			else if (thumbLength >= trackLength)
			{
				s.Value = s.Minimum;
			}
			else
			{
				s.Value = (int)(s.Minimum + trackLength *
					((thumbCenter - s.Minimum - thumbLength / 2)
					/ (trackLength - thumbLength)));
			}
		}

		public static double GetThumbLength(this System.Windows.Controls.Primitives.ScrollBar s)
		{
			double trackLength = s.Maximum - s.Minimum;
			return trackLength * s.ViewportSize /
				(trackLength + s.ViewportSize);
		}

		public static void SetThumbLength(this System.Windows.Controls.Primitives.ScrollBar s, double thumbLength)
		{
			double trackLength = s.Maximum - s.Minimum;

			if (thumbLength < 0)
			{
				s.ViewportSize = 0;
			}
			else if (thumbLength < trackLength)
			{
				s.ViewportSize = trackLength * thumbLength / (trackLength - thumbLength);
			}
			else
			{
				s.ViewportSize = double.MaxValue;
			}
		}
	}

	public partial class MainWindow : Window
	{
		mudocument mu_doc = null;
		public Pages m_docPages;
		List<textSelectInfo_t> m_textSelect;
		List<DocPage> m_thumbnails;
		List<List<RectList>> m_page_link_list = null;
		IList<RectList> m_text_list;
		DocPage m_PrintPreviewPage;
		public List<LinesText> m_lineptrs = null;
		public List<BlocksText> m_textptrs = null;
		List<Boolean> m_textset = null;
		private bool m_file_open;
		private int m_currpage;
		private int m_searchpage;
		private int m_num_pages;
		private bool m_init_done;
		private bool m_links_on;
		String m_textsearchcolor = "#4072AC25";
		String m_textselectcolor = "#402572AC";
		String m_regionselect = "#00FFFFFF";
		String m_blockcolor = "#00FFFFFF";
		//String m_regionselect = "#FFFF0000";  /* Debug */
		String m_linkcolor = "#40AC7225";
		private bool m_have_thumbs;
		double m_doczoom;
		ghostsharp m_ghostscript;
		String m_currfile;
		String m_origfile;
		private gsprint m_ghostprint = null;
		bool m_isXPS;
		bool m_isImage;
		gsOutput m_gsoutput;
		Convert m_convertwin;
		PageExtractSave m_extractwin;
		Password m_password = null;
		PrintControl m_printcontrol = null;
		String m_currpassword = null;
		BackgroundWorker m_thumbworker = null;
		BackgroundWorker m_textsearch = null;
		BackgroundWorker m_linksearch = null;
		BackgroundWorker m_openfile = null;
		BackgroundWorker m_initrender = null;
		BackgroundWorker m_printerpreview = null;
		BackgroundWorker m_copytext = null;
		String m_document_type;
		Info m_infowindow;
		OutputIntent m_outputintents;
		Selection m_selection;
		String m_prevsearch = null;
		bool m_clipboardset;
		bool m_doscroll;
		bool m_intxtselect;
		bool m_textselected;
		System.Windows.Threading.DispatcherTimer m_dispatcherTimer = null;
		double m_lastY;
		double m_maxY;
		bool m_ignorescrollchange;
		double m_totalpageheight;
		AA_t m_AA;
		bool m_regstartup;
		int m_initpage;
		bool m_selectall;
		bool m_showannot;
		bool m_ScrolledChanged;

		public MainWindow()
		{
			InitializeComponent();
			this.Closing += new System.ComponentModel.CancelEventHandler(Window_Closing);
			m_file_open = false;
			m_regstartup = true;
			m_showannot = true;

			/* Allocations and set up */
			try
			{
				m_docPages = new Pages();
				m_thumbnails = new List<DocPage>();
				m_PrintPreviewPage = new DocPage();
				m_lineptrs = new List<LinesText>();
				m_textptrs = new List<BlocksText>();
				m_textset = new List<Boolean>();
				m_ghostscript = new ghostsharp();
				m_ghostscript.gsUpdateMain += new ghostsharp.gsCallBackMain(gsProgress);
				m_gsoutput = new gsOutput();
				m_gsoutput.Activate();
				m_outputintents = new OutputIntent();
				m_outputintents.Activate();
				m_ghostscript.gsIOUpdateMain += new ghostsharp.gsIOCallBackMain(gsIO);
				m_ghostscript.gsDLLProblemMain += new ghostsharp.gsDLLProblem(gsDLL);
				m_convertwin = null;
				m_extractwin = null;
				m_selection = null;
				xaml_ZoomSlider.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(ZoomReleased), true);
				xaml_PageList.AddHandler(Grid.DragOverEvent, new System.Windows.DragEventHandler(Grid_DragOver), true);
				xaml_PageList.AddHandler(Grid.DropEvent, new System.Windows.DragEventHandler(Grid_Drop), true);
				DimSelections();
				status_t result = CleanUp();

				string[] arguments = Environment.GetCommandLineArgs();
				if (arguments.Length > 1)
				{
					string filePath = arguments[1];
					ProcessFile(filePath);
				}
				else
				{
					if (m_regstartup)
						InitFromRegistry();
				}
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed at initialization\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
		}

		private void Grid_DragOver(object sender, System.Windows.DragEventArgs e)
		{
			if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop))
			{
				e.Effects = System.Windows.DragDropEffects.All;
			}
			else
			{
				e.Effects = System.Windows.DragDropEffects.None;
			}
			e.Handled = false;
		}

		private void Grid_Drop(object sender, System.Windows.DragEventArgs e)
		{
			if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop))
			{
				string[] docPath = (string[]) e.Data.GetData(System.Windows.DataFormats.FileDrop);
				ProcessFile(String.Join("",docPath));
			}
		}

		void CloseExtraWindows(bool shutdown)
		{
			if (m_selection != null)
				m_selection.Close();
			if (m_convertwin != null)
				m_convertwin.Close();
			if (m_extractwin != null)
				m_extractwin.Close();
			if (m_infowindow != null)
				m_infowindow.Close();
			if (shutdown)
			{
				if (m_gsoutput != null)
					m_gsoutput.RealWindowClosing();
				if (m_outputintents != null)
					m_outputintents.RealWindowClosing();
				if (m_printcontrol != null)
					m_printcontrol.RealWindowClosing();
			}
			else
			{
				if (m_gsoutput != null)
					m_gsoutput.Hide();
				if (m_outputintents != null)
					m_outputintents.Hide();
				if (m_printcontrol != null)
					m_printcontrol.Hide();
			}
		}

		void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			CloseExtraWindows(true);
		}

		/* Stuff not enabled when source is XPS */
		void EnabletoPDF()
		{
			xaml_savepdf.IsEnabled = true;
			xaml_linearize_pdf.IsEnabled = true;
			xaml_saveas.IsEnabled = true;
			xaml_Extract.IsEnabled = true;
			xaml_conversions.IsEnabled = true;
			xaml_extractselection.IsEnabled = true;
		}

		void DisabletoPDF()
		{
			xaml_savepdf.IsEnabled = false;
			xaml_linearize_pdf.IsEnabled = false;
			xaml_saveas.IsEnabled = false;
			xaml_Extract.IsEnabled = false;
			xaml_conversions.IsEnabled = false;
			xaml_extractselection.IsEnabled = false;
		}

		private void DimSelections()
		{
			xaml_currPage.Text = "";
			xaml_TotalPages.Text = "/ 0";
			xaml_Zoomsize.Text = "100";
			xaml_BackPage.Opacity = 0.5;
			xaml_Contents.Opacity = 0.5;
			xaml_currPage.Opacity = 0.5;
			xaml_currPage.IsEnabled = false;
			xaml_ForwardPage.Opacity = 0.5;
			xaml_Links.Opacity = 0.5;
			xaml_Print.Opacity = 0.5;
			xaml_SavePDF.Opacity = 0.5;
			xaml_Search.Opacity = 0.5;
			xaml_Thumbs.Opacity = 0.5;
			xaml_TotalPages.Opacity = 0.5;
			xaml_zoomIn.Opacity = 0.5;
			xaml_zoomOut.Opacity = 0.5;
			xaml_Zoomsize.Opacity = 0.5;
			xaml_ExpandFill.Opacity = 0.5;
			xaml_ContScrollFill.Opacity = 0.5;
			xaml_ActualSize.Opacity = 0.5;
			xaml_Zoomsize.IsEnabled = false;
			xaml_ZoomSlider.Opacity = 0.5;
			xaml_ZoomSlider.IsEnabled = false;
			xaml_saveas.IsEnabled = false;
			xaml_closefile.IsEnabled = false;
			xaml_showinfo.IsEnabled = false;
			xaml_extractselection.IsEnabled = false;
			xaml_conversions.IsEnabled = false;
			xaml_gsmessage.IsEnabled = false;
			xaml_print_menu.IsEnabled = false;
			xaml_view.IsEnabled = false;
			xaml_edit.IsEnabled = false;
		}

		private status_t CleanUp()
		{
			m_init_done = false;
			this.Cursor = System.Windows.Input.Cursors.Arrow;
			/* Collapse this stuff since it is going to be released */
			xaml_ThumbGrid.Visibility = System.Windows.Visibility.Collapsed;
			xaml_ContentGrid.Visibility = System.Windows.Visibility.Collapsed;
			xaml_VerticalScroll.Visibility = System.Windows.Visibility.Collapsed;

			/* Clear out everything */
			if (m_docPages != null && m_docPages.Count > 0)
				m_docPages.Clear();
			if (m_textSelect != null)
				m_textSelect.Clear();
			if (m_textset != null)
				m_textset.Clear();
			if (m_lineptrs != null && m_lineptrs.Count > 0)
				m_lineptrs.Clear();
			if (m_thumbnails != null && m_thumbnails.Count > 0)
				m_thumbnails.Clear();
			if (m_textptrs != null && m_textptrs.Count > 0)
				m_textptrs.Clear();
			if (m_page_link_list != null && m_page_link_list.Count > 0)
			{
				m_page_link_list.Clear();
				m_page_link_list = null;
			}
			if (m_text_list != null && m_text_list.Count > 0)
			{
				m_text_list.Clear();
				m_text_list = null;
			}
			if (mu_doc != null)
				mu_doc.CleanUp();
			try
			{
				mu_doc = new mudocument();
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed during clean up\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
			mu_doc.mupdfDLLProblemMain += new mudocument.mupdfDLLProblem(muDLL);
			status_t result = mu_doc.Initialize();
			mu_doc.mupdfUpdateMain += new mudocument.mupdfCallBackMain(mupdfUpdate);

			if (result != status_t.S_ISOK)
			{
				Console.WriteLine("Library allocation failed during clean up\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Library allocation failed!");
				return result;
			}

			m_have_thumbs = false;
			m_file_open = false;
			m_num_pages = -1;
			m_links_on = false;
			m_doczoom = 1.0;
			m_isXPS = false;
			m_isImage = false;
			//xaml_CancelThumb.IsEnabled = true;
			m_currpage = 0;
			m_ignorescrollchange = false;
			m_document_type = DocumentTypes.UNKNOWN;
			EnabletoPDF();
			m_clipboardset = false;
			m_doscroll = false;
			m_intxtselect = false;
			m_textselected = false;
			m_currpassword = null;
			CloseExtraWindows(false);
			ResetScroll();
			m_totalpageheight = 0;
			m_AA = GetAA();
			m_origfile = null;
			m_initpage = 0;
			xaml_Zoomsize.Text = "100";
			m_selectall = false;
			return result;
		}

		private String GetVersion()
		{
			Assembly assembly = Assembly.GetExecutingAssembly();
			FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
			String vers = fileVersionInfo.ProductVersion;
			String[] parts = vers.Split('.');
			String simple_vers = parts[0] + '.' + parts[1];
			return simple_vers;
		}

		/* Initialize from registry */
		private void InitFromRegistry()
		{
			RegistryKey key = Registry.CurrentUser.CreateSubKey("Software");
			RegistryKey keyA = key.CreateSubKey("Artifex Software");
			String vers = GetVersion();
			RegistryKey keygs = keyA.CreateSubKey("gsview " + vers);
			String filepath = null;
			Int32 page;
			AA_t aa = AA_t.HIGH;

			try
			{
				filepath = (String)keygs.GetValue("File", null);
				aa = (AA_t)keygs.GetValue("AA");
				page = (Int32)keygs.GetValue("Page");
			}
			catch
			{
				return;
			}
			keygs.Close();
			keyA.Close();
			key.Close();

			SetAA(aa);
			m_AA = aa;

			if (filepath != null && File.Exists(filepath))
			{
				m_initpage = page;
				ProcessFile(filepath);
			}
			else
				m_initpage = 0;
		}

		private void SetRegistry()
		{
			if (m_currfile == null)
				return;

			RegistryKey key = Registry.CurrentUser.CreateSubKey("Software");
			RegistryKey keyA = key.CreateSubKey("Artifex Software");
			String vers = GetVersion();
			RegistryKey keygs = keyA.CreateSubKey("gsview " + vers);

			if (m_origfile != null && (m_document_type == DocumentTypes.PS ||
				m_document_type == DocumentTypes.EPS))
			{
				keygs.SetValue("File", m_origfile, RegistryValueKind.String);
			}
			else
			{
				keygs.SetValue("File", m_currfile, RegistryValueKind.String);
			}
			keygs.SetValue("Page", m_currpage, RegistryValueKind.DWord);
			Int32 aa_int = (Int32)m_AA;
			keygs.SetValue("AA", aa_int, RegistryValueKind.DWord);
			keygs.Close();
			keyA.Close();
			key.Close();
		}

		private void AppClosing(object sender, CancelEventArgs e)
		{
			if (m_init_done)
				SetRegistry();
		}

		private void ShowMessage(NotifyType_t type, String Message)
		{
			if (type == NotifyType_t.MESS_ERROR)
			{
				System.Windows.Forms.MessageBox.Show(Message, "Error",
					MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			}
			else
			{
				System.Windows.Forms.MessageBox.Show(Message, "Notice",
					MessageBoxButtons.OK);
			}
		}

		private void CloseCommand(object sender, ExecutedRoutedEventArgs e)
		{
			if (m_init_done)
				CloseDoc();
		}

		private void CloseDoc()
		{
			CleanUp();
		}

		/* Set the page with the new raster information */
		private void UpdatePage(int page_num, Byte[] bitmap, Point ras_size,
			Page_Content_t content, double zoom_in, AA_t AA)
		{
			DocPage doc_page = this.m_docPages[page_num];

			doc_page.Width = (int)ras_size.X;
			doc_page.Height = (int)ras_size.Y;

			doc_page.Content = content;
			doc_page.Zoom = zoom_in;

			int stride = doc_page.Width * 4;
			doc_page.BitMap = BitmapSource.Create(doc_page.Width, doc_page.Height, 
				72, 72, PixelFormats.Pbgra32, BitmapPalettes.Halftone256, bitmap, stride);
			doc_page.PageNum = page_num;
			doc_page.AA = AA;

			if (content == Page_Content_t.THUMBNAIL)
			{
				doc_page.Width = (int)(ras_size.X / Constants.SCALE_THUMB);
				doc_page.Height = (int)(ras_size.Y / Constants.SCALE_THUMB);
			}
		}

		private void OpenFileCommand(object sender, ExecutedRoutedEventArgs e)
		{
			OpenFile(sender, e);
		}

		private void OpenFile(object sender, RoutedEventArgs e)
		{
			if (m_password != null && m_password.IsActive)
				m_password.Close();

			if (m_printcontrol != null && m_printcontrol.IsActive)
				m_printcontrol.Close();

			/* Release the print control regardless of it being opened. 
			   We don't want previous documents pages in the preview */
			m_printcontrol = null;

			if (m_infowindow != null && m_infowindow.IsActive)
				m_infowindow.Close();

			/* Check if gs is currently busy. If it is then don't allow a new
			 * file to be opened. They can cancel gs with the cancel button if
			 * they want */
			if (m_ghostscript.GetStatus() != gsStatus.GS_READY)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "GS busy. Cancel to open new file.");
				return;
			}

			if (m_ghostprint != null && m_ghostprint.IsBusy())
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Let printing complete");
				return;
			}

			System.Windows.Forms.OpenFileDialog dlg = new System.Windows.Forms.OpenFileDialog();
			dlg.Filter = "Document Files(*.ps;*.eps;*.pdf;*.xps;*.oxps;*.cbz;*.png;*.jpg;*.jpeg)|*.ps;*.eps;*.pdf;*.xps;*.oxps;*.cbz;*.png;*.jpg;*.jpeg|All files (*.*)|*.*";
			dlg.FilterIndex = 1;
			if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				ProcessFile(dlg.FileName);
		}

		private void ProcessFile(String FileName)
		{
			if (m_file_open)
			{
				CloseDoc();
			}
			/* If we have a ps or eps file then launch the distiller first
			 * and then we will get a temp pdf file which will be opened by
			 * mupdf */
			string extension = System.IO.Path.GetExtension(FileName);
			/* We are doing this based on the extension but like should do
			 * it based upon the content */
			switch (extension.ToUpper())
			{
				case ".PS":
					m_document_type = DocumentTypes.PS;
					break;
				case ".EPS":
					m_document_type = DocumentTypes.EPS;
					break;
				case ".XPS":
				case ".OXPS":
					m_document_type = DocumentTypes.XPS;
					break;
				case ".PDF":
					m_document_type = DocumentTypes.PDF;
					break;
				case ".CBZ":
					m_document_type = DocumentTypes.CBZ;
					break;
				case ".PNG":
					m_document_type = DocumentTypes.PNG;
					break;
				case ".JPG":
					m_document_type = DocumentTypes.JPG;
					break;
				case ".JPEG":
					m_document_type = DocumentTypes.JPG;
					break;
				default:
					{
						ShowMessage(NotifyType_t.MESS_STATUS, "Unknown File Type");
						return;
					}
			}
			if (extension.ToUpper() == ".PS" || extension.ToUpper() == ".EPS")
			{
				xaml_DistillProgress.Value = 0;
				if (m_ghostscript.DistillPS(FileName, Constants.DEFAULT_GS_RES) == gsStatus.GS_BUSY)
				{
					ShowMessage(NotifyType_t.MESS_STATUS, "GS currently busy");
					return;
				}
				xaml_DistillName.Text = "Distilling";
				xaml_CancelDistill.Visibility = System.Windows.Visibility.Visible;
				xaml_DistillName.FontWeight = FontWeights.Bold;
				xaml_DistillGrid.Visibility = System.Windows.Visibility.Visible;
				return;
			}
			/* Set if this is already xps for printing */
			if (extension.ToUpper() == ".XPS" || extension.ToUpper() == ".OXPS")
				m_isXPS = true;
			if (extension.ToUpper() == ".CBZ" || extension.ToUpper() == ".PNG" ||
				extension.ToUpper() == ".JPG")
				m_isImage = true;
			OpenFile2(FileName);
		}

		private void OpenFile2(String File)
		{
			m_currfile = File;
			xaml_OpenProgressGrid.Visibility = System.Windows.Visibility.Visible;
			xaml_openfilestatus.Text = "Opening File";
		/* The file open can take a fair amount of time. So that we can show
			* an indeterminate progress bar while opening, go ahead an do this
			* on a separate thread */
			OpenFileBG();
		}

		private void OpenWork(object sender, DoWorkEventArgs e)
		{
			BackgroundWorker worker = sender as BackgroundWorker;

			status_t code = mu_doc.OpenFile(m_currfile);
			worker.ReportProgress(100, code);
		}

		private void OpenProgress(object sender, ProgressChangedEventArgs e)
		{
			status_t result = (status_t)(e.UserState);

			if (result == status_t.S_ISOK)
			{
				/* Check if we need a password */
				if (mu_doc.RequiresPassword())
				{
					xaml_OpenProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
					GetPassword();
				}
				else
					StartViewer();
			}
			else
			{
				m_currfile = null;
			}
		}

		private void OpenFileBG()
		{
			try
			{
				m_openfile = new BackgroundWorker();
				m_openfile.WorkerReportsProgress = true;
				m_openfile.WorkerSupportsCancellation = false;
				m_openfile.DoWork += new DoWorkEventHandler(OpenWork);
				m_openfile.ProgressChanged += new ProgressChangedEventHandler(OpenProgress);
				m_openfile.RunWorkerAsync();
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed during opening\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
		}

		private void SetPageAnnot(int page_num, Annotate_t render_result)
		{
			if (m_docPages[page_num].Annotate == Annotate_t.UNKNOWN ||
				m_docPages[page_num].Annotate == Annotate_t.COMPUTING)
			{
				if (render_result == Annotate_t.NO_ANNOTATE)
					m_docPages[page_num].Annotate = Annotate_t.NO_ANNOTATE;
				else
				{
					if (m_showannot)
						m_docPages[page_num].Annotate = Annotate_t.ANNOTATE_VISIBLE;
					else
						m_docPages[page_num].Annotate = Annotate_t.ANNOTATE_HIDDEN;
				}
			}
			else
			{
				if (m_docPages[page_num].Annotate != Annotate_t.NO_ANNOTATE)
				{
					if (m_showannot)
						m_docPages[page_num].Annotate = Annotate_t.ANNOTATE_VISIBLE;
					else
						m_docPages[page_num].Annotate = Annotate_t.ANNOTATE_HIDDEN;
				}
			}
		}

		private void InitialRenderWork(object sender, DoWorkEventArgs e)
		{
			BackgroundWorker worker = sender as BackgroundWorker;
			int look_ahead = Math.Min(m_num_pages, Constants.INIT_LOOK_AHEAD);

			/* Do the first few full res pages */
			for (int k = 0; k < look_ahead; k++)
			{
				if (m_num_pages > k)
				{
					Point ras_size;
					double scale_factor = 1.0;
					Byte[] bitmap;
					BlocksText charlist;
					status_t code;
					Annotate_t annot;

					if (ComputePageSize(k, scale_factor, out ras_size) == status_t.S_ISOK)
					{
						try
						{
							bitmap = new byte[(int)ras_size.X * (int)ras_size.Y * 4];

							/* Synchronous call on our background thread */
							code = (status_t)mu_doc.RenderPage(k, bitmap, (int)ras_size.X,
								(int)ras_size.Y, scale_factor, false, true,
								!(m_textset[k]), out charlist, m_showannot, out annot);
						}
						catch (OutOfMemoryException em)
						{
							Console.WriteLine("Memory allocation failed init page " + k + em.Message + "\n");
							break;
						}
						/* create new page if we rendered ok. set ui value with 
						 * progress call back, pass page number, charlist and bitmap */
						if (code == status_t.S_ISOK)
						{
							pageprogress_t page_prog = new pageprogress_t();
							page_prog.bitmap = bitmap;
							page_prog.charlist = charlist;
							page_prog.pagenum = k;
							page_prog.size = ras_size;
							page_prog.annot = annot;
							worker.ReportProgress(100, page_prog);
						}
					}
				}
			}
		}

		private void InitialRenderProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			pageprogress_t result = (pageprogress_t)(e.UserState);
			int k = result.pagenum;

			m_textset[k] = true;
			m_textptrs[k] = result.charlist;
			m_docPages[k].TextBlocks = result.charlist;
			UpdatePage(k, result.bitmap, result.size, Page_Content_t.FULL_RESOLUTION, 1.0, m_AA);
			m_docPages[k].NativeHeight = (int) result.size.Y;
			m_docPages[k].NativeWidth = (int)result.size.X;
			SetPageAnnot(k, result.annot);
		}

		private void InitialRenderCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			m_init_done = true;
			m_currpage = 0;
			RenderThumbs();
			m_file_open = true;
			xaml_BackPage.Opacity = 1;
			xaml_Contents.Opacity = 1;
			xaml_currPage.Opacity = 1;
			xaml_ForwardPage.Opacity = 1;
			xaml_Links.Opacity = 1;
			xaml_Print.Opacity = 1;
			xaml_SavePDF.Opacity = 1;
			xaml_Search.Opacity = 1;
			xaml_Thumbs.Opacity = 1;
			xaml_TotalPages.Opacity = 1;
			xaml_zoomIn.Opacity = 1;
			xaml_zoomOut.Opacity = 1;
			xaml_Zoomsize.Opacity = 1;
			xaml_ExpandFill.Opacity = 1;
			xaml_ContScrollFill.Opacity = 1;
			xaml_ActualSize.Opacity = 1;
			xaml_Zoomsize.IsEnabled = true;
			xaml_currPage.IsEnabled = true;
			xaml_TotalPages.Text = "/ " + m_num_pages.ToString();
			xaml_currPage.Text = "1";
			xaml_ZoomSlider.Opacity = 1.0;
			xaml_ZoomSlider.IsEnabled = true;
			xaml_closefile.IsEnabled = true;
			xaml_saveas.IsEnabled = true;
			xaml_showinfo.IsEnabled = true;
			xaml_extractselection.IsEnabled = true;
			xaml_conversions.IsEnabled = true;
			xaml_gsmessage.IsEnabled = true;
			xaml_print_menu.IsEnabled = true;
			xaml_view.IsEnabled = true;
			xaml_edit.IsEnabled = true;
			if (m_isXPS || m_isImage)
				DisabletoPDF();
			if (m_isImage)
			{
				xaml_Print.IsEnabled = false;
				xaml_print_menu.IsEnabled = false;
				xaml_Print.Opacity = 0.5;
				xaml_print_menu.Opacity = 0.5;
			}
			else
			{
				xaml_Print.IsEnabled = true;
				xaml_print_menu.IsEnabled = true;
				xaml_Print.Opacity = 1.0;
				xaml_print_menu.Opacity = 1.0;
			}
			xaml_OpenProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
			xaml_VerticalScroll.Visibility = System.Windows.Visibility.Visible;
			xaml_VerticalScroll.Value = 0;
		}

		private void InitialRenderBG()
		{
			int look_ahead = Math.Min(Constants.INIT_LOOK_AHEAD, m_num_pages);
			m_currpage = 0;
			m_thumbnails.Capacity = m_num_pages;
			
			for (int k = 0; k < Constants.INIT_LOOK_AHEAD; k++)
			{
				m_docPages.Add(InitDocPage());
				m_docPages[k].PageNum = k;
				m_textptrs.Add(new BlocksText());
				m_lineptrs.Add(new LinesText());
				m_textset.Add(false);
			}
			var dummy = InitDocPage();
			for (int k = Constants.INIT_LOOK_AHEAD; k < m_num_pages; k++)
			{
				m_docPages.Add(dummy);
				m_textptrs.Add(new BlocksText());
				m_lineptrs.Add(new LinesText());
				m_textset.Add(false);
			}

			xaml_PageList.ItemsSource = m_docPages;

			try
			{
				m_initrender = new BackgroundWorker();
				m_initrender.WorkerReportsProgress = true;
				m_initrender.WorkerSupportsCancellation = false;
				m_initrender.DoWork += new DoWorkEventHandler(InitialRenderWork);
				m_initrender.RunWorkerCompleted += new RunWorkerCompletedEventHandler(InitialRenderCompleted);
				m_initrender.ProgressChanged += new ProgressChangedEventHandler(InitialRenderProgressChanged);
				m_initrender.RunWorkerAsync();
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed during initial render\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
		}
		private void StartViewer()
		{
			m_num_pages = mu_doc.GetPageCount();

			if (m_num_pages == 0)
			{
				xaml_OpenProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
				CleanUp();
				ShowMessage(NotifyType_t.MESS_ERROR, m_currfile + " is corrupted");
			}
			else
			{
				xaml_openfilestatus.Text = "Initial Page Rendering";
				xaml_openfilestatus.UpdateLayout();
				InitialRenderBG();
			}
		}

		private status_t ComputePageSize(int page_num, double scale_factor,
									out Point render_size)
		{
			Point renpageSize = new Point();

			status_t code = (status_t)mu_doc.GetPageSize(page_num, out render_size);
			if (code != status_t.S_ISOK)
				return code;

			renpageSize.X = (render_size.X * scale_factor);
			renpageSize.Y = (render_size.Y * scale_factor);

			render_size = renpageSize;

			return status_t.S_ISOK;
		}

		private DocPage InitDocPage()
		{
			DocPage doc_page = new DocPage();

			doc_page.BitMap = null;
			doc_page.Height = Constants.BLANK_HEIGHT;
			doc_page.Width = Constants.BLANK_WIDTH;
			doc_page.NativeHeight = Constants.BLANK_HEIGHT;
			doc_page.NativeWidth = Constants.BLANK_WIDTH;
			doc_page.Content = Page_Content_t.NOTSET;
			doc_page.TextBox = null;
			doc_page.LinkBox = null;
			doc_page.SelHeight = 0;
			doc_page.SelWidth = 0;
			doc_page.SelX = 0;
			doc_page.SelY = 0;
			return doc_page;
		}

		#region Navigation
		private void OnBackPageClick(object sender, RoutedEventArgs e)
		{
			if (m_currpage == 0 || !m_init_done) return;
			m_ignorescrollchange = true;
			RenderRange(m_currpage - 1, true, zoom_t.NO_ZOOM, 0);
		}

		private void OnForwardPageClick(object sender, RoutedEventArgs e)
		{
			if (m_currpage == m_num_pages - 1 || !m_init_done) return;
			m_ignorescrollchange = true;
			RenderRange(m_currpage + 1, true, zoom_t.NO_ZOOM, 0);
		}

		private void PageEnterClicked(object sender, System.Windows.Input.KeyEventArgs e)
		{
			if (e.Key == Key.Return)
			{
				e.Handled = true;
				var desired_page = xaml_currPage.Text;
				try
				{
					int page = System.Convert.ToInt32(desired_page);
					if (page > 0 && page < (m_num_pages + 1))
					{
						m_ignorescrollchange = true;
						RenderRange(page - 1, true, zoom_t.NO_ZOOM, 0);
					}
				}
				catch (FormatException e1)
				{
					Console.WriteLine("String is not a sequence of digits.");
				}
				catch (OverflowException e2)
				{
					Console.WriteLine("The number cannot fit in an Int32.");
				}
			}
		}

		private void OnKeyDownHandler(object sender, System.Windows.Input.KeyEventArgs e)
		{
			switch (e.Key)
			{
				case Key.Left:
				case Key.PageUp:
					if (m_currpage == 0 || !m_init_done)
						return;
					m_ignorescrollchange = true;
					RenderRange(m_currpage - 1, true, zoom_t.NO_ZOOM, 0);
					e.Handled = true;
					break;

				case Key.Right:
				case Key.PageDown:
					if (m_currpage == m_num_pages - 1 || !m_init_done)
						return;
					m_ignorescrollchange = true;
					RenderRange(m_currpage + 1, true, zoom_t.NO_ZOOM, 0);
					e.Handled = true;
					break;

				case Key.Up:
					if (!m_init_done)
						return;
					e.Handled = true;
					OffsetScroll(-Constants.VERT_SCROLL_STEP * m_doczoom);
					break;

				case Key.Down:
					if (!m_init_done)
						return;
					e.Handled = true;
					OffsetScroll(Constants.VERT_SCROLL_STEP * m_doczoom);
					break;
			}
		}
		#endregion Navigation

		private void CancelLoadClick(object sender, RoutedEventArgs e)
		{
			/* Cancel during thumbnail loading. Deactivate the button 
			 * and cancel the thumbnail rendering */
			if (m_thumbworker != null)
				m_thumbworker.CancelAsync();
			//xaml_CancelThumb.IsEnabled = false;
		}

		private void ToggleThumbs(object sender, RoutedEventArgs e)
		{
			if (m_have_thumbs)
			{
				if (xaml_ThumbGrid.Visibility == System.Windows.Visibility.Collapsed)
				{
					xaml_ThumbGrid.Visibility = System.Windows.Visibility.Visible;
				}
				else
				{
					xaml_ThumbGrid.Visibility = System.Windows.Visibility.Collapsed;
				}
			}
		}

		private void ToggleContents(object sender, RoutedEventArgs e)
		{
			if (xaml_ContentGrid.Visibility == System.Windows.Visibility.Visible)
			{
				xaml_ContentGrid.Visibility = System.Windows.Visibility.Collapsed;
				return;
			}

			if (m_num_pages < 0)
				return;

			if (xaml_ContentList.Items.IsEmpty)
			{
				int size_content = mu_doc.ComputeContents();
				if (size_content == 0)
					return;
				xaml_ContentList.ItemsSource = mu_doc.contents;
			}
			xaml_ContentGrid.Visibility = System.Windows.Visibility.Visible;
		}

		private void ThumbSelected(object sender, MouseButtonEventArgs e)
		{
			var item = ((FrameworkElement)e.OriginalSource).DataContext as DocPage;

			if (item != null)
			{
				if (item.PageNum < 0)
					return;
				RenderRange(item.PageNum, true, zoom_t.NO_ZOOM, 0);
			}
		}

		private void ContentSelected(object sender, MouseButtonEventArgs e)
		{
			var item = ((FrameworkElement)e.OriginalSource).DataContext as ContentItem;
			if (item != null && item.Page < m_num_pages)
			{
				int page = m_docPages[item.Page].PageNum;
				if (page >= 0 && page < m_num_pages)
					RenderRange(page, true, zoom_t.NO_ZOOM, 0);
			}
		}

		/* We need to avoid rendering due to size changes */
		private void ListViewScrollChanged(object sender, ScrollChangedEventArgs e)
		{
			/* This makes sure we dont call render range a second time due to 
			 * page advances */
			int first_item = -1;
			int second_item = -1;
			//Console.WriteLine("***************************************/n");
			//Console.WriteLine("VerticalChange = " + e.VerticalChange + "/n");
			//Console.WriteLine("ExtentHeightChange = " + e.ExtentHeightChange + "/n");
			//Console.WriteLine("ExtentWidthChange = " + e.ExtentWidthChange + "/n");
			//Console.WriteLine("HorizontalChange = " + e.HorizontalChange + "/n");
			//Console.WriteLine("ViewportHeightChange = " + e.ViewportHeightChange + "/n");
			//Console.WriteLine("ViewportWidthChange = " + e.ViewportWidthChange + "/n");
			//Console.WriteLine("ExtentHeight = " + e.ExtentHeight + "/n");
			//Console.WriteLine("ViewportHeight = " + e.ViewportHeight + "/n");
			//Console.WriteLine("VerticalOffset = " + e.VerticalOffset + "/n");
			//Console.WriteLine("***************************************/n");
			if (m_ignorescrollchange == true)
			{
				m_ignorescrollchange = false;
				return;
			}
			if (!m_init_done)
				return;
			if (e.VerticalChange == 0)
				return;
			if (m_num_pages == 1)
				return;

			/* From current page go forward and backward checking if pages are
			 * visible */
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
			{
				double bottom = this.ActualHeight;
				/* first going forward */
				for (int kk = m_currpage + 1; kk < m_num_pages; kk++)
				{
					UIElement uiElement = (UIElement)xaml_PageList.ItemContainerGenerator.ContainerFromIndex(kk);
					double y_top = uiElement.TranslatePoint(new System.Windows.Point(0, 0), xaml_PageList).Y;
					double y_bottom = uiElement.TranslatePoint(new System.Windows.Point(0, m_docPages[kk].Height), xaml_PageList).Y;
					/* Test if this and all further pages are outside window */
					if (y_top > bottom)
						break;
					/* Test if page is not even yet in window */
					if (y_bottom > 0)
					{
						if (!(m_dispatcherTimer != null && m_dispatcherTimer.IsEnabled == true))
						{
							/* In this case grab the first one that we find */
							if (second_item == -1)
								second_item = kk;
						}
					}
				}

				/* and now going backward */
				for (int kk = m_currpage; kk > -1; kk--)
				{
					UIElement uiElement = (UIElement)xaml_PageList.ItemContainerGenerator.ContainerFromIndex(kk);
					double y_top = uiElement.TranslatePoint(new System.Windows.Point(0, 0), xaml_PageList).Y;
					double y_bottom = uiElement.TranslatePoint(new System.Windows.Point(0, m_docPages[kk].Height), xaml_PageList).Y;
					/* Test if this and all further pages are outside window */
					if (y_bottom < 0)
						break;
					if (y_top < bottom)
						if (!(m_dispatcherTimer != null && m_dispatcherTimer.IsEnabled == true))
							first_item = kk;
				}
				e.Handled = true;
				if (first_item != -1)
					second_item = first_item;
				/* Finish */
				if (m_ScrolledChanged)
				{
					m_ScrolledChanged = false;
				}
				else
				{
					/* We have to update the vertical scroll position */
					double perc = (e.VerticalOffset) / (e.ExtentHeight - e.ViewportHeight);
					xaml_VerticalScroll.Value = perc * xaml_VerticalScroll.Maximum;
				}
				if (second_item < 0)
					second_item = 0;
				RenderRange(second_item, false, zoom_t.NO_ZOOM, 0);
			}
		}

		/* ScrollIntoView will not scroll to top on its own.  If item is already
		 * in view it just sits there */
		private void ScrollPageToTop(int k, double offset, bool from_scroller)
		{
			if (m_num_pages == 1)
				return;
			/* Get access to the scrollviewer */
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
			{
				UIElement uiElement = (UIElement) xaml_PageList.ItemContainerGenerator.ContainerFromIndex(k);
				double y = uiElement.TranslatePoint(new System.Windows.Point(0, offset), xaml_PageList).Y;
				double curr_value = viewer.VerticalOffset;
				viewer.ScrollToVerticalOffset(curr_value + y);

				if (!from_scroller)
				{
					double perc = (double) k / (double) ( m_num_pages - 1);
					xaml_VerticalScroll.Value = perc * xaml_VerticalScroll.Maximum;
				}
			}
		}

		/* Scroll to offset */
		private void OffsetScroll(double offset)
		{
			if (m_num_pages == 1)
				return;
			/* Get access to the scrollviewer */
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
			{
				double curr_value = viewer.VerticalOffset;
				AdjustScrollPercent(offset / viewer.ScrollableHeight);
				viewer.ScrollToVerticalOffset(curr_value + offset);
			}
		}

		/* Scroll to offset */
		private void OffsetScrollPercent(double percent)
		{
			/* Get access to the scrollviewer */
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
			{
				double curr_value = viewer.VerticalOffset;
				if (curr_value < 0 || curr_value > viewer.MaxHeight)
					return;
				var extentheight = viewer.ExtentHeight - viewer.ViewportHeight;

				var pos = extentheight * percent;
				viewer.ScrollToVerticalOffset(pos);
			}
		}

		/* Render +/- the look ahead from where we are if blank page is present */
		async private void RenderRange(int new_page, bool scrollto, zoom_t newzoom, double zoom_offset)
		{
			/* Need to figure out what pages are going to be visible */
			double bottom = this.ActualHeight;
			bool done = false;
			int final_page = new_page;
			double count = -zoom_offset;
			int offset = -1;
			bool scrollbottom = false;

			if (newzoom != zoom_t.NO_ZOOM)
				offset = 0;

			if (m_thumbnails.Count < m_num_pages)
				final_page = final_page + 1;
			else
			{
				while (!done && final_page >= 0 && final_page < m_num_pages)
				{
					count = count + m_thumbnails[final_page].NativeHeight * m_doczoom;
					final_page = final_page + 1;
					if (final_page == m_num_pages || count > bottom)
						done = true;
				}
				/* We have zoomed out to a point where the offset will not stay
				 * in its current spot.  Figure out where we need to be */
				final_page = final_page - 1;
				if (newzoom == zoom_t.ZOOM_OUT && count < bottom)
				{
					int curr_page = new_page - 1;
					while (true)
					{
						if (curr_page < 0)
							break;
						count = count + m_thumbnails[curr_page].NativeHeight * m_doczoom;
						if (count > bottom)
							break;
						curr_page = curr_page - 1;
					}
					new_page = curr_page;
					if (new_page < 0)
						new_page = 0;
					scrollbottom = true;
				}
			}

			for (int k = new_page + offset; k <= final_page + 1; k++)
			{
				if (k >= 0 && k < m_num_pages)
				{
					/* Check if page is already rendered */
					var doc = m_docPages[k];
					if (doc.Content != Page_Content_t.FULL_RESOLUTION ||
						doc.Zoom != m_doczoom || m_AA != doc.AA ||
						(doc.Annotate == Annotate_t.UNKNOWN && m_showannot) ||
						(doc.Annotate == Annotate_t.ANNOTATE_VISIBLE && !m_showannot) ||
						(doc.Annotate == Annotate_t.ANNOTATE_HIDDEN && m_showannot))
					{
						Point ras_size;
						double scale_factor = m_doczoom;
						/* To avoid multiple page renderings on top of one 
						 * another with scroll changes mark this as being 
						 * full resolution */
						m_docPages[k].Content = Page_Content_t.FULL_RESOLUTION;
						/* Avoid launching another thread just because we don't 
						 * know the annotation condition for this page */
						m_docPages[k].Annotate = Annotate_t.COMPUTING;
						if (ComputePageSize(k, scale_factor, out ras_size) == status_t.S_ISOK)
						{
							try
							{
								Byte[] bitmap = new byte[(int)ras_size.X * (int)ras_size.Y * 4];
								BlocksText charlist = null;
								Annotate_t annot = Annotate_t.UNKNOWN;
								m_docPages[k].NativeWidth = (int)(ras_size.X / scale_factor);
								m_docPages[k].NativeHeight = (int)(ras_size.Y / scale_factor);

								Task<int> ren_task =
									new Task<int>(() => mu_doc.RenderPage(k, bitmap,
										(int)ras_size.X, (int)ras_size.Y, scale_factor,
										false, true, !(m_textset[k]), out charlist, m_showannot,
										out annot));
								ren_task.Start();
								await ren_task.ContinueWith((antecedent) =>
								{
									status_t code = (status_t)ren_task.Result;
									if (code == status_t.S_ISOK)
									{
										SetPageAnnot(k, annot);
										if (m_docPages[k].TextBox != null)
											ScaleTextBox(k);
										if (m_links_on && m_page_link_list != null)
										{
											m_docPages[k].LinkBox = m_page_link_list[k];
											if (m_docPages[k].LinkBox != null)
												ScaleLinkBox(k);
										}
										else
										{
											m_docPages[k].LinkBox = null;
										}
										if (!(m_textset[k]) && charlist != null)
										{
											m_textptrs[k] = charlist;
											if (scale_factor != 1.0)
												ScaleTextBlocks(k, scale_factor);
											m_docPages[k].TextBlocks = m_textptrs[k];
											m_textset[k] = true;
											if (m_selectall)
											{
												int num_blocks = m_docPages[k].TextBlocks.Count;
												for (int jj = 0; jj < num_blocks; jj++)
												{
													m_docPages[k].TextBlocks[jj].Color = m_textselectcolor;
												}
											}
										}
										else
										{
											/* We had to rerender due to scale */
											if (m_textptrs[k] != null)
											{
												ScaleTextBlocks(k, scale_factor);
												m_docPages[k].TextBlocks = m_textptrs[k];
											}
											if (m_lineptrs[k] != null)
											{
												ScaleTextLines(k, scale_factor);
												m_docPages[k].SelectedLines = m_lineptrs[k];
											}
										}
										/* This needs to be handled here to reduce 
										 * flashing effects */
										if (newzoom != zoom_t.NO_ZOOM && k == new_page)
										{
											m_ignorescrollchange = true;
											UpdatePageSizes();
											xaml_VerticalScroll.Maximum = m_totalpageheight * m_doczoom + 4 * m_num_pages;
											if (!scrollbottom)
												ScrollPageToTop(new_page, zoom_offset, false);
										}
										UpdatePage(k, bitmap, ras_size,
											Page_Content_t.FULL_RESOLUTION, m_doczoom, m_AA);
										if (k == new_page && scrollto && new_page != m_currpage)
										{
											m_doscroll = true;
											ScrollPageToTop(k, 0, false);
										}
									}
								}, TaskScheduler.FromCurrentSynchronizationContext());
							}
							catch (OutOfMemoryException e)
							{
								Console.WriteLine("Memory allocation failed page " + k + "\n");
								ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
							}
						}
					}
					else
					{
						/* We did not have to render the page but we may need to
						 * scroll to it */
						if (k == new_page && scrollto && new_page != m_currpage)
						{
							m_ignorescrollchange = true;
							ScrollPageToTop(k, 0, false);
						}
					}
				}
			}
			/* Release old range and set new page */
			//ReleasePages(m_currpage, new_page - 1, final_page + 1);
			m_currpage = new_page;
			xaml_currPage.Text = (m_currpage + 1).ToString();
		}

		/* Avoids the next page jumping into view when touched by mouse. See xaml code */
		private void AvoidScrollIntoView(object sender, RequestBringIntoViewEventArgs e)
		{
			if (!m_doscroll)
				e.Handled = true;
			else
				m_doscroll = false;
		}

		private void ReleasePages(int old_page, int new_page, int final_page)
		{
			if (old_page == new_page) return;
			/* To keep from having memory issue reset the page back to
				the thumb if we are done rendering the thumbnails */
			for (int k = 0; k < m_num_pages; k++)
			{
				if (k < new_page || k > final_page)
				{
					if (k >= 0 && k < m_num_pages)
					{
						SetThumb(k);
					}
				}
			}
		}

		/* Return this page from a full res image to the thumb image */
		private void SetThumb(int page_num)
		{
			/* See what is there now */
			var doc_page = m_docPages[page_num];
			if (doc_page.Content == Page_Content_t.THUMBNAIL &&
				doc_page.Zoom == m_doczoom) return;

			if (m_thumbnails.Count > page_num)
			{
				doc_page.Content = Page_Content_t.THUMBNAIL;
				doc_page.Zoom = m_doczoom;

				doc_page.BitMap = m_thumbnails[page_num].BitMap;
				doc_page.Width = (int)(m_doczoom * doc_page.BitMap.PixelWidth / Constants.SCALE_THUMB);
				doc_page.Height = (int)(m_doczoom * doc_page.BitMap.PixelHeight / Constants.SCALE_THUMB);
				doc_page.PageNum = page_num;
				doc_page.LinkBox = null;
				doc_page.TextBox = null;
				/* No need to refresh unless it just occurs during other stuff
				 * we just want to make sure we can release the bitmaps */
				//doc_page.PageRefresh();
			}
		}

		private void gsDLL(object gsObject, String mess)
		{
			ShowMessage(NotifyType_t.MESS_STATUS, mess);
		}

		/* Catastrophic */
		private void muDLL(object gsObject, String mess)
		{
			ShowMessage(NotifyType_t.MESS_ERROR, mess);
			/* Disable even the ability to open a file */
			xaml_open.Opacity = 0.5;
			xaml_open.IsEnabled = false;
			xaml_file.Opacity = 0.5;
			xaml_file.IsEnabled = false;
			/* And to drag - drop or registry start up */
			xaml_PageList.RemoveHandler(Grid.DragOverEvent, new System.Windows.DragEventHandler(Grid_DragOver));
			xaml_PageList.RemoveHandler(Grid.DropEvent, new System.Windows.DragEventHandler(Grid_Drop));
			m_regstartup = false;
		}

		private void gsIO(object gsObject, String mess, int len)
		{
			m_gsoutput.Update(mess, len);
		}

		private void mupdfUpdate(object muObject, muPDFEventArgs asyncInformation)
		{
			if (asyncInformation.Completed)
			{
				xaml_MuPDFProgress.Value = 100;
				xaml_MuPDFGrid.Visibility = System.Windows.Visibility.Collapsed;
				if (asyncInformation.Params.result == GS_Result_t.gsFAILED)
				{
					ShowMessage(NotifyType_t.MESS_STATUS, "MuPDF failed to convert document");
				}
				MuPDFResult(asyncInformation.Params);
			}
			else
			{
				this.xaml_MuPDFProgress.Value = asyncInformation.Progress;
			}
		}

		/* MuPDF Result*/
		public void MuPDFResult(ConvertParams_t gs_result)
		{
			if (gs_result.result == GS_Result_t.gsCANCELLED)
			{
				xaml_MuPDFGrid.Visibility = System.Windows.Visibility.Collapsed;
				return;
			}
			if (gs_result.result == GS_Result_t.gsFAILED)
			{
				xaml_MuPDFGrid.Visibility = System.Windows.Visibility.Collapsed;
				ShowMessage(NotifyType_t.MESS_STATUS, "MuPDF Failed Conversion");
				return;
			}
			ShowMessage(NotifyType_t.MESS_STATUS, "MuPDF Completed Conversion");
		}

		private void gsProgress(object gsObject, gsEventArgs asyncInformation)
		{
			if (asyncInformation.Completed)
			{
				xaml_DistillProgress.Value = 100;
				xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
				if (asyncInformation.Params.result == GS_Result_t.gsFAILED)
				{
					switch (asyncInformation.Params.task)
					{
						case GS_Task_t.CREATE_XPS:
							ShowMessage(NotifyType_t.MESS_STATUS, "Ghostscript failed to create XPS");
							break;

						case GS_Task_t.PS_DISTILL:
							ShowMessage(NotifyType_t.MESS_STATUS, "Ghostscript failed to distill PS");
							break;

						case GS_Task_t.SAVE_RESULT:
							ShowMessage(NotifyType_t.MESS_STATUS, "Ghostscript failed to convert document");
							break;
					}
					return;
				}
				GSResult(asyncInformation.Params);
			}
			else
			{
				this.xaml_DistillProgress.Value = asyncInformation.Progress;
			}
		}

		/* GS Result*/
		public void GSResult(gsParams_t gs_result)
		{
			if (gs_result.result == GS_Result_t.gsCANCELLED)
			{
				xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
				return;
			}
			if (gs_result.result == GS_Result_t.gsFAILED)
			{
				xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
				ShowMessage(NotifyType_t.MESS_STATUS, "GS Failed Conversion");
				return;
			}
			switch (gs_result.task)
			{
				case GS_Task_t.CREATE_XPS:
					xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
					PrintXPS(gs_result.outputfile);
					break;

				case GS_Task_t.PS_DISTILL:
					xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
					m_origfile = gs_result.inputfile;
					OpenFile2(gs_result.outputfile);
					break;

				case GS_Task_t.SAVE_RESULT:
					ShowMessage(NotifyType_t.MESS_STATUS, "GS Completed Conversion");
					break;
			}
		}

		private void PrintCommand(object sender, ExecutedRoutedEventArgs e)
		{
			Print(sender, e);
		}

		/* Printing is achieved using xpswrite device in ghostscript and
		 * pushing that file through the XPS print queue */
		private void Print(object sender, RoutedEventArgs e)
		{
			if (!m_file_open)
				return;

			if (m_printcontrol == null)
			{
				m_printcontrol = new PrintControl(m_num_pages, m_currpage);
				m_printcontrol.PrintDiagUpdatePreview += new PrintControl.PrintDiagCallBackPreview(PrintDiagUpdatePreview);
				m_printcontrol.PrintDiagPrint += new PrintControl.PrintDiagCallBackPrint(PrintDiagPrint);
				m_printcontrol.PrintDLLProblemMain += new PrintControl.PrintDLLProblem(gsDLL);
				m_printcontrol.Activate();
				m_printcontrol.Show();  /* Makes it modal */
				PrintDiagEventArgs args = new PrintDiagEventArgs(0);
				PrintDiagUpdatePreview(null, args);
			}
			else
				m_printcontrol.Show();
			return;
		}

		private void PrintXPS(String file)
		{
			gsprint ghostprint = new gsprint();

			/* We have to create the XPS document on a different thread */
			XpsDocument xpsDocument = new XpsDocument(file, FileAccess.Read);
			FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
			System.Windows.Size temp = new Size(200, 200);
			fixedDocSeq.DocumentPaginator.PageSize = temp;

			PrintQueue printq = m_printcontrol.m_selectedPrinter;

			m_ghostprint = ghostprint;
			xaml_PrintGrid.Visibility = System.Windows.Visibility.Visible;

			xaml_PrintProgress.Value = 0;

			ghostprint.Print(printq, fixedDocSeq, m_printcontrol);
		}

		private void PrintProgress(object printHelper, gsPrintEventArgs Information)
		{
			if (Information.Status != PrintStatus_t.PRINT_BUSY)
			{
				xaml_PrintProgress.Value = 100;
				xaml_PrintGrid.Visibility = System.Windows.Visibility.Collapsed;
			}
			else
			{
				xaml_PrintProgress.Value =
					100.0 * (double)Information.Page / (double)m_num_pages;
			}
		}

		private void CancelMuPDFClick(object sender, RoutedEventArgs e)
		{
			xaml_CancelMuPDF.IsEnabled = false;
			mu_doc.Cancel();
		}

		private void CancelDistillClick(object sender, RoutedEventArgs e)
		{
			xaml_CancelDistill.IsEnabled = false;
			if (m_ghostscript != null)
				m_ghostscript.Cancel();
		}

		private void CancelPrintClick(object sender, RoutedEventArgs e)
		{
			m_ghostprint.CancelAsync();
		}

		private void ShowGSMessage(object sender, RoutedEventArgs e)
		{
			m_gsoutput.Show();
		}

		private void ConvertClick(object sender, RoutedEventArgs e)
		{
			if (m_ghostscript.GetStatus() != gsStatus.GS_READY)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "GS busy");
				return;
			}

			if (m_convertwin == null || !m_convertwin.IsActive)
			{
				m_convertwin = new Convert(m_num_pages);
				m_convertwin.ConvertUpdateMain += new Convert.ConvertCallBackMain(ConvertReturn);
				m_convertwin.Activate();
				m_convertwin.Show();
			}
		}

		private void ConvertReturn(object sender)
		{
			Device device = (Device)m_convertwin.xaml_DeviceList.SelectedItem;
			if (device == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "No Device Selected");
				return;
			} 

			if (m_ghostscript.GetStatus() != gsStatus.GS_READY &&
				!device.MuPDFDevice)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "GS busy");
				return;
			}

			System.Collections.IList pages = m_convertwin.xaml_PageList.SelectedItems;
			System.Collections.IList pages_selected = null;
			String options = m_convertwin.xaml_options.Text;
			int resolution = 72;
			bool multi_page_needed = false;
			int first_page = -1;
			int last_page = -1;

			if (pages.Count == 0)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "No Pages Selected");
				return;
			}

			/* Get a filename */
			System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog();
			dlg.Filter = "All files (*.*)|*.*";
			dlg.FilterIndex = 1;
			if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				if (device.MuPDFDevice)
				{
					/* Allow only one of these as a time */
					pages_selected = pages;
					var val = m_convertwin.xaml_resolution.Text;
					if (val.Length > 0)
					{
						bool isok = true;
						int num = resolution;
						try
						{
							num = System.Convert.ToInt32(val);
						}
						catch (FormatException e)
						{
							isok = false;
							Console.WriteLine("Input string is not a sequence of digits.");
						}
						catch (OverflowException e)
						{
							isok = false;
							Console.WriteLine("The number cannot fit in an Int32.");
						}
						if (isok && num > 0)
							resolution = num;
					}

					if (mu_doc.ConvertSave(device.DeviceType, dlg.FileName,
						pages.Count, pages_selected, resolution) == gsStatus.GS_BUSY)
					{
						ShowMessage(NotifyType_t.MESS_STATUS, "MuPDF conversion busy");
						return;
					}
					xaml_CancelMuPDF.Visibility = System.Windows.Visibility.Visible;
					xaml_MuPDFGrid.Visibility = System.Windows.Visibility.Visible;
				}
				else
				{
					if (!device.SupportsMultiPage && m_num_pages > 1)
						multi_page_needed = true;

					if (pages.Count != m_num_pages)
					{
						/* We may need to go through page by page. Determine if
						 * selection of pages is continuous.  This is done by 
						 * looking at the first one in the list and the last one
						 * in the list and checking the length */
						SelectPage lastpage = (SelectPage)pages[pages.Count - 1];
						SelectPage firstpage = (SelectPage)pages[0];
						int temp = lastpage.Page - firstpage.Page + 1;
						if (temp == pages.Count)
						{
							/* Pages are contiguous.  Add first and last page 
							 * as command line option */
							options = options + " -dFirstPage=" + firstpage.Page + " -dLastPage=" + lastpage.Page;
							first_page = firstpage.Page;
							last_page = lastpage.Page;
						}
						else
						{
							/* Pages are not continguous.  We will do this page 
							 * by page.*/
							pages_selected = pages;
							multi_page_needed = true;  /* need to put in separate outputs */
						}
					}
					xaml_DistillProgress.Value = 0;
					if (m_ghostscript.Convert(m_currfile, options,
						device.DeviceName, dlg.FileName, pages.Count, resolution,
						multi_page_needed, pages_selected, first_page, last_page,
						null, null) == gsStatus.GS_BUSY)
					{
						ShowMessage(NotifyType_t.MESS_STATUS, "GS busy");
						return;
					}
					xaml_DistillName.Text = "GS Converting Document";
					xaml_CancelDistill.Visibility = System.Windows.Visibility.Collapsed;
					xaml_DistillName.FontWeight = FontWeights.Bold;
					xaml_DistillGrid.Visibility = System.Windows.Visibility.Visible;
				}
				m_convertwin.Close();
			}
			return;
		}

		private void ExtractPages(object sender, RoutedEventArgs e)
		{
			if (!m_init_done || m_isXPS || m_isImage)
				return;

			if (m_extractwin == null || !m_extractwin.IsActive)
			{
				m_extractwin = new PageExtractSave(m_num_pages);
				m_extractwin.ExtractMain += new PageExtractSave.ExtractCallBackMain(ExtractReturn);
				m_extractwin.Activate();
				m_extractwin.Show();
			}
		}

		private void ExtractReturn(object sender)
		{
			if (m_extractwin.xaml_PageList.SelectedItems.Count == 0)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "No Pages Selected");
				return;
			}

			/* Go through the actual list not the selected items list. The 
			 * selected items list contains them in the order that the were
			 * selected not the order graphically shown */
			List<SelectPage> pages = new List<SelectPage>(m_extractwin.xaml_PageList.SelectedItems.Count);

			for (int kk = 0; kk < m_extractwin.xaml_PageList.Items.Count; kk++)
			{
				var item = (m_extractwin.xaml_PageList.ItemContainerGenerator.ContainerFromIndex(kk)) as System.Windows.Controls.ListViewItem;
				if (item.IsSelected == true)
				{
					pages.Add((SelectPage) m_extractwin.Pages[kk]);
				}
			}

			/* Get a filename */
			System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog();
			dlg.Filter = "All files (*.pdf)|*.pdf";
			dlg.FilterIndex = 1;
			if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				mu_doc.PDFExtract(m_currfile, dlg.FileName, m_currpassword, m_currpassword != null,
					false, pages.Count, pages);
				m_extractwin.Close();
			}
			return;
		}

		private void GetPassword()
		{
			if (m_password == null)
			{
				m_password = new Password();
				m_password.PassUpdateMain += new Password.PassCallBackMain(PasswordReturn);
				m_password.Activate();
				m_password.Show();
			}
		}

		private void PasswordReturn(object sender)
		{
			if (mu_doc.ApplyPassword(m_password.xaml_Password.Password))
			{
				m_currpassword = m_password.xaml_Password.Password;
				m_password.Close();
				m_password = null;
				xaml_OpenProgressGrid.Visibility = System.Windows.Visibility.Visible;
				xaml_openfilestatus.Text = "Opening File";
				StartViewer();
			}
			else
			{
				xaml_OpenProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
				ShowMessage(NotifyType_t.MESS_STATUS, "Password Incorrect");
			}
		}

		private void ShowInfo(object sender, RoutedEventArgs e)
		{
			String Message;

			if (m_file_open)
			{
				String filename;

				if (m_origfile != null && (m_document_type == DocumentTypes.PS ||
					m_document_type == DocumentTypes.EPS))
					filename = m_origfile;
				else
					filename = m_currfile;
				
				Message =
					"         File: " + filename + "\n" +
					"Document Type: " + m_document_type + "\n" +
					"        Pages: " + m_num_pages + "\n" +
					" Current Page: " + (m_currpage + 1) + "\n";
				if (m_infowindow == null || !(m_infowindow.IsActive))
					m_infowindow = new Info();
				m_infowindow.xaml_TextInfo.Text = Message;
				m_infowindow.FontFamily = new FontFamily("Courier New");
				m_infowindow.Show();
			}
		}

		#region Zoom Control

		/* Find out where the current page is */
		private double ComputeOffsetZoomOut(double old_zoom)
		{
			double y = 0;
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
			{
				/* Look at the offset and where it falls relative to the top of our current page */
				UIElement uiElement = (UIElement)xaml_PageList.ItemContainerGenerator.ContainerFromIndex(m_currpage);
				y = viewer.TranslatePoint(new System.Windows.Point(0, 0), uiElement).Y;
			}
			return y * m_doczoom / old_zoom;
		}

		private double ComputeOffsetZoomIn(double old_zoom, out int new_page)
		{
			double y = 0;
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			new_page = m_currpage;
			if (viewer != null)
			{
				/* Look at the offset and where it falls relative to the top of our current page */
				UIElement uiElement = (UIElement)xaml_PageList.ItemContainerGenerator.ContainerFromIndex(m_currpage);
				y = viewer.TranslatePoint(new System.Windows.Point(0, 0), uiElement).Y;

				/* If we are zoomed out, we can be on a page that is not on the top boundry. See if we can find one
				 * that is */
				if (y < 0)
				{
					new_page = m_currpage - 1;
					while (true)
					{
						if (new_page < 0)
						{
							new_page = 0;
							return 0;
						}
						uiElement = (UIElement)xaml_PageList.ItemContainerGenerator.ContainerFromIndex(new_page);
						y = viewer.TranslatePoint(new System.Windows.Point(0, 0), uiElement).Y;
						if (y >= 0)
						{
							return y * m_doczoom / old_zoom;
						}
						new_page = new_page - 1;
					}
				}
			}
			return y * m_doczoom / old_zoom;
		}

		private void ZoomOut(object sender, RoutedEventArgs e)
		{
			if (!m_init_done || m_doczoom <=  Constants.ZOOM_MIN)
				return;
			double old_zoom = m_doczoom;
			m_doczoom = m_doczoom - Constants.ZOOM_STEP;
			if (m_doczoom < Constants.ZOOM_MIN)
				m_doczoom = Constants.ZOOM_MIN;
			xaml_ZoomSlider.Value = m_doczoom * 100.0;
			double offset = ComputeOffsetZoomOut(old_zoom);
			RenderRange(m_currpage, false, zoom_t.ZOOM_OUT, offset);
		}

		private void ZoomIn(object sender, RoutedEventArgs e)
		{
			if (!m_init_done || m_doczoom >= Constants.ZOOM_MAX)
				return;
			double old_zoom = m_doczoom;
			m_doczoom = m_doczoom + Constants.ZOOM_STEP;
			if (m_doczoom > Constants.ZOOM_MAX)
				m_doczoom = Constants.ZOOM_MAX;
			xaml_ZoomSlider.Value = m_doczoom * 100.0;
			int newpage;
			double offset = ComputeOffsetZoomIn(old_zoom, out newpage);
			RenderRange(newpage, false, zoom_t.ZOOM_IN, offset);
		}

		private void ActualSize(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;
			double old_zoom = m_doczoom;
			m_doczoom = 1.0;
			xaml_ZoomSlider.Value = m_doczoom * 100.0;
			if (old_zoom < 1.0)
			{
				int new_page;
				double offset = ComputeOffsetZoomIn(old_zoom, out new_page);
				RenderRange(new_page, false, zoom_t.ZOOM_IN, offset);
			}
			else if (old_zoom > 1.0)
			{
				double offset = ComputeOffsetZoomOut(old_zoom);
				RenderRange(m_currpage, false, zoom_t.ZOOM_OUT, offset);
			}
		}

		private void ContScrollFill(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;
			/* Scale our pages based upon the size of scrollviewer */
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer == null)
				return;
			double width = viewer.ViewportWidth;
			double page_width = m_thumbnails[m_currpage].NativeWidth;
			double scale = width / page_width;
			if (scale < Constants.ZOOM_MIN)
				scale = Constants.ZOOM_MIN;
			if (scale > Constants.ZOOM_MAX)
				scale = Constants.ZOOM_MAX;
			if (m_doczoom == scale)
				return;
			double old_zoom = m_doczoom;
			m_doczoom = scale;
			xaml_ZoomSlider.Value = m_doczoom * 100.0;
			if (old_zoom > m_doczoom)
				RenderRange(m_currpage, true, zoom_t.ZOOM_OUT, 0);
			else
				RenderRange(m_currpage, true, zoom_t.ZOOM_IN, 0);
		}

		private void ExpandFill(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;
			/* Scale our pages based upon the size of scrollviewer */
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer == null)
				return;
			double height = viewer.ViewportHeight;
			double width = viewer.ViewportWidth;
			double page_height = m_thumbnails[m_currpage].NativeHeight;
			double page_width = m_thumbnails[m_currpage].NativeWidth;
			double height_scale = height / page_height;
			double width_scale = width / page_width;
			double scale = Math.Min(height_scale, width_scale);
			if (scale < Constants.ZOOM_MIN)
				scale = Constants.ZOOM_MIN;
			if (scale > Constants.ZOOM_MAX)
				scale = Constants.ZOOM_MAX;
			if (m_doczoom == scale)
				return;
			double old_zoom = m_doczoom;
			m_doczoom = scale;
			xaml_ZoomSlider.Value = m_doczoom * 100.0;
			if (old_zoom > m_doczoom)
				RenderRange(m_currpage, true, zoom_t.ZOOM_OUT, 0);
			else
				RenderRange(m_currpage, true, zoom_t.ZOOM_IN, 0);
		}

		private void ShowFooter(object sender, RoutedEventArgs e)
		{
			xaml_FooterControl.Visibility = System.Windows.Visibility.Visible;
		}

		private void HideFooter(object sender, RoutedEventArgs e)
		{
			xaml_FooterControl.Visibility = System.Windows.Visibility.Collapsed;
		}

		private void ZoomReleased(object sender, MouseButtonEventArgs e)
		{
			if (m_init_done)
			{
				double zoom = xaml_ZoomSlider.Value / 100.0;
				if (zoom > Constants.ZOOM_MAX)
					zoom = Constants.ZOOM_MAX;
				if (zoom < Constants.ZOOM_MIN)
					zoom = Constants.ZOOM_MIN;
				double old_zoom = zoom;
				m_doczoom = zoom;
				if (old_zoom > m_doczoom)
				{
					double offset = ComputeOffsetZoomOut(old_zoom);
					RenderRange(m_currpage, false, zoom_t.ZOOM_OUT, offset);
				}
				else
				{
					int new_page;
					double offset = ComputeOffsetZoomIn(old_zoom, out new_page);
					RenderRange(new_page, false, zoom_t.ZOOM_IN, offset);
				}
			}
		}

		/* If the zoom is not equalto 1 then set the zoom to 1 and scoll to this page */
		private void PageDoubleClick(object sender, MouseButtonEventArgs e)
		{
			return; /* Disable this for now */
			if (m_doczoom != 1.0)
			{
				double old_zoom = m_doczoom; 
				m_doczoom = 1.0;
				xaml_Zoomsize.Text = "100";
				var item = ((FrameworkElement)e.OriginalSource).DataContext as DocPage;
				if (item != null)
				{
					if (old_zoom > m_doczoom)
					{
						double offset = ComputeOffsetZoomOut(old_zoom);
						RenderRange(m_currpage, false, zoom_t.ZOOM_OUT, offset);
					}
					else
					{
						int new_page;
						double offset = ComputeOffsetZoomIn(old_zoom, out new_page);
						RenderRange(new_page, false, zoom_t.ZOOM_IN, offset);
					}
				}
			}
		}

		private void ZoomEnterClicked(object sender, System.Windows.Input.KeyEventArgs e)
		{
			if (e.Key == Key.Return)
			{
				e.Handled = true;
				var desired_zoom = xaml_Zoomsize.Text;
				try
				{
					double zoom = (double)System.Convert.ToInt32(desired_zoom) / 100.0;
					if (zoom > Constants.ZOOM_MAX)
						zoom = Constants.ZOOM_MAX;
					if (zoom < Constants.ZOOM_MIN)
						zoom = Constants.ZOOM_MIN;
					double old_zoom = m_doczoom;
					m_doczoom = zoom;
					if (old_zoom > m_doczoom)
					{
						double offset = ComputeOffsetZoomOut(old_zoom);
						RenderRange(m_currpage, false, zoom_t.ZOOM_OUT, offset);
					}
					else
					{
						int new_page;
						double offset = ComputeOffsetZoomIn(old_zoom, out new_page);
						RenderRange(new_page, false, zoom_t.ZOOM_IN, offset);
					}
				}
				catch (FormatException e1)
				{
					Console.WriteLine("String is not a sequence of digits.");
				}
				catch (OverflowException e2)
				{
					Console.WriteLine("The number cannot fit in an Int32.");
				}
			}
		}

		/* Rescale the pages based upon the zoom value and the native size */
		private void UpdatePageSizes()
		{
			SetThumbwidth();
			for (int k = 0; k > m_num_pages; k++)
			{
				var thumbpage = m_thumbnails[k];
				var page = m_docPages[k];

				if (page.Zoom == m_doczoom)
					continue;
				int scale_zoom = (int)Math.Round((double)page.Height / (double)thumbpage.NativeHeight);
				if (scale_zoom != m_doczoom)
				{
					page.Height = (int)Math.Round(thumbpage.NativeHeight * m_doczoom);
					page.Width = (int)Math.Round(thumbpage.NativeWidth * m_doczoom);
				}
			}
		}
		#endregion Zoom Control

		#region Thumb Rendering
		void SetThumbInit(int page_num, Byte[] bitmap, Point ras_size, double zoom_in)
		{
			/* Three jobs. Store the thumb and possibly update the full page. Also
			 add to collection of pages.  Set up page geometry info (scale of
			 100 percent ) */

			DocPage doc_page = new DocPage();
			m_thumbnails.Add(doc_page);

			doc_page.Width = (int)ras_size.X;
			
			doc_page.Height = (int)ras_size.Y;
			doc_page.NativeWidth = (int)(ras_size.X / Constants.SCALE_THUMB);
			doc_page.NativeHeight = (int)(ras_size.Y / Constants.SCALE_THUMB);
			m_totalpageheight = m_totalpageheight + doc_page.NativeHeight;

			doc_page.Content = Page_Content_t.THUMBNAIL;
			doc_page.Zoom = zoom_in;
			int stride = doc_page.Width * 4;
			doc_page.BitMap = BitmapSource.Create(doc_page.Width, doc_page.Height, 
				72, 72, PixelFormats.Pbgra32, BitmapPalettes.Halftone256, bitmap, stride);
			doc_page.PageNum = page_num;

			/* Lets see if we need to set the main page */
			var doc = m_docPages[page_num];
			switch (doc.Content)
			{
				case Page_Content_t.FULL_RESOLUTION:
				case Page_Content_t.THUMBNAIL:
					return;
					
				case Page_Content_t.NOTSET:
					doc_page = InitDocPage();
					doc_page.Content = Page_Content_t.THUMBNAIL;
					doc_page.Zoom = zoom_in;
					doc_page.BitMap = m_thumbnails[page_num].BitMap;
					doc_page.Width = (int)(ras_size.X / Constants.SCALE_THUMB);
					doc_page.Height = (int)(ras_size.Y / Constants.SCALE_THUMB);
					doc_page.PageNum = page_num;
					this.m_docPages[page_num] = doc_page;
					break;

				case Page_Content_t.OLD_RESOLUTION:
					return;
			}
		}

		private void ThumbsWork(object sender, DoWorkEventArgs e)
		{
			Point ras_size;
			status_t code;
			double scale_factor = Constants.SCALE_THUMB;
			BackgroundWorker worker = sender as BackgroundWorker;

			Byte[] bitmap;

			for (int k = 0; k < m_num_pages; k++)
			{
				if (ComputePageSize(k, scale_factor, out ras_size) == status_t.S_ISOK)
				{
					try
					{
						bitmap = new byte[(int)ras_size.X * (int)ras_size.Y * 4];
						BlocksText charlist;
						Annotate_t annot;
						/* Synchronous call on our background thread */
						code = (status_t)mu_doc.RenderPage(k, bitmap, (int)ras_size.X,
							(int)ras_size.Y, scale_factor, false, false, false,
							out charlist, false, out annot);
					}
					catch (OutOfMemoryException em)
					{
						Console.WriteLine("Memory allocation failed thumb page " + k + em.Message + "\n");
						break;
					}
					/* Use thumb if we rendered ok */
					if (code == status_t.S_ISOK)
					{
						double percent = 100 * (double)(k + 1) / (double)m_num_pages;
						thumb_t curr_thumb = new thumb_t();
						curr_thumb.page_num = k;
						curr_thumb.bitmap = bitmap;
						curr_thumb.size = ras_size;
						worker.ReportProgress((int)percent, curr_thumb);
					}
				}
				if (worker.CancellationPending == true)
				{
					e.Cancel = true;
					break;
				}
			}
		}

		private void ThumbsCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			xaml_ProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
			xaml_ThumbProgress.Value = 0;
			xaml_ThumbList.ItemsSource = m_thumbnails;
			m_have_thumbs = true;
			m_thumbworker = null;
			//xaml_CancelThumb.IsEnabled = true;
			xaml_ThumbList.Items.Refresh();
			xaml_VerticalScroll.Minimum = 0;
			xaml_VerticalScroll.Maximum = m_totalpageheight + 4 * m_num_pages;
			//thumbSize = (viewportSize/(maximum–minimum+viewportSize))×trackLength
			SetThumbwidth();
			//ScrollBarExtensions.SetThumbLength(xaml_VerticalScroll, 1);
		}

		private void ThumbsProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			thumb_t thumb = (thumb_t)(e.UserState);

			xaml_ThumbProgress.Value = e.ProgressPercentage;
			SetThumbInit(thumb.page_num, thumb.bitmap, thumb.size, 1.0);
		}

		private void RenderThumbs()
		{
			/* Create background task for rendering the thumbnails.  Allow
			this to be cancelled if we open a new doc while we are in loop
			rendering.  Put the UI updates in the progress changed which will
			run on the main thread */
			try
			{
				m_thumbworker = new BackgroundWorker();
				m_thumbworker.WorkerReportsProgress = true;
				m_thumbworker.WorkerSupportsCancellation = true;
				m_thumbworker.DoWork += new DoWorkEventHandler(ThumbsWork);
				m_thumbworker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ThumbsCompleted);
				m_thumbworker.ProgressChanged += new ProgressChangedEventHandler(ThumbsProgressChanged);
				xaml_ProgressGrid.Visibility = System.Windows.Visibility.Visible;
				m_thumbworker.RunWorkerAsync();
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed during thumb rendering\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
		}
		#endregion Thumb Rendering

		#region Copy Paste
		/* Copy the current page as a bmp to the clipboard this is done at the 
		 * current resolution */
		private void CopyPage(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;
			var curr_page = m_docPages[m_currpage];
			System.Windows.Clipboard.SetImage(curr_page.BitMap);
			m_clipboardset = true;
		}

		/* Paste the page to various types supported by the windows encoder class */
		private void PastePage(object sender, RoutedEventArgs e)
		{
			var menu = (System.Windows.Controls.MenuItem)sender;

			String tag = (String)menu.Tag;

			if (!m_clipboardset || !System.Windows.Clipboard.ContainsImage() ||
				!m_init_done)
				return;
			var bitmap = System.Windows.Clipboard.GetImage();

			BitmapEncoder encoder;
			System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog();
			dlg.FilterIndex = 1;

			switch (tag)
			{
				case "PNG":
					dlg.Filter = "PNG Files(*.png)|*.png";
					encoder = new PngBitmapEncoder();

					break;
				case "JPG":
					dlg.Filter = "JPEG Files(*.jpg)|*.jpg";
					encoder = new JpegBitmapEncoder();
					break;

				case "WDP":
					dlg.Filter = "HDP Files(*.wdp)|*.wdp";
					encoder = new WmpBitmapEncoder();
					break;

				case "TIF":
					dlg.Filter = "TIFF Files(*.tif)|*.tif";
					encoder = new TiffBitmapEncoder();
					break;

				case "BMP":
					dlg.Filter = "BMP Files(*.bmp)|*.bmp";
					encoder = new BmpBitmapEncoder();
					break;

				case "GIF":
					dlg.Filter = "GIF Files(*.gif)|*.gif";
					encoder = new GifBitmapEncoder();
					break;

				default:
					return;
			}

			encoder.Frames.Add(BitmapFrame.Create(bitmap));
			if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				using (var stream = dlg.OpenFile())
					encoder.Save(stream);
			}
		}
		#endregion Copy Paste

		#region SaveAs
		String CreatePDFXA(Save_Type_t type)
		{
			Byte[] Resource;
			String Profile;

			switch (type)
			{
				case Save_Type_t.PDFA1_CMYK:
				case Save_Type_t.PDFA2_CMYK:
					Resource = Properties.Resources.PDFA_def;
					Profile = m_outputintents.cmyk_icc;
					break;

				case Save_Type_t.PDFA1_RGB:
				case Save_Type_t.PDFA2_RGB:
					Resource = Properties.Resources.PDFA_def;
					Profile = m_outputintents.rgb_icc;
					break;

				case Save_Type_t.PDFX3_CMYK:
					Resource = Properties.Resources.PDFX_def;
					Profile = m_outputintents.cmyk_icc;
					break;

				case Save_Type_t.PDFX3_GRAY:
					Resource = Properties.Resources.PDFX_def;
					Profile = m_outputintents.gray_icc;
					break;

				default:
					return null;
			}

			String Profile_new = Profile.Replace("\\", "/");
			String result = System.Text.Encoding.UTF8.GetString(Resource);
			String pdfx_cust = result.Replace("ICCPROFILE", Profile_new);
			var out_file = System.IO.Path.GetTempFileName();
			System.IO.File.WriteAllText(out_file, pdfx_cust);
			return out_file;
		}

		private void SaveFile(Save_Type_t type)
		{
			if (!m_file_open)
				return;

			System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog();
			dlg.FilterIndex = 1;

			/* PDF output types */
			if (type <= Save_Type_t.PDF)
			{
				dlg.Filter = "PDF Files(*.pdf)|*.pdf";
				if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				{
					String options = null;
					bool use_gs = true;
					String init_file = CreatePDFXA(type);

					switch (type)
					{
						case Save_Type_t.PDF:
							/* All done.  No need to use gs or mupdf */
							System.IO.File.Copy(m_currfile, dlg.FileName, true);
							use_gs = false;
							break;
						case Save_Type_t.LINEAR_PDF:
							mu_doc.PDFExtract(m_currfile, dlg.FileName, m_currpassword,
								m_currpassword != null, true, -1, null);
							use_gs = false;
							break;
						case Save_Type_t.PDF13:
							options = "-dCompatibilityLevel=1.3";
							break;
						case Save_Type_t.PDFA1_CMYK:
							options = "-dPDFA=1 -dNOOUTERSAVE -dPDFACompatibilityPolicy=1 -sProcessColorModel=DeviceCMYK -dColorConversionStrategy=/CMYK -sOutputICCProfile=" + m_outputintents.cmyk_icc;
							break;
						case Save_Type_t.PDFA1_RGB:
							options = "-dPDFA=1 -dNOOUTERSAVE -dPDFACompatibilityPolicy=1 -sProcessColorModel=DeviceRGB -dColorConversionStrategy=/RGB -sOutputICCProfile=" + m_outputintents.rgb_icc;
							break;
						case Save_Type_t.PDFA2_CMYK:
							options = "-dPDFA=2 -dNOOUTERSAVE -dPDFACompatibilityPolicy=1 -sProcessColorModel=DeviceCMYK -dColorConversionStrategy=/CMYK -sOutputICCProfile=" + m_outputintents.cmyk_icc;
							break;
						case Save_Type_t.PDFA2_RGB:
							options = "-dPDFA=2 -dNOOUTERSAVE -dPDFACompatibilityPolicy=1 -sProcessColorModel=DeviceRGB -dColorConversionStrategy=/RGB -sOutputICCProfile=" + m_outputintents.rgb_icc;
							break;
						case Save_Type_t.PDFX3_CMYK:
							options = "-dPDFX -dNOOUTERSAVE -dPDFACompatibilityPolicy=1 -sProcessColorModel=DeviceCMYK -dColorConversionStrategy=/CMYK -sOutputICCProfile=" + m_outputintents.cmyk_icc;
							break;
						case Save_Type_t.PDFX3_GRAY:
							options = "-dPDFX -dNOOUTERSAVE -dPDFACompatibilityPolicy=1 -sProcessColorModel=DeviceGray -dColorConversionStrategy=/Gray -sOutputICCProfile=" + m_outputintents.cmyk_icc;
							break;

					}
					if (use_gs)
					{
						xaml_DistillProgress.Value = 0;
						if (m_ghostscript.Convert(m_currfile, options,
							Enum.GetName(typeof(gsDevice_t), gsDevice_t.pdfwrite),
							dlg.FileName, m_num_pages, 300, false, null, -1, -1,
							init_file, null) == gsStatus.GS_BUSY)
						{
							ShowMessage(NotifyType_t.MESS_STATUS, "GS busy");
							return;
						}
						xaml_DistillName.Text = "Creating PDF";
						xaml_CancelDistill.Visibility = System.Windows.Visibility.Collapsed;
						xaml_DistillName.FontWeight = FontWeights.Bold;
						xaml_DistillGrid.Visibility = System.Windows.Visibility.Visible;
					}
				}
			}
			else
			{
				/* Non PDF output */
				gsDevice_t Device = gsDevice_t.xpswrite;
				bool use_mupdf = true;
				String Message = "";
				textout_t textout = textout_t.HTML;
				switch (type)
				{
					case Save_Type_t.HTML:
						dlg.Filter = "HTML (*.html)|*.html";
						Message = "HTML content written";
						break;
					case Save_Type_t.XML:
						dlg.Filter = "XML (*.xml)|*.xml";
						Message = "XML content written";
						textout = textout_t.XML;
						break;
					case Save_Type_t.TEXT:
						dlg.Filter = "Text (*.txt)|*.txt";
						Message = "Text content written";
						textout = textout_t.TEXT;
						break;
					case Save_Type_t.PCLXL:
						use_mupdf = false;
						dlg.Filter = "PCL-XL (*.bin)|*.bin";
						Device = gsDevice_t.pxlcolor;
						break;
					case Save_Type_t.XPS:
						use_mupdf = false;
						dlg.Filter = "XPS Files(*.xps)|*.xps";
						break;
				}
				if (!use_mupdf)
				{
					if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
					{
						if (m_ghostscript.Convert(m_currfile, "",
							Enum.GetName(typeof(gsDevice_t), Device),
							dlg.FileName, 1, 300, false, null, -1, -1,
							null, null) == gsStatus.GS_BUSY)
						{
							ShowMessage(NotifyType_t.MESS_STATUS, "GS busy");
							return;
						}
					}
				}
				else
				{
					if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
					{
						/* Write out first non null page then append the rest */
						int curr_page = 0;
						bool done = false;

						while (!done)
						{
							String output = null;
							output = mu_doc.GetText(curr_page, textout);
							if (output == null)
							{
								curr_page = curr_page + 1;
								if (curr_page == m_num_pages)
								{
									ShowMessage(NotifyType_t.MESS_STATUS, "No text found in file");
									return;
								}
							}
							else
							{
								System.IO.File.WriteAllText(dlg.FileName, output);
								done = true;
							}
						}
						curr_page = curr_page + 1;

						if (curr_page == m_num_pages)
						{
							ShowMessage(NotifyType_t.MESS_STATUS, Message);
							return;
						}
						done = false;
						while (!done)
						{
							String output = null;
							output = mu_doc.GetText(curr_page, textout);
							if (output != null)
							{
								System.IO.File.AppendAllText(dlg.FileName, output);
							}
							curr_page = curr_page + 1;
							if (curr_page == m_num_pages)
							{
								ShowMessage(NotifyType_t.MESS_STATUS, Message);
								return;
							}
						}
					}
				}
			}
		}

		private void SaveSVG(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.SVG);
		}

		private void SavePDF(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.PDF);
		}

		private void SaveText(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.TEXT);
		}

		private void SaveXML(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.XML);
		}

		private void SaveHTML(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.HTML);
		}

		private void Linearize(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.LINEAR_PDF);
		}

		private void SavePDF13(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.PDF13);
		}

		private void SavePDFX3_Gray(object sender, RoutedEventArgs e)
		{
			if (m_outputintents.gray_icc == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Set Gray Output Intent ICC Profile");
				return;
			}
			SaveFile(Save_Type_t.PDFX3_GRAY);
		}

		private void SavePDFX3_CMYK(object sender, RoutedEventArgs e)
		{
			if (m_outputintents.cmyk_icc == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Set CMYK Output Intent ICC Profile");
				return;
			}
			SaveFile(Save_Type_t.PDFX3_CMYK);
		}

		private void SavePDFA1_RGB(object sender, RoutedEventArgs e)
		{
			if (m_outputintents.rgb_icc == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Set RGB Output Intent ICC Profile");
				return;
			}
			SaveFile(Save_Type_t.PDFA1_RGB);
		}

		private void SavePDFA1_CMYK(object sender, RoutedEventArgs e)
		{
			if (m_outputintents.cmyk_icc == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Set CMYK Output Intent ICC Profile");
				return;
			}
			SaveFile(Save_Type_t.PDFA1_CMYK);
		}

		private void SavePDFA2_RGB(object sender, RoutedEventArgs e)
		{
			if (m_outputintents.rgb_icc == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Set RGB Output Intent ICC Profile");
				return;
			}
			SaveFile(Save_Type_t.PDFA2_RGB);
		}

		private void SavePDFA2_CMYK(object sender, RoutedEventArgs e)
		{
			if (m_outputintents.cmyk_icc == null)
			{
				ShowMessage(NotifyType_t.MESS_STATUS, "Set CMYK Output Intent ICC Profile");
				return;
			}
			SaveFile(Save_Type_t.PDFA2_CMYK);
		}

		private void SavePCLXL(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.PCLXL);
		}
		private void SaveXPS(object sender, RoutedEventArgs e)
		{
			SaveFile(Save_Type_t.XPS);
		}
		#endregion SaveAs

		#region Extract
		private void Extract(Extract_Type_t type)
		{
			if (m_selection != null || !m_init_done)
				return;

			m_selection = new Selection(m_currpage + 1, m_doczoom, type);
			m_selection.UpdateMain += new Selection.CallBackMain(SelectionMade);
			m_selection.Show();
			m_selection.xaml_Image.Source = m_docPages[m_currpage].BitMap;
			m_selection.xaml_Image.Height = m_docPages[m_currpage].Height;
			m_selection.xaml_Image.Width = m_docPages[m_currpage].Width;
		}

		async private void SelectionZoom(int page_num, double zoom)
		{
			Point ras_size;
			if (ComputePageSize(page_num, zoom, out ras_size) == status_t.S_ISOK)
			{
				try
				{
					Byte[] bitmap = new byte[(int)ras_size.X * (int)ras_size.Y * 4];
					BlocksText charlist;
					Annotate_t annot;

					Task<int> ren_task =
						new Task<int>(() => mu_doc.RenderPage(page_num, bitmap,
							(int)ras_size.X, (int)ras_size.Y, zoom, false, true,
							false, out charlist, true, out annot));
					ren_task.Start();
					await ren_task.ContinueWith((antecedent) =>
					{
						status_t code = (status_t)ren_task.Result;
						if (code == status_t.S_ISOK)
						{
							if (m_selection != null)
							{
								int stride = (int)ras_size.X * 4;
								m_selection.xaml_Image.Source = BitmapSource.Create((int)ras_size.X, (int)ras_size.Y, 72, 72, PixelFormats.Pbgra32, BitmapPalettes.Halftone256, bitmap, stride);
								m_selection.xaml_Image.Height = (int)ras_size.Y;
								m_selection.xaml_Image.Width = (int)ras_size.X;
								m_selection.UpdateRect();
								m_selection.m_curr_state = SelectStatus_t.OK;
							}
						}
					}, TaskScheduler.FromCurrentSynchronizationContext());
				}
				catch (OutOfMemoryException e)
				{
					Console.WriteLine("Memory allocation failed page " + page_num + "\n");
					ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
				}
			}
		}

		private void SelectionMade(object gsObject, SelectEventArgs results)
		{
			switch (results.State)
			{
				case SelectStatus_t.CANCEL:
				case SelectStatus_t.CLOSE:
					m_selection = null;
					return;
				case SelectStatus_t.SELECT:
					/* Get the information we need */
					double zoom = results.ZoomFactor;
					Point start = results.TopLeft;
					Point size = results.Size;
					int page = results.PageNum;
					gsDevice_t Device = gsDevice_t.pdfwrite;

					start.X = start.X / zoom;
					start.Y = start.Y / zoom;
					size.X = size.X / zoom;
					size.Y = size.Y / zoom;

					/* Do the actual extraction */
					String options;
					System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog();
					dlg.FilterIndex = 1;

					/* Get us set up to do a fixed size */
					options = "-dFirstPage=" + page + " -dLastPage=" + page +
						" -dDEVICEWIDTHPOINTS=" + size.X + " -dDEVICEHEIGHTPOINTS=" +
						size.Y + " -dFIXEDMEDIA";

					/* Set up the translation */
					String init_string = "<</Install {-" + start.X + " -" +
						start.Y + " translate (testing) == flush}>> setpagedevice";

					switch (results.Type)
					{
						case Extract_Type_t.PDF:
							dlg.Filter = "PDF Files(*.pdf)|*.pdf";
							break;
						case Extract_Type_t.EPS:
							dlg.Filter = "EPS Files(*.eps)|*.eps";
							Device = gsDevice_t.eps2write;
							break;
						case Extract_Type_t.PS:
							dlg.Filter = "PostScript Files(*.ps)|*.ps";
							Device = gsDevice_t.ps2write;
							break;
						case Extract_Type_t.SVG:
							dlg.Filter = "SVG Files(*.svg)|*.svg";
							break;
					}
					if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
					{
						if (m_ghostscript.Convert(m_currfile, options,
							Enum.GetName(typeof(gsDevice_t), Device),
							dlg.FileName, 1, 300, false, null, page, page,
							null, init_string) == gsStatus.GS_BUSY)
						{
							ShowMessage(NotifyType_t.MESS_STATUS, "GS busy");
							return;
						}
					}
					m_selection.Close();
					break;
				case SelectStatus_t.ZOOMIN:
					/* Render new page at this resolution and hand it off */
					SelectionZoom(results.PageNum - 1, results.ZoomFactor);
					break;
				case SelectStatus_t.ZOOMOUT:
					/* Render new page at this resolution and hand it off */
					SelectionZoom(results.PageNum - 1, results.ZoomFactor);
					break;
			}
		}

		private void ExtractPDF(object sender, RoutedEventArgs e)
		{
			Extract(Extract_Type_t.PDF);
		}
		private void ExtractEPS(object sender, RoutedEventArgs e)
		{
			Extract(Extract_Type_t.EPS);
		}
		private void ExtractPS(object sender, RoutedEventArgs e)
		{
			Extract(Extract_Type_t.PS);
		}
		private void OutputIntents(object sender, RoutedEventArgs e)
		{
			m_outputintents.Show();
		}
		#endregion Extract

		#region Search
		/* Search related code */
		private void Search(object sender, RoutedEventArgs e)
		{
			if (!m_init_done || (m_textsearch != null && m_textsearch.IsBusy))
				return;

			m_textsearch = null; /* Start out fresh */
			if (xaml_SearchControl.Visibility == System.Windows.Visibility.Collapsed)
				xaml_SearchControl.Visibility = System.Windows.Visibility.Visible;
			else
			{
				xaml_SearchControl.Visibility = System.Windows.Visibility.Collapsed;
				xaml_SearchGrid.Visibility = System.Windows.Visibility.Collapsed;
				ClearTextSearch();
			}
		}

		private void OnSearchBackClick(object sender, RoutedEventArgs e)
		{
			String textToFind = xaml_SearchText.Text;
			TextSearchSetUp(-1, textToFind);
		}

		private void OnSearchForwardClick(object sender, RoutedEventArgs e)
		{
			String textToFind = xaml_SearchText.Text;
			TextSearchSetUp(1, textToFind);
		}

		/* The thread that is actually doing the search work */
		void SearchWork(object sender, DoWorkEventArgs e)
		{
			BackgroundWorker worker = sender as BackgroundWorker;
			List<object> genericlist = e.Argument as List<object>;
			int direction = (int)genericlist[0];
			String needle = (String)genericlist[1];
			/* To make sure we get the next page or current page during search */
			int in_search = (int)genericlist[2];
			m_searchpage = m_currpage + direction * in_search;
			searchResults_t results = new searchResults_t();

			/* Break if we find something, get to the end (or start of doc) 
			 * or if we have a cancel occur */
			while (true)
			{
				int box_count = mu_doc.TextSearchPage(m_searchpage, needle);
				int percent;

				if (direction == 1)
					percent = (int)(100.0 * ((double)m_searchpage + 1) / (double)m_num_pages);
				else
					percent = 100 - (int)(100.0 * ((double)m_searchpage) / (double)m_num_pages);

				if (box_count > 0)
				{
					/* This page has something lets go ahead and extract and 
					 * signal to the UI thread and end this thread */
					results.done = false;
					results.num_rects = box_count;
					results.page_found = m_searchpage;
					results.rectangles = new List<Rect>();

					for (int kk = 0; kk < box_count; kk++)
					{
						Point top_left;
						Size size;
						mu_doc.GetTextSearchItem(kk, out top_left, out size);
						var rect = new Rect(top_left, size);
						results.rectangles.Add(rect);
					}
					/* Reset global smart pointer once we have everything */
					mu_doc.ReleaseTextSearch();
					worker.ReportProgress(percent, results);
					break;
				}
				else
				{
					/* This page has nothing.  Lets go ahead and just update
					 * the progress bar */
					worker.ReportProgress(percent, null);
					if (percent >= 100)
					{
						results.done = true;
						results.needle = needle;
						break;
					}
					m_searchpage = m_searchpage + direction;
				}
				if (worker.CancellationPending == true)
				{
					e.Cancel = true;
					break;
				}
			}
			e.Result = results;
		}

		private void SearchProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			if (e.UserState == null)
			{
				/* Nothing found */
				xaml_SearchProgress.Value = e.ProgressPercentage;
			}
			else
			{
				m_text_list = new List<RectList>();
				/* found something go to page and show results */
				searchResults_t results = (searchResults_t)e.UserState;
				xaml_SearchProgress.Value = e.ProgressPercentage;
				m_currpage = results.page_found;
				/* Add in the rectangles */
				for (int kk = 0; kk < results.num_rects; kk++)
				{
					var rect_item = new RectList();
					rect_item.Scale = m_doczoom;
					rect_item.Color = m_textsearchcolor;
					rect_item.Height = results.rectangles[kk].Height * m_doczoom;
					rect_item.Width = results.rectangles[kk].Width * m_doczoom;
					rect_item.X = results.rectangles[kk].X * m_doczoom;
					rect_item.Y = results.rectangles[kk].Y * m_doczoom;
					rect_item.Index = kk.ToString();
					m_text_list.Add(rect_item);
				}
				m_docPages[results.page_found].TextBox = m_text_list;
				m_doscroll = true;
				xaml_PageList.ScrollIntoView(m_docPages[results.page_found]);
			}
		}

		private void SearchCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (e.Cancelled == true)
			{
				xaml_SearchGrid.Visibility = System.Windows.Visibility.Collapsed;
				m_textsearch = null;
			}
			else
			{
				searchResults_t results = (searchResults_t)e.Result;
				if (results.done == true)
				{
					xaml_SearchGrid.Visibility = System.Windows.Visibility.Collapsed;
					m_textsearch = null;
					ShowMessage(NotifyType_t.MESS_STATUS, "End of document search for \"" + results.needle + "\"");
				}
			}
		}

		private void CancelSearchClick(object sender, RoutedEventArgs e)
		{
			if (m_textsearch != null && m_textsearch.IsBusy)
				m_textsearch.CancelAsync();
			xaml_SearchGrid.Visibility = System.Windows.Visibility.Collapsed;
			m_textsearch = null;
			ClearTextSearch();
		}

		private void ClearTextSearch()
		{
			for (int kk = 0; kk < m_num_pages; kk++)
			{
				var temp = m_docPages[kk].TextBox;
				if (temp != null)
				{
					m_docPages[kk].TextBox = null;
				}
			}
		}

		private void ScaleTextBox(int pagenum)
		{
			var temp = m_docPages[pagenum].TextBox;
			for (int kk = 0; kk < temp.Count; kk++)
			{
				var rect_item = temp[kk];
				double factor = m_doczoom / temp[kk].Scale;

				temp[kk].Height = temp[kk].Height * factor;
				temp[kk].Width = temp[kk].Width * factor;
				temp[kk].X = temp[kk].X * factor;
				temp[kk].Y = temp[kk].Y * factor;

				temp[kk].Scale = m_doczoom;
				temp[kk].PageRefresh();
			}
			m_docPages[pagenum].TextBox = temp;
		}

		private void TextSearchSetUp(int direction, String needle)
		{
			/* Create background task for performing text search. */
			try
			{
				int in_text_search = 0;

				if (m_textsearch != null && m_textsearch.IsBusy)
					return;

				if (m_textsearch != null)
				{
					in_text_search = 1;
					m_textsearch = null;
				}
				if (m_prevsearch != null && needle != m_prevsearch)
				{
					in_text_search = 0;
					ClearTextSearch();
				}

				if (m_textsearch == null)
				{
					m_prevsearch = needle;
					m_textsearch = new BackgroundWorker();
					m_textsearch.WorkerReportsProgress = true;
					m_textsearch.WorkerSupportsCancellation = true;
					var arguments = new List<object>();
					arguments.Add(direction);
					arguments.Add(needle);
					arguments.Add(in_text_search);
					m_textsearch.DoWork += new DoWorkEventHandler(SearchWork);
					m_textsearch.RunWorkerCompleted += new RunWorkerCompletedEventHandler(SearchCompleted);
					m_textsearch.ProgressChanged += new ProgressChangedEventHandler(SearchProgressChanged);
					xaml_SearchGrid.Visibility = System.Windows.Visibility.Visible;
					m_textsearch.RunWorkerAsync(arguments);
				}
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed during text search\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
		}
		#endregion Search

		#region Link
		private void LinksToggle(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;

			m_links_on = !m_links_on;

			if (m_page_link_list == null)
			{
				if (m_linksearch != null && m_linksearch.IsBusy)
					return;

				m_page_link_list = new List<List<RectList>>();
				m_linksearch = new BackgroundWorker();
				m_linksearch.WorkerReportsProgress = false;
				m_linksearch.WorkerSupportsCancellation = true;
				m_linksearch.DoWork += new DoWorkEventHandler(LinkWork);
				m_linksearch.RunWorkerCompleted += new RunWorkerCompletedEventHandler(LinkCompleted);
				m_linksearch.RunWorkerAsync();
			}
			else
			{
				if (m_links_on)
					LinksOn();
				else
					LinksOff();
			}
		}

		private void LinkWork(object sender, DoWorkEventArgs e)
		{
			BackgroundWorker worker = sender as BackgroundWorker;

			for (int k = 0; k < m_num_pages; k++)
			{
				int box_count = mu_doc.GetLinksPage(k);
				List<RectList> links = new List<RectList>();
				if (box_count > 0)
				{
					for (int j = 0; j < box_count; j++)
					{
						Point top_left;
						Size size;
						String uri;
						int type;
						int topage;

						mu_doc.GetLinkItem(j, out top_left, out size, out uri,
							out topage, out type);
						var rectlist = new RectList();
						rectlist.Height = size.Height * m_doczoom;
						rectlist.Width = size.Width * m_doczoom;
						rectlist.X = top_left.X * m_doczoom;
						rectlist.Y = top_left.Y * m_doczoom;
						rectlist.Color = m_linkcolor;
						rectlist.Index = k.ToString() + "." + j.ToString();
						rectlist.PageNum = topage;
						rectlist.Scale = m_doczoom;
						if (uri != null)
							rectlist.Urilink = new Uri(uri);
						rectlist.Type = (Link_t)type;
						links.Add(rectlist);
					}
				}
				mu_doc.ReleaseLink();
				m_page_link_list.Add(links);

				if (worker.CancellationPending == true)
				{
					e.Cancel = true;
					break;
				}
			}
		}

		private void LinkCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			LinksOn();
		}

		private void ScaleLinkBox(int pagenum)
		{
			var temp = m_docPages[pagenum].LinkBox;
			for (int kk = 0; kk < temp.Count; kk++)
			{
				var rect_item = temp[kk];
				double factor = m_doczoom / temp[kk].Scale;

				temp[kk].Height = temp[kk].Height * factor;
				temp[kk].Width = temp[kk].Width * factor;
				temp[kk].X = temp[kk].X * factor;
				temp[kk].Y = temp[kk].Y * factor;

				temp[kk].Scale = m_doczoom;
				temp[kk].PageRefresh();
			}
			m_docPages[pagenum].LinkBox = temp;
		}
		/* Merge these */
		private void ScaleTextLines(int pagenum, double scale_factor)
		{
			var temp = m_lineptrs[pagenum];
			for (int kk = 0; kk < temp.Count; kk++)
			{
				var rect_item = temp[kk];
				double factor = scale_factor / temp[kk].Scale;

				temp[kk].Height = temp[kk].Height * factor;
				temp[kk].Width = temp[kk].Width * factor;
				temp[kk].X = temp[kk].X * factor;
				temp[kk].Y = temp[kk].Y * factor;

				temp[kk].Scale = scale_factor;
			}
			m_lineptrs[pagenum] = temp;
		}

		private void ScaleTextBlocks(int pagenum, double scale_factor)
		{
			var temp = m_textptrs[pagenum];
			for (int kk = 0; kk < temp.Count; kk++)
			{
				var rect_item = temp[kk];
				double factor = scale_factor / temp[kk].Scale;

				temp[kk].Height = temp[kk].Height * factor;
				temp[kk].Width = temp[kk].Width * factor;
				temp[kk].X = temp[kk].X * factor;
				temp[kk].Y = temp[kk].Y * factor;

				temp[kk].Scale = scale_factor;
			}
			m_textptrs[pagenum] = temp;
		}

		private int GetVisibleRange()
		{
			/* Need to figure out what pages are going to be visible */
			double bottom = this.ActualHeight;
			bool done = false;
			int final_page = m_currpage;
			double count = 0;

			while (!done)
			{
				count = count + m_thumbnails[final_page].NativeHeight * m_doczoom;
				final_page = final_page + 1;
				if (final_page == m_num_pages || count > bottom)
					done = true;
			}
			return final_page;
		}

		/* Only visible pages */
		private void LinksOff()
		{
			int final_page = GetVisibleRange();
			for (int kk = m_currpage - 1; kk <= final_page + 1; kk++)
			{
				var temp = m_docPages[kk].LinkBox;
				if (temp != null)
				{
					m_docPages[kk].LinkBox = null;
				}
			}
		}

		/* Only visible pages */
		private void LinksOn()
		{
			int final_page = GetVisibleRange();
			for (int kk = m_currpage - 1; kk <= final_page + 1; kk++)
			{
				if (!(kk < 0 || kk > m_num_pages - 1))
				{
					var temp = m_docPages[kk].LinkBox;
					if (temp == null)
					{
						m_docPages[kk].LinkBox = m_page_link_list[kk];
					}
				}
			}
		}

		private void LinkClick(object sender, MouseButtonEventArgs e)
		{
			var item = (Rectangle)sender;

			if (item == null)
				return;

			String tag = (String)item.Tag;
			int page = 0;
			int index = 0;

			if (tag == null || tag.Length < 3 || !(tag.Contains('.')))
				return;

			String[] parts = tag.Split('.');
			try
			{
				page = System.Convert.ToInt32(parts[0]);
				index = System.Convert.ToInt32(parts[1]);

			}
			catch (FormatException e1)
			{
				Console.WriteLine("String is not a sequence of digits.");
			}
			catch (OverflowException e2)
			{
				Console.WriteLine("The number cannot fit in an Int32.");
			}

			if (index >= 0 && index < m_num_pages && page >= 0 && page < m_num_pages)
			{
				var link_list = m_page_link_list[page];
				var link = link_list[index];

				if (link.Type == Link_t.LINK_GOTO)
				{
					if (m_currpage != link.PageNum && link.PageNum >= 0 &&
						link.PageNum < m_num_pages)
						RenderRange(link.PageNum, true, zoom_t.NO_ZOOM, 0);
				}
				else if (link.Type == Link_t.LINK_URI)
					System.Diagnostics.Process.Start(link.Urilink.AbsoluteUri);
			}
		}
		#endregion Link

		#region TextSelection

		/* Change cursor if we are over text block */
		private void ExitTextBlock(object sender, System.Windows.Input.MouseEventArgs e)
		{
			this.Cursor = System.Windows.Input.Cursors.Arrow;
		}

		private void EnterTextBlock(object sender, System.Windows.Input.MouseEventArgs e)
		{
			this.Cursor = System.Windows.Input.Cursors.IBeam;
		}

		private void ClearSelections()
		{
			for (int kk = 0; kk < m_textSelect.Count; kk++)
			{
				m_lineptrs[m_textSelect[kk].pagenum].Clear();
				if (m_docPages[m_textSelect[kk].pagenum].SelectedLines != null)
					m_docPages[m_textSelect[kk].pagenum].SelectedLines.Clear();
			}
			m_textSelect.Clear();
			m_textselected = false;
			m_selectall = false;
			SetSelectAll(m_blockcolor);
		}

		private void InitTextSelection(DocPage page)
		{
			if (m_textSelect != null)
				ClearSelections();
			else
				m_textSelect = new List<textSelectInfo_t>();

			m_intxtselect = true;

			textSelectInfo_t selinfo = new textSelectInfo_t();
			selinfo.pagenum = page.PageNum;
			selinfo.first_line_full = false;
			selinfo.last_line_full = false;
			m_textSelect.Add(selinfo);
		}

		private void PageMouseDown(object sender, MouseButtonEventArgs e)
		{
			if (this.Cursor != System.Windows.Input.Cursors.IBeam)
				return;

			var page = ((FrameworkElement)e.Source).DataContext as DocPage;
			Canvas can = ((FrameworkElement)e.Source).Parent as Canvas;
			if (page == null || can == null)
				return;

			InitTextSelection(page);
			var posit = e.GetPosition(can);

			page.SelX = posit.X;
			page.SelY = posit.Y;
			page.SelAnchorX = posit.X;
			page.SelAnchorY = posit.Y;
			page.SelColor = m_regionselect;

			/* Create new holder for lines highlighted */
			m_lineptrs[page.PageNum] = new LinesText();
		}

		private void PageMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
		{
			if (e.LeftButton == MouseButtonState.Released || m_intxtselect == false)
				return;

			var page = ((FrameworkElement)e.Source).DataContext as DocPage;
			Canvas can = ((FrameworkElement)e.Source).Parent as Canvas;
			if (page == null || can == null)
				return;
			if (page.PageNum < 0)
				return;
			/* Store the location of our most recent page in case we exit window */
			var pos = e.GetPosition(can);
			m_lastY = pos.Y;
			m_maxY = can.Height;
			/* Don't allow the listview to maintain control of the mouse, we need
			 * to detect if we leave the window */
			/* Make sure page is rendered */
			if (page.Content != Page_Content_t.FULL_RESOLUTION ||
				page.Zoom != m_doczoom)
			{
				RenderRange(page.PageNum, false, zoom_t.NO_ZOOM, 0);
			}

			UpdateSelection(pos, page);
		}

		/* Resize selection rect */
		private void UpdateSelection(System.Windows.Point pos, DocPage page)
		{
			bool new_page = true;
			TextLine start_line, end_line;
			double x = 0, y, w = 0, h;
			bool found_first = false;
			bool above_anchor = true;
			bool first_line_full = false;
			bool last_line_full = false;

			for (int kk = 0; kk < m_textSelect.Count; kk++)
				if (m_textSelect[kk].pagenum == page.PageNum)
					new_page = false;

			/* See if we have gone back to a previous page */
			if (!new_page && page.PageNum != m_textSelect[m_textSelect.Count - 1].pagenum)
			{
				DocPage curr_page = m_docPages[m_textSelect[m_textSelect.Count - 1].pagenum];
				curr_page.SelHeight = 0;
				curr_page.SelWidth = 0;
				m_textSelect.RemoveAt(m_textSelect.Count - 1);
				m_lineptrs[curr_page.PageNum].Clear();
				curr_page.SelectedLines.Clear();
			}
			if (new_page)
			{
				/* New page */
				page.SelX = pos.X;
				page.SelY = pos.Y;
				page.SelAnchorX = m_docPages[m_textSelect[m_textSelect.Count - 1].pagenum].SelAnchorX;
				if (m_textSelect[m_textSelect.Count - 1].pagenum > page.PageNum)
				{
					page.SelAnchorY = page.Height;
				}
				else
				{
					page.SelAnchorY = 0;
				}
				page.SelColor = m_regionselect;
				textSelectInfo_t info = new textSelectInfo_t();
				info.pagenum = page.PageNum;
				info.first_line_full = false;
				info.last_line_full = false;
				m_textSelect.Add(info);
				/* Create new holder for lines highlighted */
				m_lineptrs[page.PageNum] = new LinesText();
			}

			if (page.TextBlocks == null || page.TextBlocks.Count == 0)
				return;

			/* Width changes translate across the pages */
			for (int jj = 0; jj < m_textSelect.Count; jj++)
			{
				DocPage curr_page = m_docPages[m_textSelect[jj].pagenum];
				x = Math.Min(pos.X, curr_page.SelAnchorX);
				w = Math.Max(pos.X, curr_page.SelAnchorX) - x;
				curr_page.SelX = x;
				curr_page.SelWidth = w;
			}
			/* Height is just the current page */
			y = Math.Min(pos.Y, page.SelAnchorY);
			h = Math.Max(pos.Y, page.SelAnchorY) - y;

			/* Determine if we are going up or down */
			if (pos.Y > page.SelAnchorY)
				above_anchor = false;
			page.SelY = y;
			page.SelHeight = h;

			/* Clear out what we currently have */
			m_lineptrs[page.PageNum].Clear();

			/* Stuff already selected above us */
			if (m_textSelect.Count > 1)
				found_first = true;
			/* Moving backwards through pages */
			if (m_textSelect.Count > 1 && m_textSelect[m_textSelect.Count - 2].pagenum > page.PageNum)
				found_first = false;

			for (int jj = 0; jj < page.TextBlocks.Count; jj++)
			{
				/* Text blocks are already scaled. Lines are not */
				var intersect_blk = page.TextBlocks[jj].CheckIntersection(x, y, w, h);
				var lines = page.TextBlocks[jj].TextLines;

				if (intersect_blk == Intersection_t.FULL)
				{
					/* Just add all the lines for this block */
					for (int kk = 0; kk < lines.Count; kk++)
						m_lineptrs[page.PageNum].Add(lines[kk]);
					if (jj == 0)
					{
						first_line_full = true;
						found_first = true;
					}
					if (jj == page.TextBlocks.Count - 1)
						last_line_full = true;
				}
				else if (intersect_blk != Intersection_t.NONE)
				{
					/* Now go through the lines */
					for (int kk = 0; kk < lines.Count; kk++)
					{
						double scale = m_doczoom / lines[kk].Scale;
						//var intersect_line = lines[kk].CheckIntersection(x * scale, y * scale, w * scale, h * scale);
						var intersect_line = lines[kk].CheckIntersection(x / scale , y / scale , w / scale , h / scale);
						if (intersect_line == Intersection_t.FULL)
						{
							m_lineptrs[page.PageNum].Add(lines[kk]);
							found_first = true;
							if (jj == 0 && kk == 0)
								first_line_full = true;
							if (jj == page.TextBlocks.Count - 1 && 
								kk == lines.Count - 1)
								last_line_full = true;

						}
						else if (intersect_line == Intersection_t.PARTIAL)
						{
							double val;
							var lett = lines[kk].TextCharacters;

							/* Now go through the width. */
							if (found_first)
							{
								if (above_anchor)
									val = page.SelAnchorX;
								else
									val = pos.X;

								/* our second partial line */
								if (val > lines[kk].X * scale + lines[kk].Width * scale)
									m_lineptrs[page.PageNum].Add(lines[kk]);
								else
								{
									/* Use either anchor point or mouse pos */
									end_line = new TextLine();
									end_line.TextCharacters = new List<TextCharacter>();
									end_line.Height = 0;
									end_line.Scale = m_doczoom;
									for (int mm = 0; mm < lett.Count; mm++)
									{
										double letscale = m_doczoom / lett[mm].Scale;
										if (lett[mm].X * letscale < val)
										{
											/* Can set to special color for debug */
											end_line.Color = m_textselectcolor;
											/* special color for debug */
											//end_line.Color = "#4000FF00";
											end_line.Height = lines[kk].Height * scale;
											end_line.Width = lett[mm].X * letscale + lett[mm].Width * letscale - lines[kk].X * scale;
											end_line.Y = lines[kk].Y * scale;
											end_line.X = lines[kk].X * scale;
											end_line.TextCharacters.Add(lett[mm]);
										}
										else
											break;
									}
									if (end_line.Height != 0)
										m_lineptrs[page.PageNum].Add(end_line);
								}
							}
							else
							{
								if (!above_anchor)
									val = page.SelAnchorX;
								else
									val = pos.X;

								/* our first partial line */
								found_first = true;
								if (val < lines[kk].X * scale)
									m_lineptrs[page.PageNum].Add(lines[kk]);
								else
								{
									start_line = new TextLine();
									start_line.TextCharacters = new List<TextCharacter>();
									start_line.Height = 0;
									start_line.Scale = m_doczoom;
									/* Use either anchor point or mouse pos */
									bool highlight_done = false;
									for (int mm = 0; mm < lett.Count; mm++)
									{
										double letscale = m_doczoom / lett[mm].Scale;
										if (lett[mm].X * letscale + lett[mm].Width * letscale >= val)
										{
											/* In this case, we are done with the 
											 * highlight section as it only
											 * depends upon the first character
											 * we encounter and the line end. 
											 * But we must continue to add in 
											 * the selected characters */
											if (!highlight_done)
											{
												start_line.Color = m_textselectcolor;
												/* special color for debug */
												/* start_line.Color = "#40FF0000"; */
												start_line.Height = lines[kk].Height * scale;
												start_line.Width = lines[kk].X * scale + lines[kk].Width * scale - lett[mm].X * letscale;
												start_line.X = lett[mm].X * letscale;
												start_line.Y = lines[kk].Y * scale;
												highlight_done = true;
											}
											start_line.TextCharacters.Add(lett[mm]);
										}
									}
									if (start_line.Height > 0)
										m_lineptrs[page.PageNum].Add(start_line);
								}
							}
						}
					}
				}
			}
			var txtsel = m_textSelect[m_textSelect.Count - 1];
			txtsel.first_line_full = first_line_full;
			txtsel.last_line_full = last_line_full;
			m_textSelect[m_textSelect.Count - 1] = txtsel;

			/* Adjust for scale before assigning */
			var temp = m_lineptrs[page.PageNum];
			for (int kk = 0; kk < temp.Count; kk++)
			{
				var rect_item = temp[kk];
				double factor = m_doczoom / rect_item.Scale;

				temp[kk].Height = temp[kk].Height * factor;
				temp[kk].Width = temp[kk].Width * factor;
				temp[kk].X = temp[kk].X * factor;
				temp[kk].Y = temp[kk].Y * factor;

				temp[kk].Scale = m_doczoom;
			}
			page.SelectedLines = m_lineptrs[page.PageNum];
		}

		/* A fix for handling column cases TODO FIXME */
		private void UpdateSelectionCol(System.Windows.Point pos, DocPage page)
		{
			bool new_page = true;
			TextLine start_line, end_line;
			double x = 0, y, w = 0, h;
			bool found_first = false;
			bool above_anchor = true;
			bool first_line_full = false;
			bool last_line_full = false;

			for (int kk = 0; kk < m_textSelect.Count; kk++)
				if (m_textSelect[kk].pagenum == page.PageNum)
					new_page = false;

			/* See if we have gone back to a previous page */
			if (!new_page && page.PageNum != m_textSelect[m_textSelect.Count - 1].pagenum)
			{
				DocPage curr_page = m_docPages[m_textSelect[m_textSelect.Count - 1].pagenum];
				curr_page.SelHeight = 0;
				curr_page.SelWidth = 0;
				m_textSelect.RemoveAt(m_textSelect.Count - 1);
				m_lineptrs[curr_page.PageNum].Clear();
				curr_page.SelectedLines.Clear();
			}
			if (new_page)
			{
				/* New page */
				page.SelX = pos.X;
				page.SelY = pos.Y;
				page.SelAnchorX = m_docPages[m_textSelect[m_textSelect.Count - 1].pagenum].SelAnchorX;
				if (m_textSelect[m_textSelect.Count - 1].pagenum > page.PageNum)
				{
					page.SelAnchorY = page.Height;
				}
				else
				{
					page.SelAnchorY = 0;
				}
				page.SelColor = m_regionselect;
				textSelectInfo_t info = new textSelectInfo_t();
				info.pagenum = page.PageNum;
				info.first_line_full = false;
				info.last_line_full = false;
				m_textSelect.Add(info);
				/* Create new holder for lines highlighted */
				m_lineptrs[page.PageNum] = new LinesText();
			}

			if (page.TextBlocks == null || page.TextBlocks.Count == 0)
				return;

			/* Width changes translate across the pages */
			for (int jj = 0; jj < m_textSelect.Count; jj++)
			{
				DocPage curr_page = m_docPages[m_textSelect[jj].pagenum];
				x = Math.Min(pos.X, curr_page.SelAnchorX);
				w = Math.Max(pos.X, curr_page.SelAnchorX) - x;
				curr_page.SelX = x;
				curr_page.SelWidth = w;
			}
			/* Height is just the current page */
			y = Math.Min(pos.Y, page.SelAnchorY);
			h = Math.Max(pos.Y, page.SelAnchorY) - y;

			/* Determine if we are going up or down */
			if (pos.Y > page.SelAnchorY)
				above_anchor = false;
			page.SelY = y;
			page.SelHeight = h;

			/* Clear out what we currently have */
			m_lineptrs[page.PageNum].Clear();

			/* Stuff already selected above us */
			if (m_textSelect.Count > 1)
				found_first = true;
			/* Moving backwards through pages */
			if (m_textSelect.Count > 1 && m_textSelect[m_textSelect.Count - 2].pagenum > page.PageNum)
				found_first = false;

			/* To properly handle the multiple columns we have to find the last 
			 * line and make sure that all blocks between our first and last
			 * line are included. To do this we do an initial step through the
			 * blocks looking at our intersections */
			int first_block = -1;
			int last_block = -1;
			for (int jj = 0; jj < page.TextBlocks.Count; jj++ )
			{
				var intersect_blk = page.TextBlocks[jj].CheckIntersection(x, y, w, h);
				if (intersect_blk == Intersection_t.NONE && first_block != -1)
				{
					last_block = jj; /* NB: this is just past last block */
					break;
				}
				else if (intersect_blk != Intersection_t.NONE && first_block == -1)
					first_block = jj; /* NB: this is the first block */
			}
			if (first_block == -1)
				return;
			if (last_block == -1)
			{
				/* Only 1 block */
				last_block = first_block + 1;
			}

			for (int jj = first_block; jj < last_block; jj++)
			{
				/* Text blocks are already scaled. Lines are not */
				var intersect_blk = page.TextBlocks[jj].CheckIntersection(x, y, w, h);
				var lines = page.TextBlocks[jj].TextLines;

				if (jj == first_block || jj == last_block - 1)
				{
					/* Partial cases */
					if (intersect_blk == Intersection_t.FULL)
					{
						for (int kk = 0; kk < lines.Count; kk++)
							m_lineptrs[page.PageNum].Add(lines[kk]);
						if (jj == first_block)
						{
							first_line_full = true;
							found_first = true;
						}
						if (jj == last_block - 1)
						{
							last_line_full = true;
						}
					}
					else if (intersect_blk == Intersection_t.PARTIAL)
					{
						for (int kk = 0; kk < lines.Count; kk++)
						{
							double scale = m_doczoom / lines[kk].Scale;
							var intersect_line = lines[kk].CheckIntersection(x * scale, y * scale, w * scale, h * scale);
							if (intersect_line == Intersection_t.FULL)
							{
								m_lineptrs[page.PageNum].Add(lines[kk]);
								found_first = true;
								if (jj == 0 && kk == 0)
									first_line_full = true;
								if (jj == page.TextBlocks.Count - 1 &&
									kk == lines.Count - 1)
									last_line_full = true;

							}
							else if (intersect_line == Intersection_t.PARTIAL)
							{
								double val;
								var lett = lines[kk].TextCharacters;

								/* Now go through the width. */
								if (found_first)
								{
									if (above_anchor)
										val = page.SelAnchorX;
									else
										val = pos.X;

									/* our second partial line */
									if (val > lines[kk].X * scale + lines[kk].Width * scale)
										m_lineptrs[page.PageNum].Add(lines[kk]);
									else
									{
										/* Use either anchor point or mouse pos */
										end_line = new TextLine();
										end_line.TextCharacters = new List<TextCharacter>();
										end_line.Height = 0;
										end_line.Scale = m_doczoom;
										for (int mm = 0; mm < lett.Count; mm++)
										{
											double letscale = m_doczoom / lett[mm].Scale;
											if (lett[mm].X * letscale < val)
											{
												/* Can set to special color for debug */
												end_line.Color = m_textselectcolor;
												/* special color for debug */
												//end_line.Color = "#4000FF00";
												end_line.Height = lines[kk].Height * scale;
												end_line.Width = lett[mm].X * letscale + lett[mm].Width * letscale - lines[kk].X * scale;
												end_line.Y = lines[kk].Y * scale;
												end_line.X = lines[kk].X * scale;
												end_line.TextCharacters.Add(lett[mm]);
											}
											else
												break;
										}
										if (end_line.Height != 0)
											m_lineptrs[page.PageNum].Add(end_line);
									}
								}
								else
								{
									if (!above_anchor)
										val = page.SelAnchorX;
									else
										val = pos.X;

									/* our first partial line */
									found_first = true;
									if (val < lines[kk].X * scale)
										m_lineptrs[page.PageNum].Add(lines[kk]);
									else
									{
										start_line = new TextLine();
										start_line.TextCharacters = new List<TextCharacter>();
										start_line.Height = 0;
										start_line.Scale = m_doczoom;
										/* Use either anchor point or mouse pos */
										for (int mm = 0; mm < lett.Count; mm++)
										{
											double letscale = m_doczoom / lett[mm].Scale;
											if (lett[mm].X * letscale + lett[mm].Width * letscale >= val)
											{
												start_line.Color = m_textselectcolor;
												/* special color for debug */
												//start_line.Color = "#40FF0000";
												start_line.Height = lines[kk].Height * scale;
												start_line.Width = lines[kk].X * scale + lines[kk].Width * scale - lett[mm].X * letscale;
												start_line.X = lett[mm].X * letscale;
												start_line.Y = lines[kk].Y * scale;
												start_line.TextCharacters.Add(lett[mm]);
												break;
											}
										}
										if (start_line.Height > 0)
											m_lineptrs[page.PageNum].Add(start_line);
									}
								}
							}
						}
					}
				}
				else
				{
					/* Add all the lines for the blocks between the first and last */
					for (int kk = 0; kk < lines.Count; kk++)
						m_lineptrs[page.PageNum].Add(lines[kk]);
				}
			}

			var txtsel = m_textSelect[m_textSelect.Count - 1];
			txtsel.first_line_full = first_line_full;
			txtsel.last_line_full = last_line_full;
			m_textSelect[m_textSelect.Count - 1] = txtsel;

			/* Adjust for scale before assigning */
			var temp = m_lineptrs[page.PageNum];
			for (int kk = 0; kk < temp.Count; kk++)
			{
				var rect_item = temp[kk];
				double factor = m_doczoom / rect_item.Scale;

				temp[kk].Height = temp[kk].Height * factor;
				temp[kk].Width = temp[kk].Width * factor;
				temp[kk].X = temp[kk].X * factor;
				temp[kk].Y = temp[kk].Y * factor;

				temp[kk].Scale = m_doczoom;
			}
			page.SelectedLines = m_lineptrs[page.PageNum];
		}

		private void CheckIfSelected()
		{
			m_textselected = false;

			if (m_selectall)
			{
				SetSelectAll(m_blockcolor);
				m_selectall = false;
			}
			/* Check if anything was selected */
			for (int kk = 0; kk < m_lineptrs.Count; kk++)
			{
				if (m_lineptrs[kk].Count > 0)
				{
					m_textselected = true;
					break;
				}
			}
		}

		/* Rect should be removed */
		private void PageLeftClickUp(object sender, MouseButtonEventArgs e)
		{
			m_intxtselect = false;
			CheckIfSelected();
		}

		private void StepScroll(int stepsize)
		{
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
			{
				var scrollpos = viewer.VerticalOffset;
				viewer.ScrollToVerticalOffset(scrollpos + stepsize);
			}
		}

		private void ResetScroll()
		{
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer != null)
				viewer.ScrollToVerticalOffset(0);
		}

		/* Recursive call to find the scroll viewer */
		private ScrollViewer FindScrollViewer(DependencyObject d)
		{
			if (d is ScrollViewer)
				return d as ScrollViewer;

			for (int i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++)
			{
				var sw = FindScrollViewer(VisualTreeHelper.GetChild(d, i));
				if (sw != null) return sw;
			}
			return null;
		}

		/* Only worry about cases where we are moving and left button is down */
		private void ListPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
		{
			var relPoint = e.GetPosition(xaml_PageList);
			var absPoint = this.PointToScreen(relPoint);
			/* Console.Write("abs Y position = " + absPoint.Y + "\n");
			Console.Write("rel Y position = " + relPoint.Y + "\n");
			Console.Write("Height is = " + (this.Top + this.Height) + "\n"); */

			if (xaml_PageList.IsMouseCaptured == true)
			{
				if (!m_intxtselect)
				{
					xaml_PageList.ReleaseMouseCapture();
					e.Handled = true;
					return;
				}

				if (relPoint.Y < Constants.SCROLL_EDGE_BUFFER ||
					absPoint.Y > (this.Top + this.Height - Constants.SCROLL_EDGE_BUFFER))
				{
					if (m_dispatcherTimer == null)
					{
						m_dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
						m_dispatcherTimer.Tick += new EventHandler(dispatcherTimerTick);
						m_dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, Constants.DISPATCH_TIME);
					}
					if (m_dispatcherTimer.IsEnabled == false)
						m_dispatcherTimer.Start();
					e.Handled = true;
				}

				/* This is not desirable, but the scrollviewer behaves badly
				 * when it has captured the mouse and we move beyond the
				 * range. So we wont allow it */
				if (relPoint.Y < 0 ||
					absPoint.Y > (this.Top + this.Height) - Constants.SCROLL_EDGE_BUFFER / 2.0)
				{
					xaml_PageList.ReleaseMouseCapture();
					e.Handled = true;
					if (m_dispatcherTimer != null && m_dispatcherTimer.IsEnabled == true)
						m_dispatcherTimer.Stop();
					return;
				}
			}
		}

		private void ListPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			if (m_dispatcherTimer != null && m_dispatcherTimer.IsEnabled)
			{
				m_dispatcherTimer.Stop();
			}
		}

		private void ListMouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
		{
			if (m_dispatcherTimer != null && m_dispatcherTimer.IsEnabled)
			{
				m_dispatcherTimer.Stop();
			}
			if (xaml_PageList.IsMouseCaptured == true)
				xaml_PageList.ReleaseMouseCapture();
		}

		/* Get mouse position, update selection accordingly */
		private void dispatcherTimerTick(object sender, EventArgs e)
		{
			var position = this.PointToScreen(Mouse.GetPosition(xaml_PageList));
			/* Console.Write("Y position = " + position.Y + "\n");
			Console.Write("Top position = " + this.Top + "\n");
			Console.Write("Bottom position = " + (this.Top + this.Height) + "\n"); */
			DocPage page;
			int page_num;

			if (!xaml_PageList.IsMouseCaptured)
			{
				//Console.Write("Lost capture\n");
				return;
			}
			/*else
			{
				Console.Write("Have capture\n");
			} */
			/* Get our most recent page */
			var pageinfo = m_textSelect[m_textSelect.Count - 1];
			page_num = pageinfo.pagenum;

			/* Scrolling up */
			if (position.Y > this.Top + this.Height - Constants.SCROLL_EDGE_BUFFER)
			{
				/* See if we have the last line for this page */
				if (pageinfo.last_line_full)
				{
					page_num = page_num + 1;
					m_lastY = 0;
					if (page_num >= m_num_pages)
						return;
				}
				page = m_docPages[page_num];
				StepScroll(Constants.SCROLL_STEP);
				/* Set position for proper selection update */
				m_lastY = m_lastY + Constants.SCROLL_STEP;
				if (m_lastY > m_maxY)
					m_lastY = m_maxY;
				position.Y = m_lastY;
				UpdateSelection(position, page);
			}
			else if (position.Y < this.Top + Constants.SCROLL_EDGE_BUFFER)
			{
				/* See if we have the first line for this page */
				if (pageinfo.first_line_full)
				{
					if (page_num <= 0)
						return;
					page_num = page_num - 1;
					m_lastY = m_docPages[page_num].Height;
				}
				page = m_docPages[page_num];
				StepScroll(-Constants.SCROLL_STEP);
				/* Set position for proper selection update */
				m_lastY = m_lastY - Constants.SCROLL_STEP;
				if (m_lastY < 0)
					m_lastY = 0;
				position.Y = m_lastY;
				UpdateSelection(position, page);
			}
		}

		private void ListPreviewLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			if (m_dispatcherTimer != null && m_dispatcherTimer.IsEnabled)
			{
				m_dispatcherTimer.Stop();
			}
		}

		private void ShowContextMenu(object sender, MouseButtonEventArgs e)
		{
			if (this.Cursor != System.Windows.Input.Cursors.IBeam)
				return;

			var contextmenu = new System.Windows.Controls.ContextMenu();
			Canvas can = ((FrameworkElement)e.Source).Parent as Canvas;
			var page = ((FrameworkElement)e.Source).DataContext as DocPage;
			if (can == null || page == null)
				return;

			var posit = e.GetPosition(can);
			ContextMenu_t info = new ContextMenu_t();
			info.mouse_position = posit;
			info.page_num = page.PageNum;
			can.ContextMenu = contextmenu;

			if (m_textselected || m_selectall)
			{
				var m1 = new System.Windows.Controls.MenuItem();
				m1.Header = "Copy";

				/* amazing what I have to do here to get the icon out of the
				 * resources into something that wpf can use */
				var iconres = Properties.Resources.copy;
				var bitmap = iconres.ToBitmap();
				using (MemoryStream memory = new MemoryStream())
				{
					bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
					memory.Position = 0;
					BitmapImage bitmapImage = new BitmapImage();
					bitmapImage.BeginInit();
					bitmapImage.StreamSource = memory;
					bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
					bitmapImage.EndInit();
					Image iconImage = new Image();
					iconImage.Source = bitmapImage;
					m1.Icon = iconImage;
					m1.Click += cntxMenuCopy;
					contextmenu.Items.Add(m1);
				}

				var m6 = new System.Windows.Controls.MenuItem();
				m6.Header = "Deselect All";
				m6.Click += cntxMenuDeselectAll;
				contextmenu.Items.Add(m6);

				/* Below to be enabled when we add annotations */
				/*
				var ma1 = new System.Windows.Controls.MenuItem();
				ma1.Header = "Highlight";
				ma1.Click += cntxMenuHighlight;
				contextmenu.Items.Add(ma1);

				var ma2 = new System.Windows.Controls.MenuItem();
				ma2.Header = "Underline";
				ma2.Click += cntxMenuUnderline;
				contextmenu.Items.Add(ma2);

				var ma3 = new System.Windows.Controls.MenuItem();
				ma3.Header = "Strikeout";
				ma3.Click += cntxMenuStrike;
				contextmenu.Items.Add(ma3);*/

			}
			var m2 = new System.Windows.Controls.MenuItem();
			m2.Header = "Select Line";
			m2.Click += cntxMenuSelectLine;
			m2.Tag = info;
			contextmenu.Items.Add(m2); 
				
			var m3 = new System.Windows.Controls.MenuItem();
			m3.Header = "Select Block";
			m3.Click += cntxMenuSelectBlock;
			m3.Tag = info;
			contextmenu.Items.Add(m3);

			var m4 = new System.Windows.Controls.MenuItem();
			m4.Header = "Select Page";
			m4.Click += cntxMenuSelectPage;
			m4.Tag = info;
			contextmenu.Items.Add(m4);

			var m5 = new System.Windows.Controls.MenuItem();
			m5.Header = "Select All";
			m5.Click += cntxMenuSelectAll;
			contextmenu.Items.Add(m5);
		}

		private void CopyTextDone(object sender, RunWorkerCompletedEventArgs e)
		{
			String result = (String) e.Result;
			xaml_CopyTextProgress.Visibility = System.Windows.Visibility.Collapsed;
			xaml_CopyTextProgress.Value = 0;

			try
			{
				System.Windows.Clipboard.SetText(result);
			}
			catch
			{
				return;
			}
		}

		private void CopyTextWork(object sender, DoWorkEventArgs e)
		{
			String output = null;
			String fullstring = null;
			BackgroundWorker worker = sender as BackgroundWorker;

			for (int k = 0; k < m_num_pages; k++)
			{
				output = mu_doc.GetText(k, textout_t.TEXT);
				if (output != null)
					fullstring = fullstring + output;

				double percent = 100 * (double)(k + 1) / (double)m_num_pages;
				worker.ReportProgress((int)percent, output);

				if (worker.CancellationPending == true)
				{
					e.Cancel = true;
					break;
				}
			}
			e.Result = fullstring;
		}

		private void CopyTextProgress(object sender, ProgressChangedEventArgs e)
		{
			String output = (String)(e.UserState);
			xaml_CopyTextProgress.Value = e.ProgressPercentage;
		}

		private void cntxMenuCopy(object sender, RoutedEventArgs e)
		{
			if (m_selectall)
			{
				/* Start a thread to go through and copy the pages to the 
				 * clipboard */
				m_copytext = new BackgroundWorker();
				m_copytext.WorkerReportsProgress = true;
				m_copytext.WorkerSupportsCancellation = true;
				m_copytext.DoWork += new DoWorkEventHandler(CopyTextWork);
				m_copytext.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CopyTextDone);
				m_copytext.ProgressChanged += new ProgressChangedEventHandler(CopyTextProgress);
				xaml_CopyTextProgress.Visibility = System.Windows.Visibility.Visible;
				m_copytext.RunWorkerAsync();
				return;
			}

			/* Go through and get each line of text */
			String result = null;

			for (int kk = 0; kk < m_textSelect.Count; kk++)
			{
				var lines = m_lineptrs[m_textSelect[kk].pagenum];
				for (int jj = 0; jj < lines.Count; jj++)
				{
					var text = lines[jj].TextCharacters;
					for (int mm = 0; mm < text.Count; mm++)
					{
						result += text[mm].character;
					}
					result += "\r\n";
				}
			}
			System.Windows.Clipboard.SetText(result);
		}

		private void cntxMenuSelectLine(object sender, RoutedEventArgs e)
		{
			var mi = sender as System.Windows.Controls.MenuItem;
			ContextMenu_t info = (ContextMenu_t)mi.Tag;
			var page = m_docPages[info.page_num];

			InitTextSelection(page);

			page.SelX = 0;
			page.SelY = info.mouse_position.Y - 1;
			page.SelAnchorX = 0;
			page.SelAnchorY = info.mouse_position.Y - 1;
			page.SelColor = m_regionselect;

			/* Create new holder for lines highlighted */
			m_lineptrs[page.PageNum] = new LinesText();

			Point pos = new Point();
			pos.X = page.Width;
			pos.Y += info.mouse_position.Y + 1;

			UpdateSelection(pos, page);
			CheckIfSelected();
		}

		/* This one requires its own special handling TODO FIXME */
		private void cntxMenuSelectBlock(object sender, RoutedEventArgs e)
		{
			var mi = sender as System.Windows.Controls.MenuItem;
			ContextMenu_t info = (ContextMenu_t)mi.Tag;
			var page = m_docPages[info.page_num];
			bool found = false;
			int jj;

			InitTextSelection(page);

			/* Find the block that we are in */
			for (jj = 0; jj < page.TextBlocks.Count; jj++)
			{
				var intersect_blk = page.TextBlocks[jj].CheckIntersection(info.mouse_position.X, info.mouse_position.Y, 1, 1);
				if (intersect_blk != Intersection_t.NONE)
				{
					found = true;
					break;
				}
			}
			if (found)
			{
				page.SelX = page.TextBlocks[jj].X;
				page.SelY = page.TextBlocks[jj].Y;
				page.SelAnchorX = page.TextBlocks[jj].X;
				page.SelAnchorY = page.TextBlocks[jj].Y;
				page.SelColor = m_regionselect;

				/* Create new holder for lines highlighted */
				m_lineptrs[page.PageNum] = new LinesText();

				Point pos = new Point();
				pos.X = page.TextBlocks[jj].X + page.TextBlocks[jj].Width;
				pos.Y = page.TextBlocks[jj].Y + page.TextBlocks[jj].Height;

				UpdateSelection(pos, page);
				CheckIfSelected();
			}
			else
				m_textselected = false;
		}

		private void SelectFullPage(int page_num)
		{
			var page = m_docPages[page_num];

			InitTextSelection(page);

			page.SelX = 0;
			page.SelY = 0;
			page.SelAnchorX = 0;
			page.SelAnchorY = 0;
			page.SelColor = m_regionselect;

			/* Create new holder for lines highlighted */
			m_lineptrs[page.PageNum] = new LinesText();

			Point pos = new Point();
			pos.X = page.Width;
			pos.Y = page.Height;

			UpdateSelection(pos, page);
		}

		private void cntxMenuSelectPage(object sender, RoutedEventArgs e)
		{
			var mi = sender as System.Windows.Controls.MenuItem;
			ContextMenu_t info = (ContextMenu_t)mi.Tag;

			SelectFullPage(info.page_num);
			CheckIfSelected();
		}

		private void cntxMenuSelectAll(object sender, RoutedEventArgs e)
		{
			var mi = sender as System.Windows.Controls.MenuItem;
			if (m_textSelect != null)
				ClearSelections();
			else
				m_textSelect = new List<textSelectInfo_t>();

			m_selectall = true;
			SetSelectAll(m_textselectcolor);
		}

		private void SetSelectAll(String color)
		{
			if (!m_init_done)
				return;

			for (int kk = 0; kk < m_num_pages; kk++)
			{
				if (m_docPages[kk] != null && m_docPages[kk].TextBlocks != null)
				{
					int num_blocks = m_docPages[kk].TextBlocks.Count;
					for (int jj = 0; jj < num_blocks; jj++)
						m_docPages[kk].TextBlocks[jj].Color = color;
				}
			}
		}

		private void cntxMenuDeselectAll(object sender, RoutedEventArgs e)
		{
			ClearSelections();
		}

		private void SelectAllCommand(object sender, ExecutedRoutedEventArgs e)
		{
			if (m_init_done)
				cntxMenuSelectAll(sender, e);
		}

		private void CopyCommand(object sender, ExecutedRoutedEventArgs e)
		{
			if (m_init_done)
				cntxMenuCopy(sender, e);
		}

		private void CancelCopyText(object sender, RoutedEventArgs e)
		{
			if (m_copytext != null && m_copytext.IsBusy)
				m_copytext.CancelAsync();
		}

		/* To add with annotation support */
		/*
		private void cntxMenuHighlight(object sender, RoutedEventArgs e)
		{
		
		}

		private void cntxMenuUnderline(object sender, RoutedEventArgs e)
		{

		}

		private void cntxMenuStrike(object sender, RoutedEventArgs e)
		{

		}
		*/
		#endregion TextSelection

		private void OnAboutClick(object sender, RoutedEventArgs e)
		{
			String muversion;
			About about = new About(this);
			var desc_static = about.Description;
			String desc;

			/* Get our gs and mupdf version numbers to add to the description */
			mu_doc.GetVersion(out muversion);
			if (muversion == null)
				desc = desc_static + "\nMuPDF DLL: Not Found";
			else
			{
				if (mu_doc.is64bit)
				{
					desc = desc_static + "\nUsing MuPDF Version " + muversion + " 64 bit\n";
				} 
				else
				{
					desc = desc_static + "\nUsing MuPDF Version " + muversion + " 32 bit\n";
				}
			}
			String gs_vers = m_ghostscript.GetVersion();
			if (gs_vers == null)
				desc = desc + "\nGhostscript DLL: Not Found";
			else
				if (mu_doc.is64bit)
				{
					desc = desc + "\nGhostscript DLL: " + gs_vers + " 64 bit\n";
				}
				else
				{
					desc = desc + "\nGhostscript DLL: " + gs_vers + " 64 bit\n";
				}
			about.description.Text = desc;
			about.ShowDialog();
		}

		private void HelpCommand(object sender, ExecutedRoutedEventArgs e)
		{
			OnHelpClick(sender, e);
		}

		private void OnHelpClick(object sender, RoutedEventArgs e)
		{

		}

		private void CloseFile(object sender, RoutedEventArgs e)
		{
			CleanUp();
			DimSelections();
		}

		private double GetTotalHeightZoom()
		{
			return m_totalpageheight * m_doczoom + (m_num_pages - 1) * Constants.PAGE_MARGIN;
		}

		private double GetTotalHeightNoZoom()
		{
			return m_totalpageheight + (m_num_pages - 1) * Constants.PAGE_MARGIN;
		}

		private double GetViewPortSize()
		{
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			return viewer.ViewportHeight;
		}

		private void SetThumbwidth()
		{
			double percent = GetViewPortSize() / GetTotalHeightZoom();
			double range = xaml_VerticalScroll.Maximum - xaml_VerticalScroll.Minimum;
			xaml_VerticalScroll.SetThumbLength(percent * range);
		}

		private void AdjustScrollPercent(double percent)
		{
			double curr_value = xaml_VerticalScroll.Value;
			double range = xaml_VerticalScroll.Maximum;
			double step = range * percent;

			xaml_VerticalScroll.Value = curr_value + step;
		}

		/* Due to the scroll bar on the scroll viewer being wonky on its updating during zooming
		 * we have to do this ourselves */
		private void VerticalScroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
		{
			var mi = sender as System.Windows.Controls.Primitives.ScrollBar;
			ScrollViewer viewer = FindScrollViewer(xaml_PageList);
			if (viewer == null || mi == null)
				return;

			m_ScrolledChanged = true;

			if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.ThumbTrack)
			{
				OffsetScrollPercent(mi.Value / mi.Maximum);
				e.Handled = true;
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.First)
			{
				mi.Value = 0;
				viewer.ScrollToTop();
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.Last)
			{
				mi.Value = mi.Maximum;
				viewer.ScrollToBottom();
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.SmallDecrement)
			{
				OffsetScroll(-Constants.VERT_SCROLL_STEP * m_doczoom);
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.SmallIncrement)
			{
				OffsetScroll(Constants.VERT_SCROLL_STEP * m_doczoom);
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.LargeDecrement)
			{
				if (m_currpage == 0)
				{
					mi.Value = 0;
					viewer.ScrollToTop();
				}
				else
					OnBackPageClick(null, null);
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.LargeIncrement)
			{
				if (m_currpage == m_num_pages - 1)
				{
					mi.Value = mi.Maximum;
					viewer.ScrollToBottom();
				}
				else
					OnForwardPageClick(null, null);
			}
			else if (e.ScrollEventType == System.Windows.Controls.Primitives.ScrollEventType.ThumbPosition)
			{
				OffsetScrollPercent(e.NewValue / mi.Maximum);
			}
		}

		private void OnAAChecked(object sender, RoutedEventArgs e)
		{
			var control = sender as System.Windows.Controls.Control;
			string Name = control.Name;

			/* It would be nice to uncheck all and then recheck the one
			 * that we want to avoid the repeated code below, but that puts
			 * us in a infinite recursion with the call from the xaml Checked
			 * call */

			switch (Name)
			{
				case "xaml_AA_High":
					m_AA = AA_t.HIGH;
					if (xaml_AA_MedHigh != null)
						xaml_AA_MedHigh.IsChecked = false;
					if (xaml_AA_Med != null)
						xaml_AA_Med.IsChecked = false;
					if (xaml_AA_Low != null)
						xaml_AA_Low.IsChecked = false;
					if (xaml_AA_None != null)
						xaml_AA_None.IsChecked = false;
					break;
				case "xaml_AA_MedHigh":
					m_AA = AA_t.MEDHIGH;
					if (xaml_AA_High != null)
						xaml_AA_High.IsChecked = false;
					if (xaml_AA_Med != null)
						xaml_AA_Med.IsChecked = false;
					if (xaml_AA_Low != null)
						xaml_AA_Low.IsChecked = false;
					if (xaml_AA_None != null)
						xaml_AA_None.IsChecked = false;
					break;
				case "xaml_AA_Med":
					m_AA = AA_t.MED;
					if (xaml_AA_High != null)
						xaml_AA_High.IsChecked = false;
					if (xaml_AA_MedHigh != null)
						xaml_AA_MedHigh.IsChecked = false;
					if (xaml_AA_Low != null)
						xaml_AA_Low.IsChecked = false;
					if (xaml_AA_None != null)
						xaml_AA_None.IsChecked = false;
					break;
				case "xaml_AA_Low":
					m_AA = AA_t.LOW;
					if (xaml_AA_High != null)
						xaml_AA_High.IsChecked = false;
					if (xaml_AA_MedHigh != null)
						xaml_AA_MedHigh.IsChecked = false;
					if (xaml_AA_Med != null)
						xaml_AA_Med.IsChecked = false;
					if (xaml_AA_None != null)
						xaml_AA_None.IsChecked = false;
					break;
				case "xaml_AA_None":
					m_AA = AA_t.NONE;
					if (xaml_AA_High != null)
						xaml_AA_High.IsChecked = false;
					if (xaml_AA_MedHigh != null)
						xaml_AA_MedHigh.IsChecked = false;
					if (xaml_AA_Med != null)
						xaml_AA_Med.IsChecked = false;
					if (xaml_AA_Low != null)
						xaml_AA_Low.IsChecked = false;
					break;
			}
			if (mu_doc != null)
				mu_doc.SetAA(m_AA);
			if (m_init_done)
				RenderRange(m_currpage, false, zoom_t.NO_ZOOM, 0);
		}

		private AA_t GetAA()
		{
			if (xaml_AA_High.IsChecked)
				return AA_t.HIGH;
			else if (xaml_AA_MedHigh.IsChecked)
				return AA_t.MEDHIGH;
			else if (xaml_AA_Med.IsChecked)
				return AA_t.MED;
			else if (xaml_AA_Low.IsChecked)
				return AA_t.LOW;
			else
				return AA_t.NONE;
		}

		private void SetAA(AA_t aa)
		{
			xaml_AA_High.IsChecked = false;
			xaml_AA_MedHigh.IsChecked = false;
			xaml_AA_Med.IsChecked = false;
			xaml_AA_Low.IsChecked = false;
			xaml_AA_None.IsChecked = false;

			switch (aa)
			{
				case AA_t.HIGH:
					xaml_AA_High.IsChecked = true;
					break;
				case AA_t.MEDHIGH:
					xaml_AA_MedHigh.IsChecked = true;
					break;
				case AA_t.MED:
					xaml_AA_High.IsChecked = true;
					break;
				case AA_t.LOW:
					xaml_AA_High.IsChecked = true;
					break;
				case AA_t.NONE:
					xaml_AA_High.IsChecked = true;
					break;
			}
		}

		private void AnnotationOn(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;
			m_showannot = true;
			RenderRange(m_currpage, false, zoom_t.NO_ZOOM, 0);
		}

		private void AnnotationOff(object sender, RoutedEventArgs e)
		{
			if (!m_init_done)
				return;
			m_showannot = false;
			RenderRange(m_currpage, false, zoom_t.NO_ZOOM, 0);
		}

		/* Print preview rendering and control */
		private void RenderPrintPreview(object sender, DoWorkEventArgs e)
		{
			BackgroundWorker worker = sender as BackgroundWorker;
			List<object> genericlist = e.Argument as List<object>;
			int k = (int)genericlist[0];
			int desiredMax = Constants.MAX_PRINT_PREVIEW_LENGTH;

			Point ras_size;
			double scale_factor = 1.0;
			Byte[] bitmap;
			BlocksText charlist;
			status_t code;
			Annotate_t annot;

			if (ComputePageSize(k, scale_factor, out ras_size) == status_t.S_ISOK)
			{
				/* Adjust the scale factor to ensure max length is set as desired */
				int maxSize = Math.Max((int)ras_size.X, (int)ras_size.Y);
				scale_factor = (double)desiredMax / (double)maxSize;
				ComputePageSize(k, scale_factor, out ras_size);
				printPreviewPage_t result;

				try
				{
					bitmap = new byte[(int)ras_size.X * (int)ras_size.Y * 4];
					code = (status_t)mu_doc.RenderPage(k, bitmap, (int)ras_size.X,
						(int)ras_size.Y, scale_factor, false, true,
						false, out charlist, m_showannot, out annot);
					result.width = (int)ras_size.X;
					result.height = (int)ras_size.Y;
					result.bitmap = bitmap;
					ComputePageSize(k, 1.0, out ras_size);
					result.height_inches = ras_size.Y / 72.0; 
					result.width_inches = ras_size.X / 72.0; 
					e.Result = result;
				}
				catch (OutOfMemoryException em)
				{
					Console.WriteLine("Memory allocation failed print preview page " + k + em.Message + "\n");
				}
			}
		}

		private void RenderPrintPreviewCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			BitmapSource BitMapSrc;
			printPreviewPage_t Result = (printPreviewPage_t)e.Result;

			int stride = Result.width * 4;
			BitMapSrc = BitmapSource.Create(Result.width, Result.height,
				72, 72, PixelFormats.Pbgra32, BitmapPalettes.Halftone256, Result.bitmap, stride);

			m_printcontrol.SetImage(BitMapSrc, Result.height_inches, Result.width_inches);
		}

		private bool PrintDiagUpdatePreview(object PrintDiag, PrintDiagEventArgs args)
		{
			try
			{
				m_printerpreview = new BackgroundWorker();
				m_printerpreview.WorkerReportsProgress = false;
				m_printerpreview.WorkerSupportsCancellation = false;
				m_printerpreview.DoWork += new DoWorkEventHandler(RenderPrintPreview);
				m_printerpreview.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RenderPrintPreviewCompleted);
				var arguments = new List<object>();
				arguments.Add(args.m_page);
				m_printerpreview.RunWorkerAsync(arguments);
			}
			catch (OutOfMemoryException e)
			{
				Console.WriteLine("Memory allocation failed during printpreview render\n");
				ShowMessage(NotifyType_t.MESS_ERROR, "Out of memory: " + e.Message);
			}
			return true;
		}

		private void PrintDiagPrint(object PrintDiag)
		{
			/* If file is already xps then gs need not do this */
			if (!m_isXPS)
			{
				xaml_DistillProgress.Value = 0;
				if (m_ghostscript.CreateXPS(m_currfile, Constants.DEFAULT_GS_RES, m_num_pages, m_printcontrol) == gsStatus.GS_BUSY)
				{
					ShowMessage(NotifyType_t.MESS_STATUS, "GS currently busy");
					return;
				}
				else
				{
					xaml_CancelDistill.Visibility = System.Windows.Visibility.Collapsed;
					xaml_DistillName.Text = "Convert to XPS";
					xaml_DistillName.FontWeight = FontWeights.Bold;
					xaml_DistillGrid.Visibility = System.Windows.Visibility.Visible;
				}
			}
			else
				PrintXPS(m_currfile);
		}
	}
}