/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.printing;


import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.cairo.*;
import org.eclipse.swt.internal.gtk.*;

/**
 * Instances of this class are used to print to a printer.
 * Applications create a GC on a printer using <code>new GC(printer)</code>
 * and then draw on the printer GC using the usual graphics calls.
 * <p>
 * A <code>Printer</code> object may be constructed by providing
 * a <code>PrinterData</code> object which identifies the printer.
 * A <code>PrintDialog</code> presents a print dialog to the user
 * and returns an initialized instance of <code>PrinterData</code>.
 * Alternatively, calling <code>new Printer()</code> will construct a
 * printer object for the user's default printer.
 * </p><p>
 * Application code must explicitly invoke the <code>Printer.dispose()</code>
 * method to release the operating system resources managed by each instance
 * when those instances are no longer required.
 * </p>
 *
 * @see PrinterData
 * @see PrintDialog
 * @see <a href="http://www.eclipse.org/swt/snippets/#printing">Printing snippets</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 */
public final class Printer extends Device {
	static PrinterData [] printerList;
	static long /*int*/ findPrinter;
	static PrinterData findData;

	PrinterData data;
	long /*int*/ printer;
	long /*int*/ printJob;
	long /*int*/ settings;
	long /*int*/ pageSetup;
	long /*int*/ surface;
	long /*int*/ cairo;

	/**
	 * whether or not a GC was created for this printer
	 */
	boolean isGCCreated = false;

	static byte [] settingsData;
	static int start, end;

	static final String GTK_LPR_BACKEND = "GtkPrintBackendLpr"; //$NON-NLS-1$
	static final String GTK_FILE_BACKEND = "GtkPrintBackendFile"; //$NON-NLS-1$

	static boolean disablePrinting = OS.IsWin32 || System.getProperty("org.eclipse.swt.internal.gtk.disablePrinting") != null; //$NON-NLS-1$

static void gtk_init() {
	if (OS.GLIB_VERSION < OS.VERSION(2, 32, 0)) {
		if (!OS.g_thread_supported()) {
			OS.g_thread_init(0);
		}
	}
	if (!GTK.gtk_init_check (new long /*int*/ [] {0}, null)) {
		SWT.error (SWT.ERROR_NO_HANDLES, null, " [gtk_init_check() failed]");
	}
}

/**
 * Returns an array of <code>PrinterData</code> objects
 * representing all available printers.  If there are no
 * printers, the array will be empty.
 *
 * @return an array of PrinterData objects representing the available printers
 */
public static PrinterData[] getPrinterList() {
	printerList = new PrinterData [0];
	if (disablePrinting) {
		return printerList;
	}
	gtk_init();
	Callback printerCallback = new Callback(Printer.class, "GtkPrinterFunc_List", 2); //$NON-NLS-1$
	long /*int*/ GtkPrinterFunc_List = printerCallback.getAddress();
	if (GtkPrinterFunc_List == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS);
	GTK.gtk_enumerate_printers(GtkPrinterFunc_List, 0, 0, true);
	/*
	* This call to gdk_threads_leave() is a temporary work around
	* to avoid deadlocks when gdk_threads_init() is called by native
	* code outside of SWT (i.e AWT, etc). It ensures that the current
	* thread leaves the GTK lock acquired by the function above.
	*/
	GDK.gdk_threads_leave();
	printerCallback.dispose ();
	return printerList;
}

static long /*int*/ GtkPrinterFunc_List (long /*int*/ printer, long /*int*/ user_data) {
	int length = printerList.length;
	PrinterData [] newList = new PrinterData [length + 1];
	System.arraycopy (printerList, 0, newList, 0, length);
	printerList = newList;
	printerList [length] = printerDataFromGtkPrinter(printer);
	/*
	* Bug in GTK. While performing a gtk_enumerate_printers(), GTK finds all of the
	* available printers from each backend and can hang. If a backend requires more
	* time to gather printer info, GTK will start an event loop waiting for a done
    * signal before continuing. For the Lpr backend, GTK does not send a done signal
    * which means the event loop never ends. The fix is to check to see if the driver
    * is of type Lpr, and stop the enumeration, which exits the event loop.
	*/
	if (printerList[length].driver.equals (GTK_LPR_BACKEND)) return 1;
	return 0;
}

/**
 * Returns a <code>PrinterData</code> object representing
 * the default printer or <code>null</code> if there is no
 * default printer.
 *
 * @return the default printer data or null
 *
 * @since 2.1
 */
public static PrinterData getDefaultPrinterData() {
	findData = null;
	if (disablePrinting) {
		return null;
	}
	gtk_init();
	Callback printerCallback = new Callback(Printer.class, "GtkPrinterFunc_Default", 2); //$NON-NLS-1$
	long /*int*/ GtkPrinterFunc_Default = printerCallback.getAddress();
	if (GtkPrinterFunc_Default == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS);
	GTK.gtk_enumerate_printers(GtkPrinterFunc_Default, 0, 0, true);
	/*
	* This call to gdk_threads_leave() is a temporary work around
	* to avoid deadlocks when gdk_threads_init() is called by native
	* code outside of SWT (i.e AWT, etc). It ensures that the current
	* thread leaves the GTK lock acquired by the function above.
	*/
	GDK.gdk_threads_leave();
	printerCallback.dispose ();
	return findData;
}

static long /*int*/ GtkPrinterFunc_Default (long /*int*/ printer, long /*int*/ user_data) {
	if (GTK.gtk_printer_is_default(printer)) {
		findData = printerDataFromGtkPrinter(printer);
		return 1;
	}
	return 0;
}

static long /*int*/ gtkPrinterFromPrinterData(PrinterData data) {
	gtk_init();
	Callback printerCallback = new Callback(Printer.class, "GtkPrinterFunc_FindNamedPrinter", 2); //$NON-NLS-1$
	long /*int*/ GtkPrinterFunc_FindNamedPrinter = printerCallback.getAddress();
	if (GtkPrinterFunc_FindNamedPrinter == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS);
	findPrinter = 0;
	findData = data;
	GTK.gtk_enumerate_printers(GtkPrinterFunc_FindNamedPrinter, 0, 0, true);
	/*
	* This call to gdk_threads_leave() is a temporary work around
	* to avoid deadlocks when gdk_threads_init() is called by native
	* code outside of SWT (i.e AWT, etc). It ensures that the current
	* thread leaves the GTK lock acquired by the function above.
	*/
	GDK.gdk_threads_leave();
	printerCallback.dispose ();
	return findPrinter;
}

static long /*int*/ GtkPrinterFunc_FindNamedPrinter (long /*int*/ printer, long /*int*/ user_data) {
	PrinterData pd = printerDataFromGtkPrinter(printer);
	if ((pd.driver.equals(findData.driver) && pd.name.equals(findData.name))
			|| (pd.driver.equals(GTK_FILE_BACKEND)) && findData.printToFile && findData.driver == null && findData.name == null) {
			// TODO: GTK_FILE_BACKEND is not GTK API (see gtk bug 345590)
		findPrinter = printer;
		OS.g_object_ref(printer);
		return 1;
	}
	return 0;
}

static PrinterData printerDataFromGtkPrinter(long /*int*/ printer) {
	long /*int*/ backend = GTK.gtk_printer_get_backend(printer);
	long /*int*/ address = OS.G_OBJECT_TYPE_NAME(backend);
	int length = C.strlen (address);
	byte [] buffer = new byte [length];
	C.memmove (buffer, address, length);
	String backendType = new String (Converter.mbcsToWcs (buffer));

	address = GTK.gtk_printer_get_name (printer);
	length = C.strlen (address);
	buffer = new byte [length];
	C.memmove (buffer, address, length);
	String name = new String (Converter.mbcsToWcs (buffer));

	return new PrinterData (backendType, name);
}

/*
* Restore printer settings and page_setup data from data.
*/
static void restore(byte [] data, long /*int*/ settings, long /*int*/ page_setup) {
	settingsData = data;
	start = end = 0;
	while (end < settingsData.length && settingsData[end] != 0) {
		start = end;
		while (end < settingsData.length && settingsData[end] != 0) end++;
		end++;
		byte [] keyBuffer = new byte [end - start];
		System.arraycopy (settingsData, start, keyBuffer, 0, keyBuffer.length);
		start = end;
		while (end < settingsData.length && settingsData[end] != 0) end++;
		end++;
		byte [] valueBuffer = new byte [end - start];
		System.arraycopy (settingsData, start, valueBuffer, 0, valueBuffer.length);
		GTK.gtk_print_settings_set(settings, keyBuffer, valueBuffer);
		if (DEBUG) System.out.println(new String (Converter.mbcsToWcs (keyBuffer))+": "+new String (Converter.mbcsToWcs (valueBuffer)));
	}
	end++; // skip extra null terminator

	/* Retrieve stored page_setup data.
	 * Note that page_setup properties must be stored (in PrintDialog) and restored (here) in the same order.
	 */
	GTK.gtk_page_setup_set_orientation(page_setup, restoreInt("orientation")); //$NON-NLS-1$
	GTK.gtk_page_setup_set_top_margin(page_setup, restoreDouble("top_margin"), GTK.GTK_UNIT_MM); //$NON-NLS-1$
	GTK.gtk_page_setup_set_bottom_margin(page_setup, restoreDouble("bottom_margin"), GTK.GTK_UNIT_MM); //$NON-NLS-1$
	GTK.gtk_page_setup_set_left_margin(page_setup, restoreDouble("left_margin"), GTK.GTK_UNIT_MM); //$NON-NLS-1$
	GTK.gtk_page_setup_set_right_margin(page_setup, restoreDouble("right_margin"), GTK.GTK_UNIT_MM); //$NON-NLS-1$
	byte [] name = restoreBytes("paper_size_name", true); //$NON-NLS-1$
	byte [] display_name = restoreBytes("paper_size_display_name", true); //$NON-NLS-1$
	byte [] ppd_name = restoreBytes("paper_size_ppd_name", true); //$NON-NLS-1$
	double width = restoreDouble("paper_size_width"); //$NON-NLS-1$
	double height = restoreDouble("paper_size_height"); //$NON-NLS-1$
	boolean custom = restoreBoolean("paper_size_is_custom"); //$NON-NLS-1$
	long /*int*/ paper_size = 0;
	if (custom) {
		if (ppd_name.length > 0) {
			paper_size = GTK.gtk_paper_size_new_from_ppd(ppd_name, display_name, width, height);
		} else {
			paper_size = GTK.gtk_paper_size_new_custom(name, display_name, width, height, GTK.GTK_UNIT_MM);
		}
	} else {
		paper_size = GTK.gtk_paper_size_new(name);
	}
	GTK.gtk_page_setup_set_paper_size(page_setup, paper_size);
	GTK.gtk_paper_size_free(paper_size);
}

static byte [] uriFromFilename(String filename) {
	if (filename == null) return null;
	int length = filename.length();
	if (length == 0) return null;
	char[] chars = new char[length];
	filename.getChars(0, length, chars, 0);
	long /*int*/[] error = new long /*int*/[1];
	long /*int*/ utf8Ptr = OS.g_utf16_to_utf8(chars, chars.length, null, null, error);
	if (error[0] != 0 || utf8Ptr == 0) return null;
	long /*int*/ localePtr = OS.g_filename_from_utf8(utf8Ptr, -1, null, null, error);
	OS.g_free(utf8Ptr);
	if (error[0] != 0 || localePtr == 0) return null;
	long /*int*/ uriPtr = OS.g_filename_to_uri(localePtr, 0, error);
	OS.g_free(localePtr);
	if (error[0] != 0 || uriPtr == 0) return null;
	length = C.strlen(uriPtr);
	byte[] uri = new byte[length + 1];
	C.memmove (uri, uriPtr, length);
	OS.g_free(uriPtr);
	return uri;
}

static DeviceData checkNull (PrinterData data) {
	if (data == null) data = new PrinterData();
	if (data.driver == null || data.name == null) {
		PrinterData defaultData = null;
		if (data.printToFile) {
			long /*int*/ filePrinter = gtkPrinterFromPrinterData(data);
			if (filePrinter != 0) {
				defaultData = printerDataFromGtkPrinter(filePrinter);
				OS.g_object_unref(filePrinter);
			}
		}
		if (defaultData == null) {
			defaultData = getDefaultPrinterData();
			if (defaultData == null) SWT.error(SWT.ERROR_NO_HANDLES);
		}
		data.driver = defaultData.driver;
		data.name = defaultData.name;
	}
	return data;
}

/**
 * Constructs a new printer representing the default printer.
 * <p>
 * Note: You must dispose the printer when it is no longer required.
 * </p>
 *
 * @exception SWTError <ul>
 *    <li>ERROR_NO_HANDLES - if there is no valid default printer
 * </ul>
 *
 * @see Device#dispose
 */
public Printer() {
	this(null);
}

/**
 * Constructs a new printer given a <code>PrinterData</code>
 * object representing the desired printer. If the argument
 * is null, then the default printer will be used.
 * <p>
 * Note: You must dispose the printer when it is no longer required.
 * </p>
 *
 * @param data the printer data for the specified printer, or null to use the default printer
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the specified printer data does not represent a valid printer
 * </ul>
 * @exception SWTError <ul>
 *    <li>ERROR_NO_HANDLES - if there are no valid printers
 * </ul>
 *
 * @see Device#dispose
 */
public Printer(PrinterData data) {
	super(checkNull(data));
}

static int restoreInt(String key) {
	byte [] value = restoreBytes(key, false);
	return Integer.parseInt(new String(value));
}

static double restoreDouble(String key) {
	byte [] value = restoreBytes(key, false);
	return Double.parseDouble(new String(value));
}

static boolean restoreBoolean(String key) {
	byte [] value = restoreBytes(key, false);
	return Boolean.valueOf(new String(value)).booleanValue();
}

static byte [] restoreBytes(String key, boolean nullTerminate) {
	//get key
	start = end;
	while (end < settingsData.length && settingsData[end] != 0) end++;
	end++;
	byte [] keyBuffer = new byte [end - start];
	System.arraycopy (settingsData, start, keyBuffer, 0, keyBuffer.length);

	//get value
	start = end;
	while (end < settingsData.length && settingsData[end] != 0) end++;
	int length = end - start;
	end++;
	if (nullTerminate) length++;
	byte [] valueBuffer = new byte [length];
	System.arraycopy (settingsData, start, valueBuffer, 0, length);

	if (DEBUG) System.out.println(new String (Converter.mbcsToWcs (keyBuffer))+": "+new String (Converter.mbcsToWcs (valueBuffer)));

	return valueBuffer;
}

/**
 * Invokes platform specific functionality to allocate a new GC handle.
 * <p>
 * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
 * API for <code>Printer</code>. It is marked public only so that it
 * can be shared within the packages provided by SWT. It is not
 * available on all platforms, and should never be called from
 * application code.
 * </p>
 *
 * @param data the platform specific GC data
 * @return the platform specific GC handle
 *
 * @noreference This method is not intended to be referenced by clients.
 */
@Override
public long /*int*/ internal_new_GC(GCData data) {
	long /*int*/ drawable = 0;
	long /*int*/ gc = cairo;
	if (gc == 0) SWT.error (SWT.ERROR_NO_HANDLES);
	if (data != null) {
		if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
		if ((data.style & mask) == 0) {
			data.style |= SWT.LEFT_TO_RIGHT;
		}
		data.device = this;
		data.drawable = drawable;
		if (GTK.GTK3) {
			data.backgroundRGBA = getSystemColor (SWT.COLOR_WHITE).handleRGBA;
			data.foregroundRGBA = getSystemColor (SWT.COLOR_BLACK).handleRGBA;
		} else {
			data.background = getSystemColor (SWT.COLOR_WHITE).handle;
			data.foreground = getSystemColor (SWT.COLOR_BLACK).handle;
		}
		data.font = getSystemFont ();
		Point dpi = getDPI(), screenDPI = getIndependentDPI();
		data.width = (int)(GTK.gtk_page_setup_get_paper_width (pageSetup, GTK.GTK_UNIT_POINTS) * dpi.x / screenDPI.x);
		data.height = (int)(GTK.gtk_page_setup_get_paper_height (pageSetup, GTK.GTK_UNIT_POINTS) * dpi.y / screenDPI.y);
		if (cairo == 0) SWT.error(SWT.ERROR_NO_HANDLES);
		Cairo.cairo_identity_matrix(cairo);
		double printX = GTK.gtk_page_setup_get_left_margin(pageSetup, GTK.GTK_UNIT_POINTS);
		double printY = GTK.gtk_page_setup_get_top_margin(pageSetup, GTK.GTK_UNIT_POINTS);
		Cairo.cairo_translate(cairo, printX, printY);
		Cairo.cairo_scale(cairo, screenDPI.x / (float)dpi.x, screenDPI.y / (float)dpi.y);
		double[] matrix = new double[6];
		Cairo.cairo_get_matrix(cairo, matrix);
		data.identity = matrix;
		data.cairo = cairo;
		isGCCreated = true;
	}
	return gc;
}

/**
 * Invokes platform specific functionality to dispose a GC handle.
 * <p>
 * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
 * API for <code>Printer</code>. It is marked public only so that it
 * can be shared within the packages provided by SWT. It is not
 * available on all platforms, and should never be called from
 * application code.
 * </p>
 *
 * @param hDC the platform specific GC handle
 * @param data the platform specific GC data
 *
 * @noreference This method is not intended to be referenced by clients.
 */
@Override
public void internal_dispose_GC(long /*int*/ hDC, GCData data) {
	if (data != null) isGCCreated = false;
}

/**
 * @noreference This method is not intended to be referenced by clients.
 */
@Override
public boolean isAutoScalable() {
	return false;
}

/**
 * Starts a print job and returns true if the job started successfully
 * and false otherwise.
 * <p>
 * This must be the first method called to initiate a print job,
 * followed by any number of startPage/endPage calls, followed by
 * endJob. Calling startPage, endPage, or endJob before startJob
 * will result in undefined behavior.
 * </p>
 *
 * @param jobName the name of the print job to start
 * @return true if the job started successfully and false otherwise.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #startPage
 * @see #endPage
 * @see #endJob
 */
public boolean startJob(String jobName) {
	checkDevice();
	byte [] buffer = Converter.wcsToMbcs (jobName, true);
	printJob = GTK.gtk_print_job_new (buffer, printer, settings, pageSetup);
	if (printJob == 0) return false;
	surface = GTK.gtk_print_job_get_surface(printJob, null);
	if (surface == 0) {
		OS.g_object_unref(printJob);
		printJob = 0;
		return false;
	}
	cairo = Cairo.cairo_create(surface);
	if (cairo == 0)  {
		OS.g_object_unref(printJob);
		printJob = 0;
		return false;
	}
	return true;
}

/**
 * Destroys the printer handle.
 * This method is called internally by the dispose
 * mechanism of the <code>Device</code> class.
 */
@Override
protected void destroy () {
	if (printer != 0) OS.g_object_unref (printer);
	if (settings != 0) OS.g_object_unref (settings);
	if (pageSetup != 0) OS.g_object_unref (pageSetup);
	if (cairo != 0) Cairo.cairo_destroy (cairo);
	if (printJob != 0) OS.g_object_unref (printJob);
	printer = settings = pageSetup = cairo = printJob = 0;
}

/**
 * Ends the current print job.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #startJob
 * @see #startPage
 * @see #endPage
 */
public void endJob() {
	checkDevice();
	if (printJob == 0) return;
	Cairo.cairo_surface_finish(surface);
	GTK.gtk_print_job_send(printJob, 0, 0, 0);
	OS.g_object_unref(printJob);
	printJob = 0;
}

/**
 * Cancels a print job in progress.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 */
public void cancelJob() {
	checkDevice();
	if (printJob == 0) return;
	//TODO: Need to implement (waiting on gtk bug 339323)
	Cairo.cairo_surface_finish(surface);
	OS.g_object_unref(printJob);
	printJob = 0;
}

/**
 * Starts a page and returns true if the page started successfully
 * and false otherwise.
 * <p>
 * After calling startJob, this method may be called any number of times
 * along with a matching endPage.
 * </p>
 *
 * @return true if the page started successfully and false otherwise.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #endPage
 * @see #startJob
 * @see #endJob
 */
public boolean startPage() {
	checkDevice();
	if (printJob == 0) return false;
	double width = GTK.gtk_page_setup_get_paper_width (pageSetup, GTK.GTK_UNIT_POINTS);
	double height = GTK.gtk_page_setup_get_paper_height (pageSetup, GTK.GTK_UNIT_POINTS);
	int type = Cairo.cairo_surface_get_type (surface);
	switch (type) {
		case Cairo.CAIRO_SURFACE_TYPE_PS:
			Cairo.cairo_ps_surface_set_size (surface, width, height);
			break;
		case Cairo.CAIRO_SURFACE_TYPE_PDF:
			Cairo.cairo_pdf_surface_set_size (surface, width, height);
			break;
	}
	return true;
}

/**
 * Ends the current page.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #startPage
 * @see #startJob
 * @see #endJob
 */
public void endPage() {
	checkDevice();
	Cairo.cairo_show_page(cairo);
}

/**
 * Returns a point whose x coordinate is the horizontal
 * dots per inch of the printer, and whose y coordinate
 * is the vertical dots per inch of the printer.
 *
 * @return the horizontal and vertical DPI
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 */
@Override
public Point getDPI() {
	checkDevice();
	int resolution = GTK.gtk_print_settings_get_resolution(settings);
	if (DEBUG) System.out.println("print_settings.resolution=" + resolution);
	//TODO: use new api for get x resolution and get y resolution
	if (resolution == 0) return new Point(72, 72);
	return new Point(resolution, resolution);
}

/**
 * Returns a rectangle describing the receiver's size and location.
 * <p>
 * For a printer, this is the size of the physical page, in pixels.
 * </p>
 *
 * @return the bounding rectangle
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #getClientArea
 * @see #computeTrim
 */
@Override
public Rectangle getBounds() {
	checkDevice();
	Point dpi = getDPI(), screenDPI = getIndependentDPI();
	double width = GTK.gtk_page_setup_get_paper_width (pageSetup, GTK.GTK_UNIT_POINTS) * dpi.x / screenDPI.x;
	double height = GTK.gtk_page_setup_get_paper_height (pageSetup, GTK.GTK_UNIT_POINTS) * dpi.y / screenDPI.y;
	return new Rectangle(0, 0, (int) width, (int) height);
}

/**
 * Returns a rectangle which describes the area of the
 * receiver which is capable of displaying data.
 * <p>
 * For a printer, this is the size of the printable area
 * of the page, in pixels.
 * </p>
 *
 * @return the client area
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #getBounds
 * @see #computeTrim
 */
@Override
public Rectangle getClientArea() {
	checkDevice();
	Point dpi = getDPI(), screenDPI = getIndependentDPI();
	double width = GTK.gtk_page_setup_get_page_width(pageSetup, GTK.GTK_UNIT_POINTS) * dpi.x / screenDPI.x;
	double height = GTK.gtk_page_setup_get_page_height(pageSetup, GTK.GTK_UNIT_POINTS) * dpi.y / screenDPI.y;
	return new Rectangle(0, 0, (int) width, (int) height);
}

Point getIndependentDPI () {
	return new Point(72, 72);
}

/**
 * Given a <em>client area</em> (as described by the arguments),
 * returns a rectangle, relative to the client area's coordinates,
 * that is the client area expanded by the printer's trim (or minimum margins).
 * <p>
 * Most printers have a minimum margin on each edge of the paper where the
 * printer device is unable to print.  This margin is known as the "trim."
 * This method can be used to calculate the printer's minimum margins
 * by passing in a client area of 0, 0, 0, 0 and then using the resulting
 * x and y coordinates (which will be <= 0) to determine the minimum margins
 * for the top and left edges of the paper, and the resulting width and height
 * (offset by the resulting x and y) to determine the minimum margins for the
 * bottom and right edges of the paper, as follows:
 * <ul>
 * 		<li>The left trim width is -x pixels</li>
 * 		<li>The top trim height is -y pixels</li>
 * 		<li>The right trim width is (x + width) pixels</li>
 * 		<li>The bottom trim height is (y + height) pixels</li>
 * </ul>
 * </p>
 *
 * @param x the x coordinate of the client area
 * @param y the y coordinate of the client area
 * @param width the width of the client area
 * @param height the height of the client area
 * @return a rectangle, relative to the client area's coordinates, that is
 * 		the client area expanded by the printer's trim (or minimum margins)
 *
 * @exception SWTException <ul>
 *    <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
 * </ul>
 *
 * @see #getBounds
 * @see #getClientArea
 */
public Rectangle computeTrim(int x, int y, int width, int height) {
	checkDevice();
	Point dpi = getDPI(), screenDPI = getIndependentDPI();
	double printWidth = GTK.gtk_page_setup_get_page_width(pageSetup, GTK.GTK_UNIT_POINTS) * dpi.x / screenDPI.x;
	double printHeight = GTK.gtk_page_setup_get_page_height(pageSetup, GTK.GTK_UNIT_POINTS) * dpi.y / screenDPI.y;
	double paperWidth = GTK.gtk_page_setup_get_paper_width (pageSetup, GTK.GTK_UNIT_POINTS) * dpi.x / screenDPI.x;
	double paperHeight = GTK.gtk_page_setup_get_paper_height (pageSetup, GTK.GTK_UNIT_POINTS) * dpi.y / screenDPI.y;
	double printX = -GTK.gtk_page_setup_get_left_margin(pageSetup, GTK.GTK_UNIT_POINTS) * dpi.x / screenDPI.x;
	double printY = -GTK.gtk_page_setup_get_top_margin(pageSetup, GTK.GTK_UNIT_POINTS) * dpi.y / screenDPI.y;
	double hTrim = paperWidth - printWidth;
	double vTrim = paperHeight - printHeight;
	return new Rectangle(x + (int)printX, y + (int)printY, width + (int)hTrim, height + (int)vTrim);
}

/**
 * Creates the printer handle.
 * This method is called internally by the instance creation
 * mechanism of the <code>Device</code> class.
 * @param deviceData the device data
 */
@Override
protected void create(DeviceData deviceData) {
	this.data = (PrinterData)deviceData;
	if (disablePrinting) SWT.error(SWT.ERROR_NO_HANDLES);
	printer = gtkPrinterFromPrinterData(data);
	if (printer == 0) SWT.error(SWT.ERROR_NO_HANDLES);
}

/**
 * Initializes any internal resources needed by the
 * device.
 * <p>
 * This method is called after <code>create</code>.
 * </p><p>
 * If subclasses reimplement this method, they must
 * call the <code>super</code> implementation.
 * </p>
 *
 * @see #create
 */
@Override
protected void init() {
	settings = GTK.gtk_print_settings_new();
	pageSetup = GTK.gtk_page_setup_new();
	if (data.otherData != null) {
		restore(data.otherData, settings, pageSetup);
	}

	/* Set values of print_settings and page_setup from PrinterData. */
	if (data.printToFile && data.fileName != null) {
		byte [] uri = uriFromFilename(data.fileName);
		if (uri != null) {
			GTK.gtk_print_settings_set(settings, GTK.GTK_PRINT_SETTINGS_OUTPUT_URI, uri);
		}
	}
	GTK.gtk_print_settings_set_n_copies(settings, data.copyCount);
	GTK.gtk_print_settings_set_collate(settings, data.collate);
	if (data.duplex != SWT.DEFAULT) {
		int duplex = data.duplex == PrinterData.DUPLEX_LONG_EDGE ? GTK.GTK_PRINT_DUPLEX_HORIZONTAL
			: data.duplex == PrinterData.DUPLEX_SHORT_EDGE ? GTK.GTK_PRINT_DUPLEX_VERTICAL
			: GTK.GTK_PRINT_DUPLEX_SIMPLEX;
		GTK.gtk_print_settings_set_duplex (settings, duplex);
		/*
		 * Bug in GTK.  The cups backend only looks at the value
		 * of the non-API field cups-Duplex in the print_settings.
		 * The fix is to manually set cups-Duplex to Tumble or NoTumble.
		 */
		String cupsDuplexType = null;
		if (duplex == GTK.GTK_PRINT_DUPLEX_HORIZONTAL) cupsDuplexType = "DuplexNoTumble";
		else if (duplex == GTK.GTK_PRINT_DUPLEX_VERTICAL) cupsDuplexType = "DuplexTumble";
		if (cupsDuplexType != null) {
			byte [] keyBuffer = Converter.wcsToMbcs ("cups-Duplex", true);
			byte [] valueBuffer = Converter.wcsToMbcs (cupsDuplexType, true);
			GTK.gtk_print_settings_set(settings, keyBuffer, valueBuffer);
		}
	}
	int orientation = data.orientation == PrinterData.LANDSCAPE ? GTK.GTK_PAGE_ORIENTATION_LANDSCAPE : GTK.GTK_PAGE_ORIENTATION_PORTRAIT;
	GTK.gtk_page_setup_set_orientation(pageSetup, orientation);
	GTK.gtk_print_settings_set_orientation(settings, orientation);
	super.init ();
}

/**
 * Returns a <code>PrinterData</code> object representing the
 * target printer for this print job.
 *
 * @return a PrinterData object describing the receiver
 */
public PrinterData getPrinterData() {
	checkDevice();
	return data;
}

}
