summaryrefslogtreecommitdiff
path: root/demos
diff options
context:
space:
mode:
authorThomas Deutschmann <whissi@gentoo.org>2020-09-10 18:10:49 +0200
committerThomas Deutschmann <whissi@gentoo.org>2020-09-11 20:06:36 +0200
commitacfc02c1747065fe450c7cfeb6f1844b62335f08 (patch)
tree5887806a2e6b99bbb0255e013a9028810e230a7f /demos
parentImport Ghostscript 9.52 (diff)
downloadghostscript-gpl-patches-acfc02c1747065fe450c7cfeb6f1844b62335f08.tar.gz
ghostscript-gpl-patches-acfc02c1747065fe450c7cfeb6f1844b62335f08.tar.bz2
ghostscript-gpl-patches-acfc02c1747065fe450c7cfeb6f1844b62335f08.zip
Import Ghostscript 9.53ghostscript-9.53
Signed-off-by: Thomas Deutschmann <whissi@gentoo.org>
Diffstat (limited to 'demos')
-rw-r--r--demos/c/ReadMe.txt31
-rw-r--r--demos/c/api_test.c1157
-rw-r--r--demos/c/api_test.vcxproj239
-rw-r--r--demos/c/api_test.vcxproj.filters14
-rw-r--r--demos/csharp/README.txt32
-rw-r--r--demos/csharp/api/ghostapi.cs199
-rw-r--r--demos/csharp/api/ghostmono.cs1088
-rw-r--r--demos/csharp/api/ghostnet.cs1198
-rw-r--r--demos/csharp/linux/gs_mono.sln17
-rw-r--r--demos/csharp/linux/gtk_viewer/Properties/AssemblyInfo.cs26
-rw-r--r--demos/csharp/linux/gtk_viewer/gtk-gui/generated.cs30
-rw-r--r--demos/csharp/linux/gtk_viewer/gtk-gui/gtk_viewer.src.gsOutput.cs10
-rw-r--r--demos/csharp/linux/gtk_viewer/gtk-gui/gui.stetic18
-rw-r--r--demos/csharp/linux/gtk_viewer/gtk_viewer.csproj73
-rw-r--r--demos/csharp/linux/gtk_viewer/gtk_viewer.sln17
-rw-r--r--demos/csharp/linux/gtk_viewer/src/DocPage.cs108
-rw-r--r--demos/csharp/linux/gtk_viewer/src/MainRender.cs136
-rw-r--r--demos/csharp/linux/gtk_viewer/src/MainThumbRendering.cs91
-rw-r--r--demos/csharp/linux/gtk_viewer/src/MainWindow.cs713
-rw-r--r--demos/csharp/linux/gtk_viewer/src/MainZoom.cs203
-rw-r--r--demos/csharp/linux/gtk_viewer/src/Program.cs25
-rw-r--r--demos/csharp/linux/gtk_viewer/src/TempFile.cs34
-rw-r--r--demos/csharp/linux/gtk_viewer/src/gsOutput.cs41
-rw-r--r--demos/csharp/windows/ghostnet.sln37
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/About.xaml94
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/About.xaml.cs254
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/App.config6
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/App.xaml9
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/App.xaml.cs17
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/DocPage.cs109
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/MainPrint.cs187
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/MainRender.cs135
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/MainThumbRendering.cs116
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml214
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml.cs739
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/MainZoom.cs168
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml148
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml.cs517
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/Properties/AssemblyInfo.cs55
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.Designer.cs71
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.resx117
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.Designer.cs30
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.settings7
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/TempFile.cs34
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/XPSprint.cs587
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/ghostnet_simple_viewer.csproj194
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/gsIO.cs29
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml28
-rw-r--r--demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml.cs49
-rw-r--r--demos/python/README.txt17
-rwxr-xr-xdemos/python/examples.py171
-rwxr-xr-xdemos/python/gsapi.py1049
-rwxr-xr-xdemos/python/gsapiwrap.py699
-rw-r--r--demos/python/jlib.py1355
54 files changed, 12742 insertions, 0 deletions
diff --git a/demos/c/ReadMe.txt b/demos/c/ReadMe.txt
new file mode 100644
index 00000000..16c4d9de
--- /dev/null
+++ b/demos/c/ReadMe.txt
@@ -0,0 +1,31 @@
+ api_test
+ ~~~~~~~~
+
+This is a simple VS2019 project that loads the gpdl dll and drives
+it via the gsapi functions.
+
+The first test feeds a variety of input formats into gpdl to create
+output PDF files. Next, mixtures of different format files are fed
+into the same instance, hence generating output PDF files collated
+from different sources.
+
+Finally, the display device is driven in a range of different
+formats, testing different alignments, chunky/planar formats,
+spot colors, and full page/rectangle request modes.
+
+These tests take a while to run, and will result in the outputs
+being given as apitest*.{pnm,pam,png,pdf}.
+
+A good quick test of the results is to run:
+
+ md5sum apitest*
+
+and you should see that the bitmaps produced group nicely into
+having the same md5sum values according to their color depths.
+
+The same code should compile and run on unix, but has not been
+tested there. Some fiddling to load the DLL may be required.
+
+Building with GHOSTPDL=0 will allow the Ghostscript DLL to be
+tested. The VS2019 project will need to be edited to use the
+appropriate .lib file too.
diff --git a/demos/c/api_test.c b/demos/c/api_test.c
new file mode 100644
index 00000000..8cc61e78
--- /dev/null
+++ b/demos/c/api_test.c
@@ -0,0 +1,1157 @@
+#ifdef _WIN32
+/* Stop windows builds complaining about sprintf being insecure. */
+#define _CRT_SECURE_NO_WARNINGS
+/* Ensure the dll import works correctly. */
+#define _WINDOWS_
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#ifndef GHOSTPDL
+#define GHOSTPDL 1
+#endif
+
+#if GHOSTPDL
+#include "pcl/pl/plapi.h" /* GSAPI - gpdf version */
+#else
+#include "psi/iapi.h" /* GSAPI - ghostscript version */
+#endif
+#include "devices/gdevdsp.h"
+
+/* In order to get consistent printing of pointers, we can't just
+ * use %p, as this includes the 0x on some platforms, and not on
+ * others. We therefore use a bit of #ifdeffery to get us a
+ * consistent result. */
+#if defined(_WIN64) || defined(_WIN32)
+ #define FMT_PTR "I64x"
+ #define PTR_CAST (__int64)(size_t)(intptr_t)
+ #define FMT_Z "I64d"
+ #define Z_CAST (__int64)
+#else
+ #define FMT_PTR "llx"
+ #define PTR_CAST (long long)(size_t)(intptr_t)
+ #define FMT_Z "lld"
+ #define Z_CAST (long long)
+#endif
+
+#define INSTANCE_HANDLE ((void *)1234)
+#define SANITY_CHECK_VALUE 0x12345678
+
+#define SANITY_CHECK(ts) assert(ts->sanity_check_value == SANITY_CHECK_VALUE)
+
+/* All the state for a given test is contained within the following
+ * structure. */
+typedef struct {
+ /* This value should always be set to SANITY_CHECK_VALUE. It
+ * allows us to check we have a valid (or at least plausible)
+ * teststate_t pointer by checking its value. */
+ int sanity_check_value;
+
+ int use_clist;
+ int legacy;
+
+ int w;
+ int h;
+ int r;
+ int pr;
+ int bh;
+ int y;
+ int lines_requested;
+ int format;
+ int align;
+
+ int n;
+ void *mem;
+ FILE *file;
+ const char *fname;
+} teststate_t;
+
+/*--------------------------------------------------------------------*/
+/* First off, we have a set of functions that cope with dumping lines
+ * of rendered data to file. We use pnm formats for their simplicity. */
+
+/* This function opens the file, named appropriately, and writes the
+ * header. */
+static FILE *save_header(teststate_t *ts)
+{
+ char text[32];
+ const char *suffix;
+ const char *align_str;
+
+ /* Only output the header once. */
+ if (ts->file != NULL)
+ return ts->file;
+
+ switch (ts->n)
+ {
+ case 1:
+ suffix = "pgm";
+ break;
+ case 3:
+ suffix = "ppm";
+ break;
+ case 4:
+ default:
+ suffix = "pam";
+ break;
+ }
+
+ switch (ts->format & DISPLAY_ROW_ALIGN_MASK) {
+ default:
+ case DISPLAY_ROW_ALIGN_DEFAULT:
+ align_str = "";
+ break;
+ case DISPLAY_ROW_ALIGN_4:
+ align_str = "_4";
+ break;
+ case DISPLAY_ROW_ALIGN_8:
+ align_str = "_8";
+ break;
+ case DISPLAY_ROW_ALIGN_16:
+ align_str = "_16";
+ break;
+ case DISPLAY_ROW_ALIGN_32:
+ align_str = "_32";
+ break;
+ case DISPLAY_ROW_ALIGN_64:
+ align_str = "_64";
+ break;
+ }
+
+ sprintf(text, "%s%s%s%s.%s",
+ ts->fname,
+ ts->use_clist ? "_c" : "",
+ ts->legacy ? "_l" : "",
+ align_str,
+ suffix);
+ ts->file = fopen(text, "wb");
+ if (ts->file == NULL) {
+ fprintf(stderr, "Fatal error: couldn't open %s for writing.\n", text);
+ exit(1);
+ }
+
+ switch (ts->n)
+ {
+ case 1:
+ fprintf(ts->file,
+ "P5\n%d %d\n255\n",
+ ts->w, ts->h);
+ break;
+ case 3:
+ fprintf(ts->file,
+ "P6\n%d %d\n255\n",
+ ts->w, ts->h);
+ break;
+ case 4:
+ default:
+ fprintf(ts->file,
+ "P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL 255\n"
+ "%s"
+ "ENDHDR\n",
+ ts->w, ts->h, ts->n, ts->n == 4 ? "TUPLTYPE CMYK\n" : "");
+ break;
+ }
+
+ /* If we're getting the lines in BOTTOMFIRST order, then pad out
+ * the file to the required length. We'll gradually backtrack
+ * through the file filling it in with real data as it arrives. */
+ if (ts->format && DISPLAY_BOTTOMFIRST) {
+ static const char blank[256] = { 0 };
+ int n = ts->w * ts->h * ts->n;
+ while (n > 0) {
+ int i = n;
+ if (i > sizeof(blank))
+ i = sizeof(blank);
+ fwrite(blank, 1, i, ts->file);
+ n -= i;
+ }
+ }
+
+ return ts->file;
+}
+
+/* Write out the next h lines from the buffer. */
+static void save_lines(teststate_t *ts, int h)
+{
+ int i, j, k;
+ const char *m = ts->mem;
+ int w = ts->w;
+ int n = ts->n;
+ int r = ts->r;
+ int pr = ts->pr;
+ int wn = w*n;
+ /* Make sure we've put a header on the file. */
+ FILE *file = save_header(ts);
+
+ /* If the data is being given in "little endian" format then
+ * reorder it as we write it out. This is required to cope with
+ * the fact that windows bitmaps prefer BGR over RGB.
+ *
+ * If we are getting data BOTTOMFIRST, then we will already be
+ * positioned at the end of the file. Backtrack through the file
+ * as we go so that the lines appear in a sane order.
+ */
+ if (ts->format & (DISPLAY_PLANAR | DISPLAY_PLANAR_INTERLEAVED)) {
+ /* Planar */
+ if (ts->format & DISPLAY_LITTLEENDIAN) {
+ for (i = 0; i < h; i++) {
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ for (j = 0; j < w; j++) {
+ for (k = n; k > 0;)
+ fputc(m[--k * pr], file);
+ m++;
+ }
+ m += r - w;
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ }
+ } else {
+ for (i = 0; i < h; i++) {
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ for (j = 0; j < w; j++) {
+ for (k = 0; k < n; k++)
+ fputc(m[k * pr], file);
+ m++;
+ }
+ m += r - w;
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ }
+ }
+ } else {
+ /* Chunky */
+ if (ts->format & DISPLAY_LITTLEENDIAN) {
+ for (i = 0; i < h; i++) {
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ for (j = 0; j < w; j++) {
+ for (k = n; k > 0;)
+ fputc(m[--k], file);
+ m += n;
+ }
+ m += r - wn;
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ }
+ } else {
+ for (i = 0; i < h; i++) {
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ fwrite(m, 1, wn, file);
+ m += r;
+ if (ts->format & DISPLAY_BOTTOMFIRST)
+ fseek(file, -wn, SEEK_CUR);
+ }
+ }
+ }
+}
+
+/* Finish writing out. */
+static void save_end(teststate_t *ts)
+{
+ fclose(ts->file);
+ ts->file = NULL;
+}
+
+/*--------------------------------------------------------------------*/
+/* Next we have the implementations of the callback functions. */
+
+static int
+open(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("open\n");
+
+ return 0;
+}
+
+static int
+preclose(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("preclose\n");
+
+ return 0;
+}
+
+static int
+close(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("close\n");
+
+ return 0;
+}
+
+static int
+presize(void *handle, void *device,
+ int width, int height, int raster, unsigned int format)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("presize: w=%d h=%d r=%d f=%x\n",
+ width, height, raster, format);
+
+ ts->w = width;
+ ts->h = height;
+ ts->r = raster;
+ ts->bh = 0;
+ ts->y = 0;
+ ts->format = format;
+
+ if (ts->format & DISPLAY_COLORS_GRAY)
+ ts->n = 1;
+ if (ts->format & DISPLAY_COLORS_RGB)
+ ts->n = 3;
+ if (ts->format & DISPLAY_COLORS_CMYK)
+ ts->n = 4;
+ if (ts->format & DISPLAY_COLORS_SEPARATION)
+ ts->n = 0;
+ if ((ts->format & DISPLAY_DEPTH_MASK) != DISPLAY_DEPTH_8)
+ return -1; /* Haven't written code for that! */
+
+ return 0;
+}
+
+static int
+size(void *handle, void *device, int width, int height,
+ int raster, unsigned int format, unsigned char *pimage)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("size: w=%d h=%d r=%d f=%x m=%p\n",
+ width, height, raster, format, pimage);
+ ts->w = width;
+ ts->h = height;
+ ts->r = raster;
+ ts->format = format;
+ ts->mem = pimage;
+
+ if (ts->format & DISPLAY_PLANAR)
+ ts->pr = ts->r * height;
+ /* When running with spots, n is not known yet. */
+ if (ts->n != 0 && ts->format & DISPLAY_PLANAR_INTERLEAVED)
+ ts->pr = ts->r / ts->n;
+
+ return 0;
+}
+
+static int
+sync(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("sync\n");
+
+ return 0;
+}
+
+static int
+page(void *handle, void *device, int copies, int flush)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("page: c=%d f=%d\n", copies, flush);
+
+ save_lines(ts, ts->h);
+ save_end(ts);
+
+ return 0;
+}
+
+static int
+update(void *handle, void *device, int x, int y, int w, int h)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ /* This print statement just makes too much noise :) */
+ /* printf("update: x=%d y=%d w=%d h=%d\n", x, y, w, h); */
+
+ return 0;
+}
+
+void *
+aligned_malloc(size_t size, int alignment)
+{
+ char *ret = malloc(size + alignment*2);
+ int boost;
+
+ if (ret == NULL)
+ return ret;
+
+ boost = alignment - (((intptr_t)ret) & (alignment-1));
+ memset(ret, boost, boost);
+
+ return ret + boost;
+}
+
+void aligned_free(void *ptr)
+{
+ char *p = ptr;
+
+ if (ptr == NULL)
+ return;
+
+ p -= p[-1];
+ free(p);
+}
+
+static void *
+memalloc(void *handle, void *device, size_t size)
+{
+ void *ret = NULL;
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+
+ if (ts->use_clist) {
+ printf("memalloc: asked for %"FMT_Z" but requesting clist\n", Z_CAST size);
+ return 0;
+ }
+
+ ret = aligned_malloc(size, 64);
+ printf("memalloc: %"FMT_Z" -> %p\n", Z_CAST size, ret);
+
+ return ret;
+}
+
+static int
+memfree(void *handle, void *device, void *mem)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("memfree: %p\n", mem);
+ aligned_free(mem);
+
+ return 0;
+}
+
+static int
+separation(void *handle, void *device,
+ int component, const char *component_name,
+ unsigned short c, unsigned short m,
+ unsigned short y, unsigned short k)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("separation: %d %s (%x,%x,%x,%x)\n",
+ component, component_name ? component_name : "<NULL>",
+ c, m, y, k);
+ ts->n++;
+
+ /* Update the plane_raster as n has changed. */
+ if (ts->format & DISPLAY_PLANAR_INTERLEAVED)
+ ts->pr = ts->r / ts->n;
+
+ return 0;
+}
+
+static int
+adjust_band_height(void *handle, void *device, int bandheight)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ printf("adjust_band_height: %d - >", bandheight);
+
+ if (bandheight > ts->h / 4)
+ bandheight = ts->h / 4;
+
+ printf("%d\n", bandheight);
+
+ ts->bh = bandheight;
+
+ return bandheight;
+}
+
+static int
+rectangle_request(void *handle, void *device,
+ void **memory, int *ox, int *oy,
+ int *raster, int *plane_raster,
+ int *x, int *y, int *w, int *h)
+{
+ teststate_t *ts = (teststate_t *)handle;
+ size_t size;
+
+ SANITY_CHECK(ts);
+ printf("rectangle_request:");
+
+ if (ts->mem) {
+ /* Rectangle returned */
+ save_lines(ts, ts->lines_requested);
+ aligned_free(ts->mem);
+ ts->mem = NULL;
+ ts->y += ts->lines_requested;
+ ts->lines_requested = 0;
+ }
+
+ if (ts->y >= ts->h)
+ {
+ /* All banded out */
+ printf("Finished!\n");
+ *ox = 0;
+ *oy = 0;
+ *raster = 0;
+ *plane_raster = 0;
+ *x = 0;
+ *y = 0;
+ *w = 0;
+ *h = 0;
+ *memory = NULL;
+ save_end(ts);
+ return 0;
+ }
+ *ox = 0;
+ *oy = ts->y;
+ *x = 0;
+ *y = ts->y;
+ *w = ts->w;
+ *h = ts->bh;
+ if (ts->y + ts->bh > ts->h)
+ *h = ts->h - ts->y;
+ ts->lines_requested = *h;
+ switch (ts->format & (DISPLAY_PLANAR | DISPLAY_PLANAR_INTERLEAVED)) {
+ case DISPLAY_CHUNKY:
+ ts->r = (ts->w * ts->n + ts->align-1) & ~(ts->align-1);
+ ts->pr = 0;
+ size = ts->r * *h;
+ break;
+ case DISPLAY_PLANAR:
+ ts->r = (ts->w + ts->align-1) & ~(ts->align-1);
+ ts->pr = ts->r * *h;
+ size = ts->pr * ts->n;
+ break;
+ case DISPLAY_PLANAR_INTERLEAVED:
+ ts->pr = (ts->w + ts->align-1) & ~(ts->align-1);
+ ts->r = ts->pr * ts->n;
+ size = ts->r * *h;
+ break;
+ }
+ *raster = ts->r;
+ *plane_raster = ts->pr;
+ ts->mem = aligned_malloc(size, 64);
+ *memory = ts->mem;
+
+ printf("x=%d y=%d w=%d h=%d mem=%p\n", *x, *y, *w, *h, *memory);
+ if (ts->mem == NULL)
+ return -1;
+
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
+/* All those callback functions live in a display_callback structure
+ * that we return to the main code. This can be done using the modern
+ * "callout" method, or by using the legacy (deprecated) direct
+ * registration method. We strongly prefer the callout method as it
+ * avoids the need to pass a pointer using -sDisplayHandle. */
+static display_callback callbacks =
+{
+ sizeof(callbacks),
+ DISPLAY_VERSION_MAJOR,
+ DISPLAY_VERSION_MINOR,
+ open,
+ preclose,
+ close,
+ presize,
+ size,
+ sync,
+ page,
+ update,
+ memalloc,
+ memfree,
+ separation,
+ adjust_band_height,
+ rectangle_request
+};
+
+/*--------------------------------------------------------------------*/
+/* This is our callout handler. It handles callouts from devices within
+ * Ghostscript. It only handles a single callout, from the display
+ * device, to return the callback handler and callback handle. */
+static int
+callout(void *instance,
+ void *callout_handle,
+ const char *device_name,
+ int id,
+ int size,
+ void *data)
+{
+ teststate_t *ts = (teststate_t *)callout_handle;
+
+ SANITY_CHECK(ts);
+
+ /* We are only interested in callouts from the display device. */
+ if (strcmp(device_name, "display"))
+ return -1;
+
+ if (id == DISPLAY_CALLOUT_GET_CALLBACK)
+ {
+ /* Fill in the supplied block with the details of our callback
+ * handler, and the handle to use. In this instance, the handle
+ * is the pointer to our test structure. */
+ gs_display_get_callback_t *cb = (gs_display_get_callback_t *)data;
+ cb->callback = &callbacks;
+ cb->caller_handle = ts;
+ return 0;
+ }
+ return -1;
+}
+
+/*--------------------------------------------------------------------*/
+/* This is the function that actually runs a test. */
+static int do_ddtest(const char *title, int format,
+ int use_clist, int legacy,
+ const char *fname)
+{
+ int code;
+ void *instance = NULL;
+ char *clist_str = use_clist ? " (clist)" : "";
+ char *legacy_str = legacy ? " (legacy)" : "";
+ char *align_str = "";
+ char format_arg[64];
+ char handle_arg[64];
+
+ /* Make the teststate a blank slate for us to work with. */
+ teststate_t teststate = { SANITY_CHECK_VALUE };
+
+ /* Construct the argc/argv to pass to ghostscript. */
+ int argc = 0;
+ char *argv[10];
+
+ argv[argc++] = "gs";
+ argv[argc++] = "-sDEVICE=display";
+ argv[argc++] = "-dNOPAUSE";
+ argv[argc++] = format_arg;
+ if (legacy)
+ argv[argc++] = handle_arg;
+ if (format & DISPLAY_COLORS_SEPARATION)
+ argv[argc++] = "../../examples/spots.ps";
+ else
+ argv[argc++] = "../../examples/tiger.eps";
+
+ sprintf(format_arg, "-dDisplayFormat=16#%x", format);
+ sprintf(handle_arg, "-sDisplayHandle=16#%" FMT_PTR, PTR_CAST &teststate);
+
+ /* Setup the details to control the test. */
+ teststate.use_clist = use_clist;
+ teststate.legacy = legacy;
+ teststate.fname = fname;
+
+ switch (format & DISPLAY_ROW_ALIGN_MASK) {
+ default:
+ case DISPLAY_ROW_ALIGN_DEFAULT:
+ align_str = " (default alignment)";
+ teststate.align = 0;
+ break;
+ case DISPLAY_ROW_ALIGN_4:
+ align_str = " (align % 4)";
+ teststate.align = 4;
+ break;
+ case DISPLAY_ROW_ALIGN_8:
+ align_str = " (align % 8)";
+ teststate.align = 8;
+ break;
+ case DISPLAY_ROW_ALIGN_16:
+ align_str = " (align % 16)";
+ teststate.align = 16;
+ break;
+ case DISPLAY_ROW_ALIGN_32:
+ align_str = " (align % 32)";
+ teststate.align = 32;
+ break;
+ case DISPLAY_ROW_ALIGN_64:
+ align_str = " (align % 64)";
+ teststate.align = 64;
+ break;
+ }
+ /* Special case: alignments are always at least pointer sized. */
+ if (teststate.align <= sizeof(void *))
+ teststate.align = sizeof(void *);
+
+ /* Print the test title. */
+ printf("%s%s%s%s\n", title, clist_str, legacy_str, align_str);
+
+ /* Create a GS instance. */
+ code = gsapi_new_instance(&instance, INSTANCE_HANDLE);
+ if (code < 0) {
+ printf("Error %d in gsapi_new_instance\n", code);
+ goto failearly;
+ }
+
+ if (legacy) {
+ /* Directly pass in the callback structure. This relies on the
+ * handle being passed using -sDisplayHandle above. */
+ code = gsapi_set_display_callback(instance, &callbacks);
+ if (code < 0) {
+ printf("Error %d in gsapi_set_display_callback\n", code);
+ goto fail;
+ }
+ } else {
+ /* Register our callout handler. This will pass the display
+ * device the callback structure and handle when requested. */
+ code = gsapi_register_callout(instance, callout, &teststate);
+ if (code < 0) {
+ printf("Error %d in gsapi_register_callout\n", code);
+ goto fail;
+ }
+ }
+
+ /* Run our test. */
+ code = gsapi_init_with_args(instance, argc, argv);
+ if ((format & DISPLAY_ROW_ALIGN_MASK) == DISPLAY_ROW_ALIGN_4 &&
+ sizeof(void *) > 4) {
+ if (code == -100) {
+ printf("Got expected failure!\n");
+ code = 0;
+ goto fail;
+ } else if (code == 0) {
+ printf("Failed to get expected failure!\n");
+ code = -1;
+ goto fail;
+ }
+ }
+ if (code < 0) {
+ printf("Error %d in gsapi_init_with_args\n", code);
+ goto fail;
+ }
+
+ /* Close the interpreter down (important, or we will leak!) */
+ code = gsapi_exit(instance);
+ if (code < 0) {
+ printf("Error %d in gsapi_exit\n", code);
+ goto fail;
+ }
+
+fail:
+ /* Delete the gs instance. */
+ gsapi_delete_instance(instance);
+
+failearly:
+ /* All done! */
+ printf("%s%s%s%s %s\n", title, clist_str, legacy_str, align_str,
+ (code < 0) ? "failed" : "complete");
+
+ return code;
+}
+
+static int displaydev_test(const char *title, int format, const char *fname)
+{
+ int use_clist, legacy, align, code;
+
+ code = 0;
+ for (use_clist = 0; use_clist <= 1; use_clist++) {
+ for (legacy = 0; legacy <= 1; legacy++) {
+ for (align = 2; align <= 7; align++) {
+ int form = format;
+ if (align != 2) {
+ form |= align<<20;
+ }
+ code = do_ddtest(title, form, use_clist, legacy, fname);
+ if (code < 0)
+ return code;
+ }
+ }
+ }
+ return code;
+}
+
+static int
+runstring_test(const char *dev, char *outfile, ...)
+{
+ int code;
+ void *instance = NULL;
+ char devtext[64];
+ va_list args;
+ char *infile;
+ int error_code;
+
+ /* Construct the argc/argv to pass to ghostscript. */
+ int argc = 0;
+ char *argv[10];
+
+ sprintf(devtext, "-sDEVICE=%s", dev);
+ argv[argc++] = "gpdl";
+ argv[argc++] = devtext;
+ argv[argc++] = "-o";
+ argv[argc++] = outfile;
+
+ /* Create a GS instance. */
+ code = gsapi_new_instance(&instance, INSTANCE_HANDLE);
+ if (code < 0) {
+ printf("Error %d in gsapi_new_instance\n", code);
+ goto failearly;
+ }
+
+ /* Run our test. */
+ code = gsapi_init_with_args(instance, argc, argv);
+ if (code < 0) {
+ printf("Error %d in gsapi_init_with_args\n", code);
+ goto fail;
+ }
+
+ va_start(args, outfile);
+ while ((infile = va_arg(args, char *)) != NULL) {
+ printf("Feeding %s via runstring\n", infile);
+ code = gsapi_run_string_begin(instance, 0, &error_code);
+ if (code < 0) {
+ printf("Error %d in gsapi_run_string_begin\n", code);
+ goto fail;
+ }
+
+ {
+ FILE *file = fopen(infile, "rb");
+ char block[1024];
+ unsigned int len;
+ if (file == NULL) {
+ printf("Error: Failed to open %s for reading\n", infile);
+ code = -1;
+ goto fail;
+ }
+ while (!feof(file)) {
+ len = (unsigned int)fread(block, 1, 1024, file);
+
+ code = gsapi_run_string_continue(instance, block, len,
+ 0, &error_code);
+ if (code < 0) {
+ printf("Error %d in gsapi_run_string_continue\n", code);
+ goto fail;
+ }
+ }
+ }
+
+ code = gsapi_run_string_end(instance, 0, &error_code);
+ if (code < 0) {
+ printf("Error %d in gsapi_run_string_end\n", code);
+ goto fail;
+ }
+ }
+ va_end(args);
+
+ /* Close the interpreter down (important, or we will leak!) */
+ code = gsapi_exit(instance);
+ if (code < 0) {
+ printf("Error %d in gsapi_exit\n", code);
+ goto fail;
+ }
+
+fail:
+ /* Delete the gs instance. */
+ gsapi_delete_instance(instance);
+
+failearly:
+
+ return code;
+}
+
+char *types[] = {
+ "null",
+ "bool",
+ "int",
+ "float",
+ "name",
+ "string",
+ "long",
+ "i64",
+ "size_t",
+ "parsed"
+};
+
+static int
+list_params(void *instance)
+{
+ void *iter = NULL;
+ char *key;
+ gs_set_param_type type;
+ char buffer[1024];
+ int code;
+
+ while ((code = gsapi_enumerate_params(instance, &iter, &key, &type)) == 0) {
+ printf("Key=%s, type=%s: ", key, type >= 0 && type <= 9 ? types[type] : "invalid");
+ code = gsapi_get_param(instance, key, NULL, gs_spt_parsed);
+ if (code < 0)
+ break;
+ if (code > sizeof(buffer)) {
+ printf("<overly long value>\n");
+ continue;
+ }
+ code = gsapi_get_param(instance, key, buffer, gs_spt_parsed);
+ if (code < 0)
+ break;
+ printf("%s\n", buffer);
+ }
+ return code;
+}
+
+static int
+param_test(const char *dev, char *outfile)
+{
+ int code, len;
+ void *instance = NULL;
+ char devtext[64];
+ char buffer[4096];
+
+ /* Construct the argc/argv to pass to ghostscript. */
+ int argc = 0;
+ char *argv[10];
+ int i;
+
+ sprintf(devtext, "-sDEVICE=%s", dev);
+ argv[argc++] = "gpdl";
+ argv[argc++] = devtext;
+ argv[argc++] = "-o";
+ argv[argc++] = outfile;
+
+ /* Create a GS instance. */
+ code = gsapi_new_instance(&instance, INSTANCE_HANDLE);
+ if (code < 0) {
+ printf("Error %d in gsapi_new_instance\n", code);
+ goto failearly;
+ }
+
+ code = gsapi_set_param(instance, "Foo", "0", gs_spt_parsed);
+ if (code < 0) {
+ printf("Got error from early param setting.\n");
+ goto fail;
+ }
+
+ /* List the params: */
+ code = list_params(instance);
+ if (code < 0) {
+ printf("Error %d while listing params\n", code);
+ goto fail;
+ }
+
+ /* Run our test. */
+ code = gsapi_init_with_args(instance, argc, argv);
+ if (code < 0) {
+ printf("Error %d in gsapi_init_with_args\n", code);
+ goto fail;
+ }
+
+ code = gsapi_set_param(instance, "Bar", "1", gs_spt_parsed | gs_spt_more_to_come);
+ if (code < 0) {
+ printf("Error %d in gsapi_set_param\n", code);
+ goto fail;
+ }
+
+ code = gsapi_set_param(instance, "Baz", "<</Test[0 1 2.3]/Charm(>>)/Vixen<01234567>/Scented/Ephemeral>>", gs_spt_parsed);
+ if (code < 0) {
+ printf("Error %d in gsapi_set_param\n", code);
+ goto fail;
+ }
+
+ /* This should fail, as /Baz is not an expected param. */
+ code = gsapi_get_param(instance, "Baz", buffer, gs_spt_parsed);
+ if (code == -21) {
+ printf("Got expected error gsapi_get_param\n");
+ } else {
+ printf("Error %d in gsapi_get_param\n", code);
+ goto fail;
+ }
+
+ i = 32;
+ code = gsapi_set_param(instance, "foo", (void *)&i, gs_spt_int);
+ if (code < 0) {
+ printf("Error %d in gsapi_set_param\n", code);
+ goto fail;
+ }
+
+ code = gsapi_get_param(instance, "foo", (void *)&i, gs_spt_int);
+ if (code == -21)
+ printf("Got expected error gsapi_get_param\n");
+ else if (code < 0) {
+ printf("Error %d in gsapi_set_param\n", code);
+ goto fail;
+ }
+
+ code = gsapi_set_param(instance, "GrayImageDict", "<</QFactor 0.1 /Blend 0/HSamples [1 1 1 1] /VSamples [ 1 1 1 1 ] /Foo[/A/B/C/D/E] /Bar (123) /Baz <0123> /Sp#20ce /D#7fl>>", gs_spt_parsed);
+ if (code < 0) {
+ printf("Error %d in gsapi_set_param\n", code);
+ goto fail;
+ }
+
+ code = gsapi_get_param(instance, "GrayImageDict", NULL, gs_spt_parsed);
+ if (code < 0) {
+ printf("Error %d in gsapi_get_param\n", code);
+ goto fail;
+ }
+ len = code;
+ buffer[len-1] = 98;
+ buffer[len] = 99;
+ code = gsapi_get_param(instance, "GrayImageDict", buffer, gs_spt_parsed);
+ if (code < 0) {
+ printf("Error %d in gsapi_get_param\n", code);
+ goto fail;
+ }
+ if (buffer[len] != 99 || buffer[len-1] != 0) {
+ printf("Bad buffer return");
+ goto fail;
+ }
+ if (strcmp(buffer, "<</Sp#20ce/D#7Fl/Baz<0123>/Bar(123)/Foo[/A/B/C/D/E]/VSamples[1 1 1 1]/HSamples[1 1 1 1]/Blend 0/QFactor 0.1>>")) {
+ printf("Bad value return");
+ goto fail;
+ }
+
+ /* List the params: */
+ code = list_params(instance);
+ if (code < 0) {
+ printf("Error %d in while listing params\n", code);
+ goto fail;
+ }
+
+ /* Close the interpreter down (important, or we will leak!) */
+ code = gsapi_exit(instance);
+ if (code < 0) {
+ printf("Error %d in gsapi_exit\n", code);
+ goto fail;
+ }
+
+fail:
+ /* Delete the gs instance. */
+ gsapi_delete_instance(instance);
+
+failearly:
+
+ return code;
+}
+
+static int
+res_change_test(const char *dev, char *outfile)
+{
+ int code, dummy;
+ void *instance = NULL;
+ char devtext[64];
+
+ /* Construct the argc/argv to pass to ghostscript. */
+ int argc = 0;
+ char *argv[10];
+
+ sprintf(devtext, "-sDEVICE=%s", dev);
+ argv[argc++] = "gpdl";
+ argv[argc++] = devtext;
+ argv[argc++] = "-o";
+ argv[argc++] = outfile;
+ argv[argc++] = "-r100";
+ argv[argc++] = "../../examples/tiger.eps";
+
+ /* Create a GS instance. */
+ code = gsapi_new_instance(&instance, INSTANCE_HANDLE);
+ if (code < 0) {
+ printf("Error %d in gsapi_new_instance\n", code);
+ goto failearly;
+ }
+
+ /* Run our test. */
+ code = gsapi_init_with_args(instance, argc, argv);
+ if (code < 0) {
+ printf("Error %d in gsapi_init_with_args\n", code);
+ goto fail;
+ }
+
+ code = gsapi_set_param(instance, "HWResolution", "[200 200]", gs_spt_parsed);
+ if (code < 0) {
+ printf("Error %d in gsapi_set_param\n", code);
+ goto fail;
+ }
+
+ code = gsapi_run_file(instance, "../../examples/tiger.eps", 0, &dummy);
+ if (code < 0) {
+ printf("Error %d in gsapi_run_file\n", code);
+ goto fail;
+ }
+
+ /* Close the interpreter down (important, or we will leak!) */
+ code = gsapi_exit(instance);
+ if (code < 0) {
+ printf("Error %d in gsapi_exit\n", code);
+ goto fail;
+ }
+
+fail:
+ /* Delete the gs instance. */
+ gsapi_delete_instance(instance);
+
+failearly:
+
+ return code;
+}
+
+int main(int argc, char *argv[])
+{
+ int code = 0;
+
+#define RUNTEST(A)\
+ if (code >= 0) code = (A)
+
+ RUNTEST(param_test("pdfwrite", "apitest20.pdf"));
+ RUNTEST(res_change_test("ppmraw", "apitest21_%d.ppm"));
+
+#define RS(A)\
+ RUNTEST(runstring_test A )
+
+ RS(("pdfwrite", "apitest12.pdf", "../../examples/tiger.eps", NULL));
+ RS(("pdfwrite", "apitest13.pdf", "../../examples/golfer.eps", NULL));
+ RS(("pdfwrite", "apitest14.pdf", "../../examples/tiger.eps",
+ "../../examples/golfer.eps",
+ NULL));
+#if GHOSTPDL
+ RS(("pdfwrite", "apitest15.pdf", "../../pcl/examples/tiger.px3", NULL));
+ RS(("pdfwrite", "apitest16.pdf", "../../xps/tools/tiger.xps", NULL));
+ RS(("pdfwrite", "apitest17.pdf", "../../pcl/examples/tiger.px3",
+ "../../examples/golfer.eps",
+ "../../xps/tools/tiger.xps",
+ NULL));
+ RS(("pdfwrite", "apitest18.pdf", "../../zlib/zlib.3.pdf", NULL));
+ RS(("pdfwrite", "apitest19.pdf", "../../pcl/examples/tiger.px3",
+ "../../examples/tiger.eps",
+ "../../examples/golfer.eps",
+ "../../zlib/zlib.3.pdf",
+ "../../xps/tools/tiger.xps",
+ NULL));
+#endif
+
+#define DD(STR, FMT, FILE)\
+ RUNTEST(displaydev_test(STR, FMT, FILE))
+
+ /* Run a variety of tests for the display device. */
+ DD("Chunky Windows Gray", 0x030802, "apitest0");
+ DD("Chunky Windows RGB", 0x030804, "apitest1");
+ /* Display device does no support "little endian" CMYK */
+ DD("Chunky Windows CMYK", 0x020808, "apitest2");
+
+ DD("Planar Windows Gray", 0x830802, "apitest3");
+ DD("Planar Windows RGB", 0x830804, "apitest4");
+ DD("Planar Windows CMYK", 0x820808, "apitest5");
+
+ DD("Planar Interleaved Windows Gray", 0x1030802, "apitest6");
+ DD("Planar Interleaved Windows RGB", 0x1030804, "apitest7");
+ DD("Planar Interleaved Windows CMYK", 0x1020808, "apitest8");
+
+ DD("Chunky Spots", 0x0A0800, "apitest9");
+ DD("Planar Spots", 0x8A0800, "apitest10");
+ DD("Planar Interleaved Spots", 0x10A0800, "apitest11");
+
+ return 0;
+}
diff --git a/demos/c/api_test.vcxproj b/demos/c/api_test.vcxproj
new file mode 100644
index 00000000..32d2a174
--- /dev/null
+++ b/demos/c/api_test.vcxproj
@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Memento|Win32">
+ <Configuration>Memento</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Memento|x64">
+ <Configuration>Memento</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <Keyword>Win32Proj</Keyword>
+ <ProjectGuid>{113ad66f-3533-47e5-8aa8-973d804443a0}</ProjectGuid>
+ <RootNamespace>apitest</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Memento|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Memento|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Memento|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Memento|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(ProjectDir)..\..\debugbin\</OutDir>
+ <TargetName>$(ProjectName)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Memento|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(ProjectDir)..\..\membin\</OutDir>
+ <TargetName>$(ProjectName)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(ProjectDir)..\..\bin\</OutDir>
+ <TargetName>$(ProjectName)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <TargetName>$(ProjectName)64</TargetName>
+ <OutDir>$(ProjectDir)..\..\debugbin\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Memento|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(ProjectDir)..\..\membin\</OutDir>
+ <TargetName>$(ProjectName)64</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(ProjectDir)..\..\bin\</OutDir>
+ <TargetName>$(ProjectName)64</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>..\..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>gpdldll32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>..\..\debugbin</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Memento|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>MEMENTO;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>..\..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>gpdldll32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>..\..\membin</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>..\..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>gpdldll32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>..\..\bin</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>..\..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>gpdldll64.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>..\..\debugbin</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Memento|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>MEMENTO;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>..\..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>gpdldll64.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>..\..\membin</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>..\..\bin</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="api_test.c" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/demos/c/api_test.vcxproj.filters b/demos/c/api_test.vcxproj.filters
new file mode 100644
index 00000000..359a1a0a
--- /dev/null
+++ b/demos/c/api_test.vcxproj.filters
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="api_test.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/demos/csharp/README.txt b/demos/csharp/README.txt
new file mode 100644
index 00000000..1665c7cd
--- /dev/null
+++ b/demos/csharp/README.txt
@@ -0,0 +1,32 @@
+The following projects show the use of the Ghostscript API
+in a C# environment. A WPF C# Windows viewer application is
+contained in the windows folder. A MONO C# Gtk viewer
+application is contained in the Linux folder.
+
+The applications share the same API file to Ghostscript or GhostPDL
+which is in api/ghostapi.cs. In this file, lib_dll is set to the appropriate
+dll or so file that is create by the the compilation of Ghostscript. Note
+that to build libgpdl.so on Linux you use "make so". On windows gpdldll64.dll
+and variants are built depending upon the VS solution configurations.
+
+The applications each have another level of interface which is api/ghostnet.cs
+for the Windows application and api/ghostmono.cs for the Linux application.
+These files assemble the commands that are to be executed, creates the working
+threads that GhostPDL/Ghostscript will run on as well as handling the call backs
+from GhostPDL/Ghostscript. The Linux and Windows applications use different
+threading methods.
+
+The Windows application includes the ability to print the document via
+the Windows XPS print pipeline. The application will call into GhostPDL/Ghostscript
+with the xpswrite device to create the XPS content.
+
+Both applications will offer the user the chance to distill any non-PDF files
+that are opened with the application.
+
+Both applications should at some point be slightly reworked to provide improved
+performance on rendering only the visible pages when PDF is the document source.
+PCL and PS file formats are streamed and so cannot work in this manner without
+a severe performance penalty.
+
+
+
diff --git a/demos/csharp/api/ghostapi.cs b/demos/csharp/api/ghostapi.cs
new file mode 100644
index 00000000..b65aac9e
--- /dev/null
+++ b/demos/csharp/api/ghostapi.cs
@@ -0,0 +1,199 @@
+using System; /* IntPtr */
+using System.Runtime.InteropServices; /* DLLImport */
+
+namespace GhostAPI
+{
+ public struct gsapi_revision_t
+ {
+ public IntPtr product;
+ public IntPtr copyright;
+ public int revision;
+ public int revisiondate;
+ }
+
+ public enum gs_set_param_type
+ {
+ gs_spt_invalid = -1,
+ gs_spt_null = 0, /* void * is NULL */
+ gs_spt_bool = 1, /* void * is NULL (false) or non-NULL (true) */
+ gs_spt_int = 2, /* void * is a pointer to an int */
+ gs_spt_float = 3, /* void * is a float * */
+ gs_spt_name = 4, /* void * is a char * */
+ gs_spt_string = 5, /* void * is a char * */
+ gs_spt_long = 6, /* void * is a long * */
+ gs_spt_i64 = 7, /* void * is a int64_t * */
+ gs_spt_size_t = 8 /* void * is a size_t * */
+ };
+
+ public enum gsEncoding
+ {
+ GS_ARG_ENCODING_LOCAL = 0,
+ GS_ARG_ENCODING_UTF8 = 1,
+ GS_ARG_ENCODING_UTF16LE = 2
+ };
+
+ static class gsConstants
+ {
+ public const int E_QUIT = -101;
+ public const int GS_READ_BUFFER = 32768;
+ public const int DISPLAY_UNUSED_LAST = (1 << 7);
+ public const int DISPLAY_COLORS_RGB = (1 << 2);
+ public const int DISPLAY_DEPTH_8 = (1 << 11);
+ public const int DISPLAY_LITTLEENDIAN = (1 << 16);
+ public const int DISPLAY_BIGENDIAN = (0 << 16);
+ }
+
+ class ghostapi
+ {
+#if MONO
+ private const string lib_dll = "libgpdl.so";
+#else
+#if WIN64
+#if !GHOSTPDL
+ private const string lib_dll = "gsdll64.dll";
+#else
+ private const string lib_dll = "gpdldll64.dll";
+#endif
+#else
+#if !GHOSTPDL
+ private const string lib_dll = "gsdll32.dll";
+#else
+ private const string lib_dll = "gpdldll32.dll";
+#endif
+#endif
+#endif
+ /* Callback proto for stdio */
+ public delegate int gsStdIOHandler(IntPtr caller_handle, IntPtr buffer, int len);
+
+ /* Callback proto for poll function */
+ public delegate int gsPollHandler(IntPtr caller_handle);
+
+ /* Callout proto */
+ public delegate int gsCallOut(IntPtr callout_handle, IntPtr device_name, int id, int size, IntPtr data);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_revision", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_revision(ref gsapi_revision_t vers, int size);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_new_instance", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_new_instance(out IntPtr pinstance,
+ IntPtr caller_handle);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_delete_instance", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern void gsapi_delete_instance(IntPtr instance);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_init_with_args", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_init_with_args(IntPtr instance, int argc,
+ IntPtr argv);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_exit", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_exit(IntPtr instance);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_arg_encoding", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_arg_encoding(IntPtr instance,
+ int encoding);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_stdio", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_stdio(IntPtr instance,
+ gsStdIOHandler stdin, gsStdIOHandler stdout, gsStdIOHandler stderr);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_stdio_with_handle", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_stdio_with_handle(IntPtr instance,
+ gsStdIOHandler stdin, gsStdIOHandler stdout, gsStdIOHandler stderr, IntPtr caller_handle);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_poll", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_poll(IntPtr instance, gsPollHandler pollfn);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_poll_with_handle", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_poll_with_handle(IntPtr instance, gsPollHandler pollfn,
+ IntPtr caller_handle);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_get_default_device_list", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_get_default_device_list(IntPtr instance,
+ ref IntPtr list, ref int listlen);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_default_device_list", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_default_device_list(IntPtr instance,
+ IntPtr list, ref int listlen);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_run_string", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_run_string(IntPtr instance, IntPtr command,
+ int usererr, ref int exitcode);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_run_string_with_length", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_run_string_with_length(IntPtr instance, IntPtr command,
+ uint length, int usererr, ref int exitcode);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_run_file", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_run_file(IntPtr instance, IntPtr filename,
+ int usererr, ref int exitcode);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_run_string_begin", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_run_string_begin(IntPtr instance,
+ int usererr, ref int exitcode);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_run_string_continue", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_run_string_continue(IntPtr instance,
+ IntPtr command, int count, int usererr, ref int exitcode);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_run_string_end", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_run_string_end(IntPtr instance,
+ int usererr, ref int exitcode);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_display_callback", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_display_callback(IntPtr pinstance, IntPtr caller_handle);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_add_control_path", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_add_control_path(IntPtr instance, int type, IntPtr path);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_remove_control_path", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_remove_control_path(IntPtr instance, int type, IntPtr path);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_purge_control_paths", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern void gsapi_purge_control_paths(IntPtr instance, int type);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_activate_path_control", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern void gsapi_activate_path_control(IntPtr instance, int enable);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_is_path_control_active", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_is_path_control_active(IntPtr instance);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_set_param", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_set_param(IntPtr instance, gs_set_param_type type,
+ IntPtr param, IntPtr value);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_register_callout", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_register_callout(IntPtr instance, gsCallOut callout,
+ IntPtr callout_handle);
+
+ [DllImport(lib_dll, EntryPoint = "gsapi_deregister_callout", CharSet = CharSet.Ansi,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int gsapi_deregister_callout(IntPtr instance, gsCallOut callout,
+ IntPtr callout_handle);
+ }
+}
diff --git a/demos/csharp/api/ghostmono.cs b/demos/csharp/api/ghostmono.cs
new file mode 100644
index 00000000..850ff039
--- /dev/null
+++ b/demos/csharp/api/ghostmono.cs
@@ -0,0 +1,1088 @@
+using System;
+using System.Runtime.InteropServices; /* Marshaling */
+using System.Threading;
+using System.Collections.Generic; /* Use of List */
+using System.IO; /* Use of path */
+using GhostAPI; /* Use of Ghostscript API */
+
+namespace GhostMono
+{
+ public enum GS_Task_t
+ {
+ PS_DISTILL,
+ CREATE_XPS,
+ SAVE_RESULT,
+ GET_PAGE_COUNT,
+ GENERIC,
+ DISPLAY_DEV_THUMBS_NON_PDF,
+ DISPLAY_DEV_THUMBS_PDF,
+ DISPLAY_DEV_NON_PDF,
+ DISPLAY_DEV_PDF,
+ }
+ public enum GS_Result_t
+ {
+ gsOK,
+ gsFAILED,
+ gsCANCELLED
+ }
+ public enum gsStatus
+ {
+ GS_READY,
+ GS_BUSY,
+ GS_ERROR
+ };
+
+ /* Parameters */
+ public struct gsParamState_t
+ {
+ public String outputfile;
+ public String inputfile;
+ public GS_Task_t task;
+ public GS_Result_t result;
+ public int num_pages;
+ public List<int> pages;
+ public int firstpage;
+ public int lastpage;
+ public int currpage;
+ public List<String> args;
+ public int return_code;
+ public double zoom;
+ };
+
+ public class gsThreadCallBack
+ {
+ private bool m_completed;
+ private int m_progress;
+ private gsParamState_t m_param;
+ public bool Completed
+ {
+ get { return m_completed; }
+ }
+ public gsParamState_t Params
+ {
+ get { return m_param; }
+ }
+ public int Progress
+ {
+ get { return m_progress; }
+ }
+ public gsThreadCallBack(bool completed, int progress, gsParamState_t param)
+ {
+ m_completed = completed;
+ m_progress = progress;
+ m_param = param;
+ }
+ }
+
+ class ghostsharp
+ {
+ public class GhostscriptException : Exception
+ {
+ public GhostscriptException(string message) : base(message)
+ {
+ }
+ }
+
+ /* Ghostscript display device callback delegates. */
+
+ /* New device has been opened */
+ /* This is the first event from this device. */
+ public delegate int display_open_del(IntPtr handle, IntPtr device);
+
+ /* Device is about to be closed. */
+ /* Device will not be closed until this function returns. */
+ public delegate int display_preclose_del(IntPtr handle, IntPtr device);
+
+ /* Device has been closed. */
+ /* This is the last event from this device. */
+ public delegate int display_close_del(IntPtr handle, IntPtr device);
+
+ /* Device is about to be resized. */
+ /* Resize will only occur if this function returns 0. */
+ /* raster is byte count of a row. */
+ public delegate int display_presize_del(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format);
+
+ /* Device has been resized. */
+ /* New pointer to raster returned in pimage */
+ public delegate int display_size_del(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format,
+ IntPtr pimage);
+
+ /* flushpage */
+ public delegate int display_sync_del(IntPtr handle, IntPtr device);
+
+ /* showpage */
+ /* If you want to pause on showpage, then don't return immediately */
+ public delegate int display_page_del(IntPtr handle, IntPtr device, int copies, int flush);
+
+
+ /* Notify the caller whenever a portion of the raster is updated. */
+ /* This can be used for cooperative multitasking or for
+ * progressive update of the display.
+ * This function pointer may be set to NULL if not required.
+ */
+ public delegate int display_update_del(IntPtr handle, IntPtr device, int x, int y,
+ int w, int h);
+
+ /* Allocate memory for bitmap */
+ /* This is provided in case you need to create memory in a special
+ * way, e.g. shared. If this is NULL, the Ghostscript memory device
+ * allocates the bitmap. This will only called to allocate the
+ * image buffer. The first row will be placed at the address
+ * returned by display_memalloc.
+ */
+ public delegate int display_memalloc_del(IntPtr handle, IntPtr device, ulong size);
+
+ /* Free memory for bitmap */
+ /* If this is NULL, the Ghostscript memory device will free the bitmap */
+ public delegate int display_memfree_del(IntPtr handle, IntPtr device, IntPtr mem);
+
+ private int display_size(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format,
+ IntPtr pimage)
+ {
+ m_pagewidth = width;
+ m_pageheight = height;
+ m_pageraster = raster;
+ m_pageptr = pimage;
+ return 0;
+ }
+
+ private int display_page(IntPtr handle, IntPtr device, int copies, int flush)
+ {
+ m_params.currpage += 1;
+ Gtk.Application.Invoke(delegate {
+ gsPageRenderedMain(m_pagewidth, m_pageheight, m_pageraster, m_pageptr, m_params);
+ });
+ return 0;
+ }
+
+ private int display_open(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ private int display_preclose(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ private int display_close(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ private int display_presize(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format)
+ {
+ return 0;
+ }
+
+ private int display_update(IntPtr handle, IntPtr device, int x, int y,
+ int w, int h)
+ {
+ return 0;
+ }
+
+ private int display_memalloc(IntPtr handle, IntPtr device, ulong size)
+ {
+ return 0;
+ }
+
+ private int display_memfree(IntPtr handle, IntPtr device, IntPtr mem)
+ {
+ return 0;
+ }
+ private int display_sync(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ /* Delegate for stdio */
+ public delegate int gs_stdio_handler(IntPtr caller_handle, IntPtr buffer,
+ int len);
+
+ private int stdin_callback(IntPtr handle, IntPtr pointer, int count)
+ {
+ String output = Marshal.PtrToStringAnsi(pointer);
+ return count;
+ }
+
+ private int stdout_callback(IntPtr handle, IntPtr pointer, int count)
+ {
+ String output = null;
+ try
+ {
+ output = Marshal.PtrToStringAnsi(pointer);
+ Gtk.Application.Invoke(delegate {
+ gsIOUpdateMain(output, count);
+ });
+ }
+ catch (Exception excep2)
+ {
+ var mess = excep2.Message;
+ }
+
+ return count;
+
+ }
+
+ private int stderr_callback(IntPtr handle, IntPtr pointer, int count)
+ {
+ String output = Marshal.PtrToStringAnsi(pointer);
+ Gtk.Application.Invoke(delegate {
+ gsIOUpdateMain(output, count);
+ });
+
+ return count;
+ }
+
+ IntPtr gsInstance;
+ IntPtr dispInstance;
+ Thread m_worker;
+ bool m_worker_busy;
+ gsParamState_t m_params;
+ IntPtr m_pageptr;
+ int m_pagewidth;
+ int m_pageheight;
+ int m_pageraster;
+
+ display_callback_t m_display_callback;
+ IntPtr ptr_display_struct;
+
+ /* Callbacks to Main */
+ internal delegate void gsDLLProblem(String mess);
+ internal event gsDLLProblem gsDLLProblemMain;
+
+ internal delegate void gsIOCallBackMain(String mess, int len);
+ internal event gsIOCallBackMain gsIOUpdateMain;
+
+ internal delegate void gsCallBackMain(gsThreadCallBack info);
+ internal event gsCallBackMain gsUpdateMain;
+
+ internal delegate void gsCallBackPageRenderedMain(int width, int height, int raster,
+ IntPtr data, gsParamState_t state);
+ internal event gsCallBackPageRenderedMain gsPageRenderedMain;
+
+
+ /* From my understanding you cannot pin delegates. These need to be declared
+ * as members to keep a reference to the delegates and avoid their possible GC.
+ * since the C# GC has no idea that GS has a reference to these items. */
+ readonly gs_stdio_handler raise_stdin;
+ readonly gs_stdio_handler raise_stdout;
+ readonly gs_stdio_handler raise_stderr;
+
+ /* Ghostscript display callback struct */
+ public struct display_callback_t
+ {
+ public int sizeof_display_callback;
+ public int major_vers;
+ public int minor_vers;
+ public display_open_del display_open;
+ public display_preclose_del display_preclose;
+ public display_close_del display_close;
+ public display_presize_del display_presize;
+ public display_size_del display_size;
+ public display_sync_del display_sync;
+ public display_page_del display_page;
+ public display_update_del display_update;
+ public display_memalloc_del display_memalloc;
+ public display_memfree_del display_memfree;
+ };
+ public ghostsharp()
+ {
+ m_worker = null;
+ gsInstance = IntPtr.Zero;
+ dispInstance = IntPtr.Zero;
+
+ /* Avoiding delegate GC during the life of this object */
+ raise_stdin = stdin_callback;
+ raise_stdout = stdout_callback;
+ raise_stderr = stderr_callback;
+
+ m_display_callback.major_vers = 1;
+ m_display_callback.minor_vers = 0;
+ m_display_callback.display_open = display_open;
+ m_display_callback.display_preclose = display_preclose;
+ m_display_callback.display_close = display_close;
+ m_display_callback.display_presize = display_presize;
+ m_display_callback.display_size = display_size;
+ m_display_callback.display_sync = display_sync;
+ m_display_callback.display_page = display_page;
+ m_display_callback.display_update = display_update;
+ //m_display_callback.display_memalloc = display_memalloc;
+ //m_display_callback.display_memfree = display_memfree;
+ m_display_callback.display_memalloc = null;
+ m_display_callback.display_memfree = null;
+
+ /* The size the structure when marshalled to unmanaged code */
+ m_display_callback.sizeof_display_callback = Marshal.SizeOf(typeof(display_callback_t));
+
+ ptr_display_struct = Marshal.AllocHGlobal(m_display_callback.sizeof_display_callback);
+ Marshal.StructureToPtr(m_display_callback, ptr_display_struct, false);
+ m_worker_busy = false;
+ }
+
+
+ /* Callback upon worker all done */
+ private void gsCompleted(gsParamState_t Params)
+ {
+ gsThreadCallBack info = new gsThreadCallBack(true, 100, Params);
+ m_worker_busy = false;
+ Gtk.Application.Invoke(delegate {
+ gsUpdateMain(info);
+ });
+ }
+
+ /* Callback as worker progresses in run string case */
+ private void gsProgressChanged(gsParamState_t Params, int percent)
+ {
+ /* Callback with progress */
+ gsThreadCallBack info = new gsThreadCallBack(false, percent, Params);
+ Gtk.Application.Invoke(delegate {
+ gsUpdateMain(info);
+ });
+ }
+
+ /* Callback for problem */
+ private void gsErrorReport(string message)
+ {
+ Gtk.Application.Invoke(delegate {
+ gsDLLProblemMain(message);;
+ });
+ m_worker_busy = false;
+ }
+
+ private gsParamState_t gsFileSync(gsParamState_t in_params)
+ {
+ int num_params = in_params.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ GCHandle argPtrsStable = new GCHandle();
+ int code = 0;
+ bool cleanup = true;
+
+ try
+ {
+ code = ghostapi.gsapi_new_instance(out gsInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_new_instance error");
+ }
+ code = ghostapi.gsapi_set_stdio(gsInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_set_stdio error");
+ }
+ code = ghostapi.gsapi_set_arg_encoding(gsInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_set_arg_encoding error");
+ }
+
+ /* Now convert our Strings to char* and get pinned handles to these.
+ * This keeps the c# GC from moving stuff around on us */
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((in_params.args[k]+"\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + in_params.args[k];
+ }
+
+ /* Also stick the array of pointers into memory that will not be GCd */
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(gsInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0 && code != gsConstants.E_QUIT)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_init_with_args error");
+ }
+ }
+ catch (DllNotFoundException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ in_params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ in_params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ }
+ catch (GhostscriptException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ }
+ catch (Exception except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ }
+ finally
+ {
+ /* All the pinned items need to be freed so the GC can do its job */
+ if (cleanup)
+ {
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+
+ int code1 = ghostapi.gsapi_exit(gsInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(gsInstance);
+ in_params.return_code = code;
+
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ {
+ in_params.result = GS_Result_t.gsOK;
+ }
+ else
+ {
+ in_params.result = GS_Result_t.gsFAILED;
+ }
+ gsInstance = IntPtr.Zero;
+ }
+ }
+ return in_params;
+ }
+
+ /* Process command line with gsapi_init_with_args */
+ private void gsFileAsync(object data)
+ {
+ List<object> genericlist = data as List<object>;
+ gsParamState_t Params = (gsParamState_t)genericlist[0];
+ gsParamState_t Result = Params;
+ int num_params = Params.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ GCHandle argPtrsStable = new GCHandle();
+ int code = 0;
+ bool cleanup = true;
+
+ try
+ {
+ code = ghostapi.gsapi_new_instance(out gsInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_new_instance error");
+ }
+ code = ghostapi.gsapi_set_stdio(gsInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_set_stdio error");
+ }
+ code = ghostapi.gsapi_set_arg_encoding(gsInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_set_arg_encoding error");
+ }
+
+ /* Now convert our Strings to char* and get pinned handles to these.
+ * This keeps the c# GC from moving stuff around on us */
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((Params.args[k]+"\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + Params.args[k];
+ }
+
+ /* Also stick the array of pointers into memory that will not be GCd */
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(gsInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_init_with_args error");
+ }
+ }
+ catch (DllNotFoundException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = Params;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = Params;
+ }
+ catch (GhostscriptException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ }
+ catch (Exception except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ }
+ finally
+ {
+ if (cleanup)
+ {
+ /* All the pinned items need to be freed so the GC can do its job */
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+
+ int code1 = ghostapi.gsapi_exit(gsInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(gsInstance);
+ Params.return_code = code;
+
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ {
+ Params.result = GS_Result_t.gsOK;
+ Result = Params;
+ }
+ else
+ {
+ Params.result = GS_Result_t.gsFAILED;
+ Result = Params;
+ }
+ gsInstance = IntPtr.Zero;
+ }
+ }
+
+ /* Completed. */
+ gsCompleted(Result);
+ return;
+ }
+
+ /* Processing with gsapi_run_string for callback progress */
+ private void gsBytesAsync(object data)
+ {
+ List<object> genericlist = data as List<object>;
+ gsParamState_t Params = (gsParamState_t)genericlist[0];
+ gsParamState_t Result = Params;
+ int num_params = Params.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ GCHandle argPtrsStable = new GCHandle();
+ Byte[] Buffer = new Byte[gsConstants.GS_READ_BUFFER];
+
+ int code = 0;
+ int exitcode = 0;
+ var Feed = new GCHandle();
+ var FeedPtr = new IntPtr();
+ String[] strParams = new String[num_params];
+ FileStream fs = null;
+ bool cleanup = true;
+
+ try
+ {
+ /* Open the file */
+ fs = new FileStream(Params.inputfile, FileMode.Open);
+ var len = (int)fs.Length;
+
+ code = ghostapi.gsapi_new_instance(out gsInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_new_instance error");
+ }
+ code = ghostapi.gsapi_set_stdio(gsInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_set_stdio error");
+ }
+ code = ghostapi.gsapi_set_arg_encoding(gsInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_set_arg_encoding error");
+ }
+
+ /* Now convert our Strings to char* and get pinned handles to these.
+ * This keeps the c# GC from moving stuff around on us */
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((Params.args[k]+"\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + Params.args[k];
+ }
+
+ /* Also stick the array of pointers into memory that will not be GCd */
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(gsInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_init_with_args error");
+ }
+
+ /* Pin data buffer */
+ Feed = GCHandle.Alloc(Buffer, GCHandleType.Pinned);
+ FeedPtr = Feed.AddrOfPinnedObject();
+
+ /* Now start feeding the input piece meal and do a call back
+ * with our progress */
+ if (code == 0)
+ {
+ int count;
+ double perc;
+ int total = 0;
+ int ret_code;
+
+ ret_code = ghostapi.gsapi_run_string_begin(gsInstance, 0, ref exitcode);
+ if (exitcode < 0)
+ {
+ code = exitcode;
+ throw new GhostscriptException("gsBytesAsync: gsapi_run_string_begin error");
+ }
+
+ while ((count = fs.Read(Buffer, 0, gsConstants.GS_READ_BUFFER)) > 0)
+ {
+ ret_code = ghostapi.gsapi_run_string_continue(gsInstance, FeedPtr, count, 0, ref exitcode);
+ if (exitcode < 0)
+ {
+ code = exitcode;
+ throw new GhostscriptException("gsBytesAsync: gsapi_run_string_continue error");
+ }
+
+ total = total + count;
+ perc = 100.0 * (double)total / (double)len;
+ gsProgressChanged(Params, (int)perc);
+ }
+ ret_code = ghostapi.gsapi_run_string_end(gsInstance, 0, ref exitcode);
+ if (exitcode < 0)
+ {
+ code = exitcode;
+ throw new GhostscriptException("gsBytesAsync: gsapi_run_string_end error");
+ }
+ }
+ }
+ catch (DllNotFoundException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = Params;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = Params;
+ }
+ catch (GhostscriptException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ }
+ catch (Exception except)
+ {
+ /* Could be a file io issue */
+ gsErrorReport("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = Params;
+ }
+ finally
+ {
+ if (cleanup)
+ {
+ fs.Close();
+
+ /* Free pinned items */
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+ Feed.Free();
+
+ /* gs clean up */
+ int code1 = ghostapi.gsapi_exit(gsInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(gsInstance);
+ Params.return_code = code;
+
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ {
+ Params.result = GS_Result_t.gsOK;
+ Result = Params;
+ }
+ else
+ {
+ Params.result = GS_Result_t.gsFAILED;
+ Result = Params;
+ }
+ gsInstance = IntPtr.Zero;
+ }
+ }
+ gsCompleted(Result);
+ return;
+ }
+
+ /* Worker task for using display device */
+ private void DisplayDeviceAsync(object data)
+ {
+ int code = 0;
+ List<object> genericlist = data as List<object>;
+ gsParamState_t gsparams = (gsParamState_t)genericlist[0];
+ gsParamState_t Result = gsparams;
+ GCHandle argPtrsStable = new GCHandle();
+ int num_params = gsparams.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ bool cleanup = true;
+
+ gsparams.result = GS_Result_t.gsOK;
+
+ try
+ {
+ code = ghostapi.gsapi_new_instance(out dispInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_new_instance error");
+ }
+
+ code = ghostapi.gsapi_set_stdio(dispInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_set_stdio error");
+ }
+
+ code = ghostapi.gsapi_set_arg_encoding(dispInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_set_arg_encoding error");
+ }
+
+ code = ghostapi.gsapi_set_display_callback(dispInstance, ptr_display_struct);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_set_display_callback error");
+ }
+
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((gsparams.args[k] + "\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + gsparams.args[k];
+ }
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(dispInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_init_with_args error");
+ }
+ }
+
+ catch (DllNotFoundException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = gsparams;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ Result = gsparams;
+ }
+ catch (GhostscriptException except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ if (dispInstance != IntPtr.Zero)
+ ghostapi.gsapi_delete_instance(dispInstance);
+ dispInstance = IntPtr.Zero;
+ }
+ catch (Exception except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ if (dispInstance != IntPtr.Zero)
+ ghostapi.gsapi_delete_instance(dispInstance);
+ dispInstance = IntPtr.Zero;
+ }
+ finally
+ {
+ if (cleanup)
+ {
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+ Result = gsparams;
+
+ if (gsparams.result == GS_Result_t.gsOK && (gsparams.task == GS_Task_t.DISPLAY_DEV_NON_PDF ||
+ gsparams.task == GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF))
+ {
+ gsParamState_t result = DisplayDeviceClose();
+ if (gsparams.result == 0)
+ {
+ gsparams.result = result.result;
+ }
+ }
+ }
+ }
+ gsCompleted(Result);
+ return;
+ }
+
+ /* Call the appropriate worker thread based upon the task
+ * that we have to do */
+ private gsStatus RunGhostscriptAsync(gsParamState_t Params)
+ {
+ try
+ {
+ if (m_worker_busy)
+ {
+ return gsStatus.GS_BUSY;
+ }
+
+ switch (Params.task)
+ {
+ case GS_Task_t.PS_DISTILL:
+ m_worker = new Thread(gsBytesAsync);
+ break;
+ case GS_Task_t.DISPLAY_DEV_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_PDF:
+ m_worker = new Thread(DisplayDeviceAsync);
+ break;
+ case GS_Task_t.SAVE_RESULT:
+ case GS_Task_t.CREATE_XPS:
+ default:
+ m_worker = new Thread(gsFileAsync);
+ break;
+ }
+
+ var arguments = new List<object>();
+ arguments.Add(Params);
+ arguments.Add(this);
+ m_worker_busy = true;
+ m_worker.Start(arguments);
+
+ return gsStatus.GS_READY;
+ }
+ catch (OutOfMemoryException)
+ {
+ Console.WriteLine("Memory allocation failed during gs rendering\n");
+ return gsStatus.GS_ERROR;
+ }
+ }
+
+#region public_methods
+
+ /* Direct call on gsapi to get the version of the DLL we are using */
+ public String GetVersion()
+ {
+ gsapi_revision_t vers;
+ vers.copyright = IntPtr.Zero;
+ vers.product = IntPtr.Zero;
+ vers.revision = 0;
+ vers.revisiondate = 0;
+ int size = System.Runtime.InteropServices.Marshal.SizeOf(vers);
+
+ try
+ {
+ if (ghostapi.gsapi_revision(ref vers, size) == 0)
+ {
+ String product = Marshal.PtrToStringAnsi(vers.product);
+ String output;
+ int major = vers.revision / 100;
+ int minor = vers.revision - major * 100;
+ String versnum = major + "." + minor;
+ output = product + " " + versnum;
+ return output;
+ }
+ else
+ return null;
+ }
+ catch (Exception except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ }
+ return null;
+ }
+
+ /* Use syncronous call to ghostscript to get the
+ * number of pages in a PDF file */
+ public int GetPageCount(String fileName)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ gsParamState_t result;
+ gsparams.args = new List<string>();
+
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNODISPLAY");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ //gsparams.args.Add("-q");
+ gsparams.args.Add("-sFile=\"" + fileName + "\"");
+ gsparams.args.Add("--permit-file-read=\"" + fileName + "\"");
+ gsparams.args.Add("-c");
+ gsparams.args.Add("\"File (r) file runpdfbegin pdfpagecount = quit\"");
+ gsparams.task = GS_Task_t.GET_PAGE_COUNT;
+ m_params = gsparams;
+
+ result = gsFileSync(gsparams);
+
+ if (result.result == GS_Result_t.gsOK)
+ return m_params.num_pages;
+ else
+ return -1;
+ }
+
+ /* Launch a thread rendering all the pages with the display device
+ * to distill an input PS file and save as a PDF. */
+ public gsStatus DistillPS(String fileName, int resolution)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ gsparams.args = new List<string>();
+
+ gsparams.inputfile = fileName;
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-sDEVICE=pdfwrite");
+ gsparams.outputfile = Path.GetTempFileName();
+ gsparams.args.Add("-o" + gsparams.outputfile);
+ gsparams.task = GS_Task_t.PS_DISTILL;
+
+ return RunGhostscriptAsync(gsparams);
+ }
+
+ /* Launch a thread rendering all the pages with the display device
+ * to collect thumbnail images or full resolution. */
+ public gsStatus gsDisplayDeviceRenderAll(String fileName, double zoom, bool aa, GS_Task_t task)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ int format = (gsConstants.DISPLAY_COLORS_RGB |
+ gsConstants.DISPLAY_DEPTH_8 |
+ gsConstants.DISPLAY_BIGENDIAN);
+ int resolution = (int)(72.0 * zoom + 0.5);
+
+ gsparams.args = new List<string>();
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-r" + resolution);
+ if (aa)
+ {
+ gsparams.args.Add("-dTextAlphaBits=4");
+ gsparams.args.Add("-dGraphicsAlphaBits=4");
+ }
+ gsparams.args.Add("-sDEVICE=display");
+ gsparams.args.Add("-dDisplayFormat=" + format);
+ gsparams.args.Add("-f");
+ gsparams.args.Add(fileName);
+ gsparams.task = task;
+ gsparams.currpage = 0;
+ m_params.currpage = 0;
+ return RunGhostscriptAsync(gsparams);
+ }
+
+
+ /* Launch a thread rendering a set of pages with the display device. For use with languages
+ that can be indexed via pages which include PDF and XPS */
+ public gsStatus gsDisplayDeviceRenderPages(String fileName, int first_page, int last_page, double zoom)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ int format = (gsConstants.DISPLAY_COLORS_RGB |
+ gsConstants.DISPLAY_DEPTH_8 |
+ gsConstants.DISPLAY_LITTLEENDIAN);
+ int resolution = (int)(72.0 * zoom + 0.5);
+
+ gsparams.args = new List<string>();
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-r" + resolution);
+ gsparams.args.Add("-sDEVICE=display");
+ gsparams.args.Add("-dDisplayFormat=" + format);
+ gsparams.args.Add("-dFirstPage=" + first_page);
+ gsparams.args.Add("-dLastPage=" + last_page);
+ gsparams.args.Add("-f");
+ gsparams.args.Add(fileName);
+ gsparams.task = GS_Task_t.DISPLAY_DEV_PDF;
+ gsparams.currpage = first_page - 1;
+ m_params.currpage = first_page - 1;
+
+ return RunGhostscriptAsync(gsparams);
+ }
+
+ /* Close the display device and delete the instance */
+ public gsParamState_t DisplayDeviceClose()
+ {
+ int code = 0;
+ gsParamState_t out_params = new gsParamState_t();
+
+ out_params.result = GS_Result_t.gsOK;
+
+ try
+ {
+ int code1 = ghostapi.gsapi_exit(dispInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(dispInstance);
+ dispInstance = IntPtr.Zero;
+
+ }
+ catch (Exception except)
+ {
+ gsErrorReport("Exception: " + except.Message);
+ out_params.result = GS_Result_t.gsFAILED;
+ }
+
+ return out_params;
+ }
+
+ /* Check if gs is currently busy */
+ public gsStatus GetStatus()
+ {
+ if (m_worker_busy)
+ return gsStatus.GS_BUSY;
+ else
+ return gsStatus.GS_READY;
+ }
+#endregion
+ }
+}
diff --git a/demos/csharp/api/ghostnet.cs b/demos/csharp/api/ghostnet.cs
new file mode 100644
index 00000000..9011768e
--- /dev/null
+++ b/demos/csharp/api/ghostnet.cs
@@ -0,0 +1,1198 @@
+using System;
+using System.Runtime.InteropServices; /* Marshaling */
+using System.ComponentModel; /* Background threading */
+using System.Collections.Generic; /* Use of List */
+using System.IO; /* Use of path */
+using GhostAPI; /* Use of Ghostscript API */
+#if WPF
+using ghostnet_wpf_example; /* For Print control */
+#endif
+
+namespace GhostNET
+{
+ public enum GS_Task_t
+ {
+ PS_DISTILL,
+ CREATE_XPS,
+ SAVE_RESULT,
+ GET_PAGE_COUNT,
+ GENERIC,
+ DISPLAY_DEV_THUMBS_NON_PDF,
+ DISPLAY_DEV_THUMBS_PDF,
+ DISPLAY_DEV_NON_PDF,
+ DISPLAY_DEV_PDF,
+ }
+ public enum GS_Result_t
+ {
+ gsOK,
+ gsFAILED,
+ gsCANCELLED
+ }
+ public enum gsStatus
+ {
+ GS_READY,
+ GS_BUSY,
+ GS_ERROR
+ };
+
+ /* Parameters */
+ public struct gsParamState_t
+ {
+ public String outputfile;
+ public String inputfile;
+ public GS_Task_t task;
+ public GS_Result_t result;
+ public int num_pages;
+ public List<int> pages;
+ public int firstpage;
+ public int lastpage;
+ public int currpage;
+ public List<String> args;
+ public int return_code;
+ public double zoom;
+ };
+
+public class gsEventArgs : EventArgs
+ {
+ private bool m_completed;
+ private int m_progress;
+ private gsParamState_t m_param;
+ public bool Completed
+ {
+ get { return m_completed; }
+ }
+ public gsParamState_t Params
+ {
+ get { return m_param; }
+ }
+ public int Progress
+ {
+ get { return m_progress; }
+ }
+ public gsEventArgs(bool completed, int progress, gsParamState_t param)
+ {
+ m_completed = completed;
+ m_progress = progress;
+ m_param = param;
+ }
+ }
+
+ class ghostsharp
+ {
+ public class GhostscriptException : Exception
+ {
+ public GhostscriptException(string message) : base(message)
+ {
+ }
+ }
+
+ /* Ghostscript display device callback delegates. */
+
+ /* New device has been opened */
+ /* This is the first event from this device. */
+ public delegate int display_open_del(IntPtr handle, IntPtr device);
+
+ /* Device is about to be closed. */
+ /* Device will not be closed until this function returns. */
+ public delegate int display_preclose_del(IntPtr handle, IntPtr device);
+
+ /* Device has been closed. */
+ /* This is the last event from this device. */
+ public delegate int display_close_del(IntPtr handle, IntPtr device);
+
+ /* Device is about to be resized. */
+ /* Resize will only occur if this function returns 0. */
+ /* raster is byte count of a row. */
+ public delegate int display_presize_del(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format);
+
+ /* Device has been resized. */
+ /* New pointer to raster returned in pimage */
+ public delegate int display_size_del(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format,
+ IntPtr pimage);
+
+ /* flushpage */
+ public delegate int display_sync_del(IntPtr handle, IntPtr device);
+
+ /* showpage */
+ /* If you want to pause on showpage, then don't return immediately */
+ public delegate int display_page_del(IntPtr handle, IntPtr device, int copies, int flush);
+
+
+ /* Notify the caller whenever a portion of the raster is updated. */
+ /* This can be used for cooperative multitasking or for
+ * progressive update of the display.
+ * This function pointer may be set to NULL if not required.
+ */
+ public delegate int display_update_del(IntPtr handle, IntPtr device, int x, int y,
+ int w, int h);
+
+ /* Allocate memory for bitmap */
+ /* This is provided in case you need to create memory in a special
+ * way, e.g. shared. If this is NULL, the Ghostscript memory device
+ * allocates the bitmap. This will only called to allocate the
+ * image buffer. The first row will be placed at the address
+ * returned by display_memalloc.
+ */
+ public delegate int display_memalloc_del(IntPtr handle, IntPtr device, ulong size);
+
+ /* Free memory for bitmap */
+ /* If this is NULL, the Ghostscript memory device will free the bitmap */
+ public delegate int display_memfree_del(IntPtr handle, IntPtr device, IntPtr mem);
+
+ private int display_size(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format,
+ IntPtr pimage)
+ {
+ m_pagewidth = width;
+ m_pageheight = height;
+ m_pageraster = raster;
+ m_pageptr = pimage;
+ return 0;
+ }
+
+ private int display_page(IntPtr handle, IntPtr device, int copies, int flush)
+ {
+ m_params.currpage += 1;
+ gsPageRenderedMain(m_pagewidth, m_pageheight, m_pageraster, m_pageptr, m_params);
+ return 0;
+ }
+
+ private int display_open(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ private int display_preclose(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ private int display_close(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ private int display_presize(IntPtr handle, IntPtr device,
+ int width, int height, int raster, uint format)
+ {
+ return 0;
+ }
+
+ private int display_update(IntPtr handle, IntPtr device, int x, int y,
+ int w, int h)
+ {
+ return 0;
+ }
+
+ private int display_memalloc(IntPtr handle, IntPtr device, ulong size)
+ {
+ return 0;
+ }
+
+ private int display_memfree(IntPtr handle, IntPtr device, IntPtr mem)
+ {
+ return 0;
+ }
+ private int display_sync(IntPtr handle, IntPtr device)
+ {
+ return 0;
+ }
+
+ /* Delegate for stdio */
+ public delegate int gs_stdio_handler(IntPtr caller_handle, IntPtr buffer,
+ int len);
+
+ private int stdin_callback(IntPtr handle, IntPtr pointer, int count)
+ {
+ String output = Marshal.PtrToStringAnsi(pointer);
+ return count;
+ }
+
+ private int stdout_callback(IntPtr handle, IntPtr pointer, int count)
+ {
+ String output = null;
+ try
+ {
+ output = Marshal.PtrToStringAnsi(pointer);
+ }
+ catch (Exception except)
+ {
+ var mess = except.Message;
+ }
+
+ try
+ {
+ gsIOUpdateMain(output, count);
+ }
+ catch (Exception excep2)
+ {
+ var mess = excep2.Message;
+ }
+
+ return count;
+ }
+
+ private int stderr_callback(IntPtr handle, IntPtr pointer, int count)
+ {
+ String output = Marshal.PtrToStringAnsi(pointer);
+ gsIOUpdateMain(output, count);
+ return count;
+ }
+
+ IntPtr gsInstance;
+ IntPtr dispInstance;
+ BackgroundWorker m_worker;
+ gsParamState_t m_params;
+ IntPtr m_pageptr;
+ int m_pagewidth;
+ int m_pageheight;
+ int m_pageraster;
+
+ display_callback_t m_display_callback;
+ IntPtr ptr_display_struct;
+
+ /* Callbacks to Main */
+ internal delegate void gsDLLProblem(String mess);
+ internal event gsDLLProblem gsDLLProblemMain;
+
+ internal delegate void gsIOCallBackMain(String mess, int len);
+ internal event gsIOCallBackMain gsIOUpdateMain;
+
+ internal delegate void gsCallBackMain(gsEventArgs info);
+ internal event gsCallBackMain gsUpdateMain;
+
+ internal delegate void gsCallBackPageRenderedMain(int width, int height, int raster,
+ IntPtr data, gsParamState_t state);
+ internal event gsCallBackPageRenderedMain gsPageRenderedMain;
+
+
+ /* From my understanding you cannot pin delegates. These need to be declared
+ * as members to keep a reference to the delegates and avoid their possible GC.
+ * since the C# GC has no idea that GS has a reference to these items. */
+ readonly gs_stdio_handler raise_stdin;
+ readonly gs_stdio_handler raise_stdout;
+ readonly gs_stdio_handler raise_stderr;
+
+ /* Ghostscript display callback struct */
+ public struct display_callback_t
+ {
+ public int sizeof_display_callback;
+ public int major_vers;
+ public int minor_vers;
+ public display_open_del display_open;
+ public display_preclose_del display_preclose;
+ public display_close_del display_close;
+ public display_presize_del display_presize;
+ public display_size_del display_size;
+ public display_sync_del display_sync;
+ public display_page_del display_page;
+ public display_update_del display_update;
+ public display_memalloc_del display_memalloc;
+ public display_memfree_del display_memfree;
+ };
+ public ghostsharp()
+ {
+ m_worker = null;
+ gsInstance = IntPtr.Zero;
+ dispInstance = IntPtr.Zero;
+
+ /* Avoiding delegate GC during the life of this object */
+ raise_stdin = stdin_callback;
+ raise_stdout = stdout_callback;
+ raise_stderr = stderr_callback;
+
+ m_display_callback.major_vers = 1;
+ m_display_callback.minor_vers = 0;
+ m_display_callback.display_open = display_open;
+ m_display_callback.display_preclose = display_preclose;
+ m_display_callback.display_close = display_close;
+ m_display_callback.display_presize = display_presize;
+ m_display_callback.display_size = display_size;
+ m_display_callback.display_sync = display_sync;
+ m_display_callback.display_page = display_page;
+ m_display_callback.display_update = display_update;
+ //m_display_callback.display_memalloc = display_memalloc;
+ //m_display_callback.display_memfree = display_memfree;
+ m_display_callback.display_memalloc = null;
+ m_display_callback.display_memfree = null;
+
+ /* The size the structure when marshalled to unmanaged code */
+ m_display_callback.sizeof_display_callback = Marshal.SizeOf(typeof(display_callback_t));
+
+ ptr_display_struct = Marshal.AllocHGlobal(m_display_callback.sizeof_display_callback);
+ Marshal.StructureToPtr(m_display_callback, ptr_display_struct, false);
+ }
+
+
+ /* Callback upon worker all done */
+ private void gsCompleted(object sender, RunWorkerCompletedEventArgs e)
+ {
+ gsParamState_t Value;
+ gsEventArgs info;
+ gsParamState_t Params;
+
+ try
+ {
+ Params = (gsParamState_t)e.Result;
+ }
+ catch (System.Reflection.TargetInvocationException)
+ {
+ /* Something went VERY wrong with GS */
+ /* Following is to help debug these issues */
+ /* var inner = ee.InnerException;
+ var message = ee.Message;
+ var inner_message = inner.Message;
+ String bound = "\n************\n";
+ gsIOUpdateMain(this, bound, bound.Length);
+ gsIOUpdateMain(this, message, message.Length);
+ gsIOUpdateMain(this, bound, bound.Length);
+ gsIOUpdateMain(this, inner_message, inner_message.Length);
+ gsIOUpdateMain(this, bound, bound.Length);
+ var temp = inner.Source;
+ gsIOUpdateMain(this, bound, bound.Length);
+ gsIOUpdateMain(this, temp, temp.Length);
+ var method = inner.TargetSite;
+ gsIOUpdateMain(this, bound, bound.Length);
+ var method_name = method.Name;
+ gsIOUpdateMain(this, method_name, method_name.Length);
+ var stack = inner.StackTrace;
+ gsIOUpdateMain(this, bound, bound.Length);
+ gsIOUpdateMain(this, stack, stack.Length); */
+ String output = "Ghostscript DLL Invalid Access.";
+ gsDLLProblemMain(output);
+ return;
+ }
+ switch (Params.task)
+ {
+ case GS_Task_t.PS_DISTILL:
+ m_worker.DoWork -= new DoWorkEventHandler(gsBytesAsync);
+ break;
+ case GS_Task_t.DISPLAY_DEV_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_PDF:
+ m_worker.DoWork -= new DoWorkEventHandler(DisplayDeviceAsync);
+ break;
+ default:
+ m_worker.DoWork -= new DoWorkEventHandler(gsFileAsync);
+ break;
+ }
+
+ if (e.Cancelled)
+ {
+ Value = new gsParamState_t();
+ Value.result = GS_Result_t.gsCANCELLED;
+ info = new gsEventArgs(true, 100, Value);
+ }
+ else
+ {
+ Value = (gsParamState_t)e.Result;
+ info = new gsEventArgs(true, 100, Value);
+ }
+ gsUpdateMain(info);
+ }
+
+ /* Callback as worker progresses */
+ private void gsProgressChanged(object sender, ProgressChangedEventArgs e)
+ {
+ /* Callback with progress */
+ gsParamState_t Value = new gsParamState_t();
+ gsEventArgs info = new gsEventArgs(false, e.ProgressPercentage, Value);
+ gsUpdateMain(info);
+ }
+ private gsParamState_t gsFileSync(gsParamState_t in_params)
+ {
+ int num_params = in_params.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ GCHandle argPtrsStable = new GCHandle();
+ int code = 0;
+ bool cleanup = true;
+
+ try
+ {
+ code = ghostapi.gsapi_new_instance(out gsInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_new_instance error");
+ }
+ code = ghostapi.gsapi_set_stdio(gsInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_set_stdio error");
+ }
+ code = ghostapi.gsapi_set_arg_encoding(gsInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_set_arg_encoding error");
+ }
+
+ /* Now convert our Strings to char* and get pinned handles to these.
+ * This keeps the c# GC from moving stuff around on us */
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((in_params.args[k]+"\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + in_params.args[k];
+ }
+
+ /* Also stick the array of pointers into memory that will not be GCd */
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(gsInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0 && code != gsConstants.E_QUIT)
+ {
+ throw new GhostscriptException("gsFileSync: gsapi_init_with_args error");
+ }
+ }
+ catch (DllNotFoundException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ in_params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ in_params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ }
+ catch (GhostscriptException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ }
+ catch (Exception except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ }
+ finally
+ {
+ /* All the pinned items need to be freed so the GC can do its job */
+ if (cleanup)
+ {
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+
+ int code1 = ghostapi.gsapi_exit(gsInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(gsInstance);
+ in_params.return_code = code;
+
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ {
+ in_params.result = GS_Result_t.gsOK;
+ }
+ else
+ {
+ in_params.result = GS_Result_t.gsFAILED;
+ }
+ gsInstance = IntPtr.Zero;
+ }
+ }
+ return in_params;
+ }
+
+ /* Process command line with gsapi_init_with_args */
+ private void gsFileAsync(object sender, DoWorkEventArgs e)
+ {
+ gsParamState_t Params = (gsParamState_t)e.Argument;
+ int num_params = Params.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ GCHandle argPtrsStable = new GCHandle();
+ int code = 0;
+ bool cleanup = true;
+
+ try
+ {
+ code = ghostapi.gsapi_new_instance(out gsInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_new_instance error");
+ }
+ code = ghostapi.gsapi_set_stdio(gsInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_set_stdio error");
+ }
+ code = ghostapi.gsapi_set_arg_encoding(gsInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_set_arg_encoding error");
+ }
+
+ /* Now convert our Strings to char* and get pinned handles to these.
+ * This keeps the c# GC from moving stuff around on us */
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((Params.args[k]+"\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + Params.args[k];
+ }
+
+ /* Also stick the array of pointers into memory that will not be GCd */
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(gsInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsFileAsync: gsapi_init_with_args error");
+ }
+ }
+ catch (DllNotFoundException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = Params;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = Params;
+ }
+ catch (GhostscriptException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ }
+ catch (Exception except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ }
+ finally
+ {
+ if (cleanup)
+ {
+ /* All the pinned items need to be freed so the GC can do its job */
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+
+ int code1 = ghostapi.gsapi_exit(gsInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(gsInstance);
+ Params.return_code = code;
+
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ {
+ Params.result = GS_Result_t.gsOK;
+ e.Result = Params;
+ }
+ else
+ {
+ Params.result = GS_Result_t.gsFAILED;
+ e.Result = Params;
+ }
+ gsInstance = IntPtr.Zero;
+ }
+ }
+ return;
+ }
+
+ /* Processing with gsapi_run_string for callback progress */
+ private void gsBytesAsync(object sender, DoWorkEventArgs e)
+ {
+ gsParamState_t Params = (gsParamState_t)e.Argument;
+ int num_params = Params.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ GCHandle argPtrsStable = new GCHandle();
+ Byte[] Buffer = new Byte[gsConstants.GS_READ_BUFFER];
+ BackgroundWorker worker = sender as BackgroundWorker;
+ int code = 0;
+ int exitcode = 0;
+ var Feed = new GCHandle();
+ var FeedPtr = new IntPtr();
+ String[] strParams = new String[num_params];
+ FileStream fs = null;
+ bool cleanup = true;
+
+ try
+ {
+ /* Open the file */
+ fs = new FileStream(Params.inputfile, FileMode.Open);
+ var len = (int)fs.Length;
+
+ code = ghostapi.gsapi_new_instance(out gsInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_new_instance error");
+ }
+ code = ghostapi.gsapi_set_stdio(gsInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_set_stdio error");
+ }
+ code = ghostapi.gsapi_set_arg_encoding(gsInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_set_arg_encoding error");
+ }
+
+ /* Now convert our Strings to char* and get pinned handles to these.
+ * This keeps the c# GC from moving stuff around on us */
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((Params.args[k]+"\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + Params.args[k];
+ }
+
+ /* Also stick the array of pointers into memory that will not be GCd */
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(gsInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0)
+ {
+ throw new GhostscriptException("gsBytesAsync: gsapi_init_with_args error");
+ }
+
+ /* Pin data buffer */
+ Feed = GCHandle.Alloc(Buffer, GCHandleType.Pinned);
+ FeedPtr = Feed.AddrOfPinnedObject();
+
+ /* Now start feeding the input piece meal and do a call back
+ * with our progress */
+ if (code == 0)
+ {
+ int count;
+ double perc;
+ int total = 0;
+ int ret_code;
+
+ ret_code = ghostapi.gsapi_run_string_begin(gsInstance, 0, ref exitcode);
+ if (exitcode < 0)
+ {
+ code = exitcode;
+ throw new GhostscriptException("gsBytesAsync: gsapi_run_string_begin error");
+ }
+
+ while ((count = fs.Read(Buffer, 0, gsConstants.GS_READ_BUFFER)) > 0)
+ {
+ ret_code = ghostapi.gsapi_run_string_continue(gsInstance, FeedPtr, count, 0, ref exitcode);
+ if (exitcode < 0)
+ {
+ code = exitcode;
+ throw new GhostscriptException("gsBytesAsync: gsapi_run_string_continue error");
+ }
+
+ total = total + count;
+ perc = 100.0 * (double)total / (double)len;
+ worker.ReportProgress((int)perc);
+ if (worker.CancellationPending == true)
+ {
+ e.Cancel = true;
+ break;
+ }
+ }
+ ret_code = ghostapi.gsapi_run_string_end(gsInstance, 0, ref exitcode);
+ if (exitcode < 0)
+ {
+ code = exitcode;
+ throw new GhostscriptException("gsBytesAsync: gsapi_run_string_end error");
+ }
+ }
+ }
+ catch (DllNotFoundException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = Params;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = Params;
+ }
+ catch (GhostscriptException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ }
+ catch (Exception except)
+ {
+ /* Could be a file io issue */
+ gsDLLProblemMain("Exception: " + except.Message);
+ Params.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = Params;
+ }
+ finally
+ {
+ if (cleanup)
+ {
+ fs.Close();
+
+ /* Free pinned items */
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+ Feed.Free();
+
+ /* gs clean up */
+ int code1 = ghostapi.gsapi_exit(gsInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(gsInstance);
+ Params.return_code = code;
+
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ {
+ Params.result = GS_Result_t.gsOK;
+ e.Result = Params;
+ }
+ else
+ {
+ Params.result = GS_Result_t.gsFAILED;
+ e.Result = Params;
+ }
+ gsInstance = IntPtr.Zero;
+ }
+ }
+ return;
+ }
+
+ /* Worker task for using display device */
+ private void DisplayDeviceAsync(object sender, DoWorkEventArgs e)
+ {
+ int code = 0;
+ gsParamState_t gsparams = (gsParamState_t)e.Argument;
+ GCHandle argPtrsStable = new GCHandle();
+
+ int num_params = gsparams.args.Count;
+ var argParam = new GCHandle[num_params];
+ var argPtrs = new IntPtr[num_params];
+ List<byte[]> CharacterArray = new List<byte[]>(num_params);
+ bool cleanup = true;
+
+ gsparams.result = GS_Result_t.gsOK;
+
+ try
+ {
+ code = ghostapi.gsapi_new_instance(out dispInstance, IntPtr.Zero);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_new_instance error");
+ }
+
+ code = ghostapi.gsapi_set_stdio(dispInstance, stdin_callback, stdout_callback, stderr_callback);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_set_stdio error");
+ }
+
+ code = ghostapi.gsapi_set_arg_encoding(dispInstance, (int)gsEncoding.GS_ARG_ENCODING_UTF8);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_set_arg_encoding error");
+ }
+
+ code = ghostapi.gsapi_set_display_callback(dispInstance, ptr_display_struct);
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_set_display_callback error");
+ }
+
+ String fullcommand = "";
+ for (int k = 0; k < num_params; k++)
+ {
+ CharacterArray.Add(System.Text.Encoding.UTF8.GetBytes((gsparams.args[k] + "\0").ToCharArray()));
+ argParam[k] = GCHandle.Alloc(CharacterArray[k], GCHandleType.Pinned);
+ argPtrs[k] = argParam[k].AddrOfPinnedObject();
+ fullcommand = fullcommand + " " + gsparams.args[k];
+ }
+ argPtrsStable = GCHandle.Alloc(argPtrs, GCHandleType.Pinned);
+
+ fullcommand = "Command Line: " + fullcommand + "\n";
+ gsIOUpdateMain(fullcommand, fullcommand.Length);
+ code = ghostapi.gsapi_init_with_args(dispInstance, num_params, argPtrsStable.AddrOfPinnedObject());
+ if (code < 0)
+ {
+ throw new GhostscriptException("DisplayDeviceAsync: gsapi_init_with_args error");
+ }
+ }
+
+ catch (DllNotFoundException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = gsparams;
+ }
+ catch (BadImageFormatException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ cleanup = false;
+ e.Result = gsparams;
+ }
+ catch (GhostscriptException except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ if (dispInstance != IntPtr.Zero)
+ ghostapi.gsapi_delete_instance(dispInstance);
+ dispInstance = IntPtr.Zero;
+ }
+ catch (Exception except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ gsparams.result = GS_Result_t.gsFAILED;
+ if (dispInstance != IntPtr.Zero)
+ ghostapi.gsapi_delete_instance(dispInstance);
+ dispInstance = IntPtr.Zero;
+ }
+ finally
+ {
+ if (cleanup)
+ {
+ for (int k = 0; k < num_params; k++)
+ {
+ argParam[k].Free();
+ }
+ argPtrsStable.Free();
+ e.Result = gsparams;
+
+ if (gsparams.result == GS_Result_t.gsOK && (gsparams.task == GS_Task_t.DISPLAY_DEV_NON_PDF ||
+ gsparams.task == GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF))
+ {
+ gsParamState_t result = DisplayDeviceClose();
+ if (gsparams.result == 0)
+ {
+ gsparams.result = result.result;
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ /* Call the appropriate worker thread based upon the task
+ * that we have to do */
+ private gsStatus RunGhostscriptAsync(gsParamState_t Params)
+ {
+ try
+ {
+ if (m_worker != null && m_worker.IsBusy)
+ {
+ m_worker.CancelAsync();
+ return gsStatus.GS_BUSY;
+ }
+ if (m_worker == null)
+ {
+ m_worker = new BackgroundWorker();
+ m_worker.WorkerReportsProgress = true;
+ m_worker.WorkerSupportsCancellation = true;
+ m_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(gsCompleted);
+ m_worker.ProgressChanged += new ProgressChangedEventHandler(gsProgressChanged);
+ }
+
+ switch (Params.task)
+ {
+ case GS_Task_t.PS_DISTILL:
+ m_worker.DoWork += new DoWorkEventHandler(gsBytesAsync);
+ break;
+ case GS_Task_t.DISPLAY_DEV_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_PDF:
+ m_worker.DoWork += new DoWorkEventHandler(DisplayDeviceAsync);
+ break;
+ case GS_Task_t.SAVE_RESULT:
+ case GS_Task_t.CREATE_XPS:
+ default:
+ m_worker.DoWork += new DoWorkEventHandler(gsFileAsync);
+ break;
+ }
+
+ m_params = Params;
+ m_worker.RunWorkerAsync(Params);
+ return gsStatus.GS_READY;
+ }
+ catch (OutOfMemoryException)
+ {
+ Console.WriteLine("Memory allocation failed during gs rendering\n");
+ return gsStatus.GS_ERROR;
+ }
+ }
+
+#region public_methods
+
+ /* Direct call on gsapi to get the version of the DLL we are using */
+ public String GetVersion()
+ {
+ gsapi_revision_t vers;
+ vers.copyright = IntPtr.Zero;
+ vers.product = IntPtr.Zero;
+ vers.revision = 0;
+ vers.revisiondate = 0;
+ int size = System.Runtime.InteropServices.Marshal.SizeOf(vers);
+
+ try
+ {
+ if (ghostapi.gsapi_revision(ref vers, size) == 0)
+ {
+ String product = Marshal.PtrToStringAnsi(vers.product);
+ String output;
+ int major = vers.revision / 100;
+ int minor = vers.revision - major * 100;
+ String versnum = major + "." + minor;
+ output = product + " " + versnum;
+ return output;
+ }
+ else
+ return null;
+ }
+ catch (Exception except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ }
+ return null;
+ }
+
+ /* Use syncronous call to ghostscript to get the
+ * number of pages in a PDF file */
+ public int GetPageCount(String fileName)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ gsParamState_t result;
+ gsparams.args = new List<string>();
+
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNODISPLAY");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ //gsparams.args.Add("-q");
+ gsparams.args.Add("-sFile=\"" + fileName + "\"");
+ gsparams.args.Add("--permit-file-read=\"" + fileName + "\"");
+ gsparams.args.Add("-c");
+ gsparams.args.Add("\"File (r) file runpdfbegin pdfpagecount = quit\"");
+ gsparams.task = GS_Task_t.GET_PAGE_COUNT;
+ m_params = gsparams;
+
+ result = gsFileSync(gsparams);
+
+ if (result.result == GS_Result_t.gsOK)
+ return m_params.num_pages;
+ else
+ return -1;
+ }
+
+#if WPF
+ /* Launch a thread to create XPS document for windows printing */
+ public gsStatus CreateXPS(String fileName, int resolution, int num_pages,
+ Print printsettings, int firstpage, int lastpage)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ gsparams.args = new List<string>();
+
+ gsparams.inputfile = fileName;
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-r" + resolution.ToString());
+ gsparams.args.Add("-dNOCACHE");
+ gsparams.args.Add("-sDEVICE=xpswrite");
+ gsparams.args.Add("-dFirstPage=" + firstpage.ToString());
+ gsparams.args.Add("-dLastPage=" + lastpage.ToString());
+
+ if (printsettings != null)
+ {
+ double paperheight;
+ double paperwidth;
+
+ if (printsettings.m_pagedetails.Landscape == true)
+ {
+ paperheight = printsettings.m_pagedetails.PrintableArea.Width;
+ paperwidth = printsettings.m_pagedetails.PrintableArea.Height;
+ }
+ else
+ {
+ paperheight = printsettings.m_pagedetails.PrintableArea.Height;
+ paperwidth = printsettings.m_pagedetails.PrintableArea.Width;
+ }
+
+ double width = paperwidth * 72.0 / 100.0;
+ double height = paperheight * 72.0 / 100.0;
+ gsparams.args.Add("-dDEVICEWIDTHPOINTS=" + width);
+ gsparams.args.Add("-dDEVICEHEIGHTPOINTS=" + height);
+ gsparams.args.Add("-dFIXEDMEDIA");
+
+ /* Scale and translate and rotate if needed */
+ if (printsettings.xaml_autofit.IsChecked == true)
+ gsparams.args.Add("-dFitPage");
+ }
+ gsparams.outputfile = Path.GetTempFileName();
+ gsparams.args.Add("-o");
+ gsparams.args.Add(gsparams.outputfile);
+ gsparams.args.Add("-f");
+ gsparams.args.Add(fileName);
+ gsparams.task = GS_Task_t.CREATE_XPS;
+
+ return RunGhostscriptAsync(gsparams);
+ }
+#endif
+
+ /* Launch a thread rendering all the pages with the display device
+ * to distill an input PS file and save as a PDF. */
+ public gsStatus DistillPS(String fileName, int resolution)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ gsparams.args = new List<string>();
+
+ gsparams.inputfile = fileName;
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-sDEVICE=pdfwrite");
+ gsparams.outputfile = Path.GetTempFileName();
+ gsparams.args.Add("-o" + gsparams.outputfile);
+ gsparams.task = GS_Task_t.PS_DISTILL;
+
+ return RunGhostscriptAsync(gsparams);
+ }
+
+ /* Launch a thread rendering all the pages with the display device
+ * to collect thumbnail images or full resolution. */
+ public gsStatus gsDisplayDeviceRenderAll(String fileName, double zoom, bool aa, GS_Task_t task)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ int format = (gsConstants.DISPLAY_COLORS_RGB |
+ gsConstants.DISPLAY_DEPTH_8 |
+ gsConstants.DISPLAY_LITTLEENDIAN);
+ int resolution = (int)(72.0 * zoom + 0.5);
+
+ gsparams.args = new List<string>();
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-r" + resolution);
+ if (aa)
+ {
+ gsparams.args.Add("-dTextAlphaBits=4");
+ gsparams.args.Add("-dGraphicsAlphaBits=4");
+ }
+ gsparams.args.Add("-sDEVICE=display");
+ gsparams.args.Add("-dDisplayFormat=" + format);
+ gsparams.args.Add("-f");
+ gsparams.args.Add(fileName);
+ gsparams.task = task;
+ gsparams.currpage = 0;
+
+ return RunGhostscriptAsync(gsparams);
+ }
+
+
+ /* Launch a thread rendering a set of pages with the display device. For use with languages
+ that can be indexed via pages which include PDF and XPS */
+ public gsStatus gsDisplayDeviceRenderPages(String fileName, int first_page, int last_page, double zoom)
+ {
+ gsParamState_t gsparams = new gsParamState_t();
+ int format = (gsConstants.DISPLAY_COLORS_RGB |
+ gsConstants.DISPLAY_DEPTH_8 |
+ gsConstants.DISPLAY_LITTLEENDIAN);
+ int resolution = (int)(72.0 * zoom + 0.5);
+
+ gsparams.args = new List<string>();
+ gsparams.args.Add("gs");
+ gsparams.args.Add("-dNOPAUSE");
+ gsparams.args.Add("-dBATCH");
+ gsparams.args.Add("-I%rom%Resource/Init/");
+ gsparams.args.Add("-dSAFER");
+ gsparams.args.Add("-r" + resolution);
+ gsparams.args.Add("-sDEVICE=display");
+ gsparams.args.Add("-dDisplayFormat=" + format);
+ gsparams.args.Add("-dFirstPage=" + first_page);
+ gsparams.args.Add("-dLastPage=" + last_page);
+ gsparams.args.Add("-f");
+ gsparams.args.Add(fileName);
+ gsparams.task = GS_Task_t.DISPLAY_DEV_PDF;
+ gsparams.currpage = first_page - 1;
+
+ return RunGhostscriptAsync(gsparams);
+ }
+
+ /* Close the display device and delete the instance */
+ public gsParamState_t DisplayDeviceClose()
+ {
+ int code = 0;
+ gsParamState_t out_params = new gsParamState_t();
+
+ out_params.result = GS_Result_t.gsOK;
+
+ try
+ {
+ int code1 = ghostapi.gsapi_exit(dispInstance);
+ if ((code == 0) || (code == gsConstants.E_QUIT))
+ code = code1;
+
+ ghostapi.gsapi_delete_instance(dispInstance);
+ dispInstance = IntPtr.Zero;
+
+ }
+ catch (Exception except)
+ {
+ gsDLLProblemMain("Exception: " + except.Message);
+ out_params.result = GS_Result_t.gsFAILED;
+ }
+
+ return out_params;
+ }
+
+ /* Check if gs is currently busy */
+ public gsStatus GetStatus()
+ {
+ if (m_worker != null && m_worker.IsBusy)
+ return gsStatus.GS_BUSY;
+ else
+ return gsStatus.GS_READY;
+ }
+
+ /* Cancel worker */
+ public void Cancel()
+ {
+ m_worker.CancelAsync();
+ }
+#endregion
+ }
+}
diff --git a/demos/csharp/linux/gs_mono.sln b/demos/csharp/linux/gs_mono.sln
new file mode 100644
index 00000000..c3a015a1
--- /dev/null
+++ b/demos/csharp/linux/gs_mono.sln
@@ -0,0 +1,17 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gtk_viewer", "gtk_viewer\gtk_viewer.csproj", "{8941969C-A209-4109-AF7B-E4CF9D309B5B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Debug|x86.ActiveCfg = Debug|x86
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Debug|x86.Build.0 = Debug|x86
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Release|x86.ActiveCfg = Release|x86
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+EndGlobal
diff --git a/demos/csharp/linux/gtk_viewer/Properties/AssemblyInfo.cs b/demos/csharp/linux/gtk_viewer/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..a9d0d285
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("gs_mono_example")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("${AuthorCopyright}")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/demos/csharp/linux/gtk_viewer/gtk-gui/generated.cs b/demos/csharp/linux/gtk_viewer/gtk-gui/generated.cs
new file mode 100644
index 00000000..4842e95d
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/gtk-gui/generated.cs
@@ -0,0 +1,30 @@
+
+// This file has been generated by the GUI designer. Do not modify.
+namespace Stetic
+{
+ internal class Gui
+ {
+ private static bool initialized;
+
+ internal static void Initialize(Gtk.Widget iconRenderer)
+ {
+ if ((Stetic.Gui.initialized == false))
+ {
+ Stetic.Gui.initialized = true;
+ }
+ }
+ }
+
+ internal class ActionGroups
+ {
+ public static Gtk.ActionGroup GetActionGroup(System.Type type)
+ {
+ return Stetic.ActionGroups.GetActionGroup(type.FullName);
+ }
+
+ public static Gtk.ActionGroup GetActionGroup(string name)
+ {
+ return null;
+ }
+ }
+}
diff --git a/demos/csharp/linux/gtk_viewer/gtk-gui/gtk_viewer.src.gsOutput.cs b/demos/csharp/linux/gtk_viewer/gtk-gui/gtk_viewer.src.gsOutput.cs
new file mode 100644
index 00000000..0eab65c6
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/gtk-gui/gtk_viewer.src.gsOutput.cs
@@ -0,0 +1,10 @@
+
+namespace gtk_viewer.src
+{
+ public partial class gsOutput
+ {
+ private void Build()
+ {
+ }
+ }
+}
diff --git a/demos/csharp/linux/gtk_viewer/gtk-gui/gui.stetic b/demos/csharp/linux/gtk_viewer/gtk-gui/gui.stetic
new file mode 100644
index 00000000..172ba497
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/gtk-gui/gui.stetic
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<stetic-interface>
+ <configuration>
+ <images-root-path>..</images-root-path>
+ <target-gtk-version>2.12</target-gtk-version>
+ </configuration>
+ <import>
+ <widget-library name="../bin/Debug/gtk_viewer.exe" internal="true" />
+ </import>
+ <widget class="Gtk.Window" id="gtk_viewer.src.gsOutput" design-size="400 300">
+ <property name="MemberName" />
+ <property name="Title" translatable="yes">gsOutput</property>
+ <property name="WindowPosition">CenterOnParent</property>
+ <child>
+ <placeholder />
+ </child>
+ </widget>
+</stetic-interface> \ No newline at end of file
diff --git a/demos/csharp/linux/gtk_viewer/gtk_viewer.csproj b/demos/csharp/linux/gtk_viewer/gtk_viewer.csproj
new file mode 100644
index 00000000..ee512951
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/gtk_viewer.csproj
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+ <ProjectGuid>{8941969C-A209-4109-AF7B-E4CF9D309B5B}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <RootNamespace>gtk_viewer</RootNamespace>
+ <AssemblyName>gtk_viewer</AssemblyName>
+ <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;MONO</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <PlatformTarget>x64</PlatformTarget>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <DefineConstants>MONO</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <PlatformTarget>x64</PlatformTarget>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="Mono.Posix" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="Mono.CSharp" />
+ <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
+ <Package>gtk-sharp-2.0</Package>
+ </Reference>
+ <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
+ <Package>gtk-sharp-2.0</Package>
+ </Reference>
+ <Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
+ <Package>glib-sharp-2.0</Package>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="gtk-gui\gui.stetic">
+ <LogicalName>gui.stetic</LogicalName>
+ </EmbeddedResource>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="gtk-gui\generated.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="src\MainWindow.cs" />
+ <Compile Include="src\Program.cs" />
+ <Compile Include="src\MainThumbRendering.cs" />
+ <Compile Include="src\DocPage.cs" />
+ <Compile Include="src\MainRender.cs" />
+ <Compile Include="src\TempFile.cs" />
+ <Compile Include="src\MainZoom.cs" />
+ <Compile Include="..\..\api\ghostapi.cs">
+ <Link>src\ghostapi.cs</Link>
+ </Compile>
+ <Compile Include="src\gsOutput.cs" />
+ <Compile Include="gtk-gui\gtk_viewer.src.gsOutput.cs" />
+ <Compile Include="..\..\api\ghostmono.cs">
+ <Link>src\ghostmono.cs</Link>
+ </Compile>
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <Import Project="packages\GtkSharp.3.22.25.98\build\GtkSharp.targets" Condition="Exists('packages\GtkSharp.3.22.25.98\build\GtkSharp.targets')" />
+</Project> \ No newline at end of file
diff --git a/demos/csharp/linux/gtk_viewer/gtk_viewer.sln b/demos/csharp/linux/gtk_viewer/gtk_viewer.sln
new file mode 100644
index 00000000..229145da
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/gtk_viewer.sln
@@ -0,0 +1,17 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gtk_viewer", "gtk_viewer.csproj", "{8941969C-A209-4109-AF7B-E4CF9D309B5B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Debug|x86.ActiveCfg = Debug|x86
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Debug|x86.Build.0 = Debug|x86
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Release|x86.ActiveCfg = Release|x86
+ {8941969C-A209-4109-AF7B-E4CF9D309B5B}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+EndGlobal
diff --git a/demos/csharp/linux/gtk_viewer/src/DocPage.cs b/demos/csharp/linux/gtk_viewer/src/DocPage.cs
new file mode 100644
index 00000000..d1cb5493
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/DocPage.cs
@@ -0,0 +1,108 @@
+using System;
+using System.ComponentModel;
+using System.Collections.ObjectModel;
+
+namespace gs_mono_example
+{
+ public class DocPage : INotifyPropertyChanged
+ {
+ private int height;
+ private int width;
+ private double zoom;
+ private Gdk.Pixbuf pixbuf;
+ private String pagename;
+ private int pagenum;
+ private Page_Content_t content;
+
+ public int Height
+ {
+ get { return height; }
+ set
+ {
+ height = value;
+ OnPropertyChanged("Height");
+ }
+ }
+
+ public int Width
+ {
+ get { return width; }
+ set
+ {
+ width = value;
+ OnPropertyChanged("Width");
+ }
+ }
+
+ public double Zoom
+ {
+ get { return zoom; }
+ set { zoom = value; }
+ }
+
+ public Gdk.Pixbuf PixBuf
+ {
+ get { return pixbuf; }
+ set
+ {
+ pixbuf = value;
+ OnPropertyChanged("PixBuf");
+ }
+ }
+
+ public String PageName
+ {
+ get { return pagename; }
+ set { pagename = value; }
+ }
+
+ public int PageNum
+ {
+ get { return pagenum; }
+ set { pagenum = value; }
+ }
+ public Page_Content_t Content
+ {
+ get { return content; }
+ set { content = value; }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ // Create the OnPropertyChanged method to raise the event
+ protected void OnPropertyChanged(string name)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ {
+ handler(this, new PropertyChangedEventArgs(name));
+ }
+ }
+
+ public DocPage()
+ {
+ this.height = 0;
+ this.width = 0;
+ this.zoom = 0;
+ this.pixbuf = null;
+ this.pagenum = -1;
+ this.pagename = "";
+ }
+
+ public DocPage(int Height, int Width, double Zoom, Gdk.Pixbuf PixBuf, int PageNum)
+ {
+ this.height = Height;
+ this.width = Width;
+ this.zoom = Zoom;
+ this.pixbuf = PixBuf;
+ this.pagename = ("Page " + (pagenum + 1));
+ }
+ };
+ public class Pages : ObservableCollection<DocPage>
+ {
+ public Pages()
+ : base()
+ {
+ }
+ }
+}
diff --git a/demos/csharp/linux/gtk_viewer/src/MainRender.cs b/demos/csharp/linux/gtk_viewer/src/MainRender.cs
new file mode 100644
index 00000000..e4749f1c
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/MainRender.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Runtime;
+using System.Runtime.InteropServices;
+using GhostMono;
+
+namespace gs_mono_example
+{
+ public partial class MainWindow
+ {
+ int m_firstpage;
+ int m_lastpage;
+ int m_current_page;
+ Gtk.TreeIter m_tree_iter;
+
+ /* For PDF optimization */
+ private void PageRangeRender(int first_page, int last_page)
+ {
+ bool render_pages = false;
+ for (int k = first_page; k <= last_page; k++)
+ {
+ if (m_docPages[k].Content != Page_Content_t.FULL_RESOLUTION)
+ {
+ render_pages = true;
+ break;
+ }
+ }
+ if (!render_pages)
+ return;
+
+ m_busy_render = true;
+ m_firstpage = first_page;
+ m_lastpage = last_page;
+ //m_ghostscript.gsDisplayDeviceRender(m_currfile, first_page + 1, last_page + 1, 1.0);
+ }
+
+ /* Callback from ghostscript with the rendered image. */
+ private void MainPageCallback(int width, int height, int raster, double zoom_in,
+ int page_num, IntPtr data)
+ {
+ Byte[] bitmap = null;
+
+ bitmap = new byte[raster * height];
+ Marshal.Copy(data, bitmap, 0, raster * height);
+ DocPage doc_page = m_docPages[m_current_page];
+
+ if (doc_page.Content != Page_Content_t.FULL_RESOLUTION ||
+ m_aa_change)
+ {
+ doc_page.Width = width;
+ doc_page.Height = height;
+ doc_page.Content = Page_Content_t.FULL_RESOLUTION;
+
+ doc_page.Zoom = m_doczoom;
+ doc_page.PixBuf = new Gdk.Pixbuf(bitmap,
+ Gdk.Colorspace.Rgb, false, 8, width,
+ height, raster);
+
+ m_GtkimageStoreMain.SetValue(m_tree_iter, 0, doc_page.PixBuf);
+ }
+
+ if (page_num == 1)
+ m_page_scroll_pos[0] = height;
+ else
+ m_page_scroll_pos[page_num - 1] = height + m_page_scroll_pos[page_num - 2];
+
+ m_GtkimageStoreMain.IterNext(ref m_tree_iter);
+ m_current_page += 1;
+ m_GtkProgressBar.Fraction = ((double)page_num / ((double)page_num + 1));
+ return;
+ }
+
+ private void RenderingDone()
+ {
+ if (m_firstime)
+ {
+ for (int k = 0; k < m_numpages; k++)
+ {
+ pagesizes_t size = new pagesizes_t();
+ size.height = m_docPages[k].Height;
+ size.width = m_docPages[k].Width;
+ m_page_sizes.Add(size);
+ }
+ }
+
+ m_aa_change = false;
+ m_firstime = false;
+ m_busy_render = false;
+
+ /* Free up the large objects generated from the image pages. If
+ * this is not done, the application will run out of memory as
+ * the user does repeated zoom or aa changes, generating more and
+ * more pages without freeing previous ones. The large object heap
+ * is handled differently than other memory in terms of its GC */
+ GCSettings.LargeObjectHeapCompactionMode =
+ GCLargeObjectHeapCompactionMode.CompactOnce;
+ GC.Collect();
+
+ RemoveProgressBar();
+ m_file_open = true;
+ m_ghostscript.gsPageRenderedMain -= new ghostsharp.gsCallBackPageRenderedMain(gsPageRendered);
+ m_GtkaaCheck.Sensitive = true;
+ m_GtkZoomMinus.Sensitive = true;
+ m_GtkZoomPlus.Sensitive = true;
+ }
+
+ /* Render all pages full resolution */
+ private void RenderMainFirst()
+ {
+ m_firstpage = 1;
+ m_current_page = 0;
+ m_GtkimageStoreMain.GetIterFirst(out m_tree_iter);
+ m_busy_render = true;
+ m_ghostscript.gsPageRenderedMain += new ghostsharp.gsCallBackPageRenderedMain(gsPageRendered);
+ AddProgressBar("Rendering Pages");
+ m_ghostscript.gsDisplayDeviceRenderAll(m_currfile, m_doczoom, m_aa, GS_Task_t.DISPLAY_DEV_NON_PDF);
+ }
+
+ /* Render all, but only if not already busy, called via zoom or aa changes */
+ private void RenderMainAll()
+ {
+ try
+ {
+ if (!m_init_done)
+ return;
+ m_GtkaaCheck.Sensitive = false;
+ m_GtkZoomMinus.Sensitive = false;
+ m_GtkZoomPlus.Sensitive = false;
+ RenderMainFirst();
+ }
+ catch(Exception except)
+ {
+ var mess = except.Message;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/demos/csharp/linux/gtk_viewer/src/MainThumbRendering.cs b/demos/csharp/linux/gtk_viewer/src/MainThumbRendering.cs
new file mode 100644
index 00000000..0d64fa13
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/MainThumbRendering.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using GhostMono;
+
+namespace gs_mono_example
+{
+ public partial class MainWindow
+ {
+ private static List<DocPage> m_thumbnails;
+
+ /* Assign current pages to blown up thumbnail images */
+ private void ThumbAssignMain(int page_num, int width, int height, double zoom_in)
+ {
+ DocPage doc_page = new DocPage();
+ doc_page.Content = Page_Content_t.THUMBNAIL;
+ doc_page.Zoom = zoom_in;
+ doc_page.Width = (int)(width / (Constants.SCALE_THUMB));
+ doc_page.Height = (int)(height / (Constants.SCALE_THUMB));
+ doc_page.PixBuf = m_thumbnails[page_num - 1].PixBuf.ScaleSimple(doc_page.Width, doc_page.Height, Gdk.InterpType.Nearest);
+ doc_page.PageNum = page_num;
+ m_docPages.Add(doc_page);
+
+ if (page_num == 1)
+ m_page_scroll_pos.Add(height);
+ else
+ m_page_scroll_pos.Add(height + m_page_scroll_pos[page_num - 2]);
+ }
+
+ private void ThumbsDone()
+ {
+ m_GtkProgressBar.Fraction = 1.0;
+ m_ghostscript.gsPageRenderedMain -= new ghostsharp.gsCallBackPageRenderedMain(gsThumbRendered);
+ m_numpages = m_thumbnails.Count;
+ if (m_numpages < 1)
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "File failed to open properly");
+ CleanUp();
+ }
+ else
+ {
+ m_currpage = 1;
+ m_GtkpageEntry.Text = "1";
+ m_GtkpageTotal.Text = "/" + m_numpages;
+ m_GtkzoomEntry.Text = "100";
+
+ m_GtkmainScroll.ShowAll();
+ m_init_done = true;
+ RemoveProgressBar();
+ RenderMainFirst();
+ }
+ }
+
+ /* Callback from GS with the rendered thumbnail. Also update progress */
+ private void ThumbPageCallback(int width, int height, int raster, double zoom_in,
+ int page_num, IntPtr data)
+ {
+ Byte[] bitmap = null;
+
+ bitmap = new byte[raster * height];
+ Marshal.Copy(data, bitmap, 0, raster * height);
+ DocPage doc_page = new DocPage();
+ m_thumbnails.Add(doc_page);
+
+ doc_page.Content = Page_Content_t.THUMBNAIL;
+ doc_page.Width = width;
+ doc_page.Height = height;
+ doc_page.Zoom = zoom_in;
+ doc_page.PixBuf = new Gdk.Pixbuf(bitmap,
+ Gdk.Colorspace.Rgb, false, 8, width,
+ height, raster);
+ m_GtkimageStoreThumb.AppendValues(doc_page.PixBuf);
+
+ ThumbAssignMain(page_num, width, height, 1.0);
+ m_GtkimageStoreMain.AppendValues(m_docPages[page_num-1].PixBuf);
+
+ m_GtkProgressBar.Fraction = ((double)page_num / ((double)page_num + 1));
+ return;
+ }
+
+ /* Render the thumbnail images */
+ private void RenderThumbs()
+ {
+ AddProgressBar("Rendering Thumbs");
+ m_ghostscript.gsPageRenderedMain +=
+ new ghostsharp.gsCallBackPageRenderedMain(gsThumbRendered);
+ m_ghostscript.gsDisplayDeviceRenderAll(m_currfile,
+ Constants.SCALE_THUMB, false, GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF);
+ }
+ }
+} \ No newline at end of file
diff --git a/demos/csharp/linux/gtk_viewer/src/MainWindow.cs b/demos/csharp/linux/gtk_viewer/src/MainWindow.cs
new file mode 100644
index 00000000..f621cd47
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/MainWindow.cs
@@ -0,0 +1,713 @@
+using System;
+using System.Collections.Generic;
+using Gtk;
+using GhostMono;
+using System.Diagnostics;
+
+namespace gs_mono_example
+{
+ static class Constants
+ {
+ public const double SCALE_THUMB = 0.1;
+ public const int DEFAULT_GS_RES = 300;
+ public const int ZOOM_MAX = 4;
+ public const double ZOOM_MIN = .25;
+ }
+
+ public enum NotifyType_t
+ {
+ MESS_STATUS,
+ MESS_ERROR
+ };
+
+ public enum Page_Content_t
+ {
+ FULL_RESOLUTION = 0,
+ THUMBNAIL,
+ OLD_RESOLUTION,
+ LOW_RESOLUTION,
+ NOTSET,
+ BLANK
+ };
+
+ public struct pagesizes_t
+ {
+ public double width;
+ public double height;
+ }
+
+ public partial class MainWindow : Gtk.Window
+ {
+ ghostsharp m_ghostscript;
+ bool m_file_open;
+ String m_currfile;
+ String m_extension;
+ List<TempFile> m_tempfiles;
+ String m_origfile;
+ int m_currpage;
+ gsOutput m_gsoutput;
+ public int m_numpages;
+ private static Pages m_docPages;
+ private static double m_doczoom;
+ public List<pagesizes_t> m_page_sizes;
+ bool m_init_done;
+ bool m_busy_render;
+ bool m_firstime;
+ bool m_aa;
+ bool m_aa_change;
+ List<int> m_page_scroll_pos;
+ Gtk.ProgressBar m_GtkProgressBar;
+ Label m_GtkProgressLabel;
+ HBox m_GtkProgressBox;
+ Gtk.TreeView m_GtkTreeThumb;
+ Gtk.TreeView m_GtkTreeMain;
+ Gtk.VBox m_GtkvBoxMain;
+ Label m_GtkpageTotal;
+ Entry m_GtkpageEntry;
+ Gtk.ListStore m_GtkimageStoreThumb;
+ Gtk.ListStore m_GtkimageStoreMain;
+ Gtk.ScrolledWindow m_GtkthumbScroll;
+ Gtk.ScrolledWindow m_GtkmainScroll;
+ Gtk.Entry m_GtkzoomEntry;
+ Gtk.CheckButton m_GtkaaCheck;
+ Gtk.Button m_GtkZoomPlus;
+ Gtk.Button m_GtkZoomMinus;
+ String m_zoom_txt;
+ String m_page_txt;
+ bool m_ignore_scroll_change;
+
+ void ShowMessage(NotifyType_t type, string message)
+ {
+ Gtk.MessageType mess;
+
+ if (type == NotifyType_t.MESS_ERROR)
+ {
+ mess = MessageType.Error;
+ }
+ else
+ {
+ mess = MessageType.Warning;
+ }
+
+ /* Dispatch on UI thread */
+ Gtk.Application.Invoke(delegate
+ {
+ MessageDialog md = new MessageDialog(null,
+ DialogFlags.Modal,
+ mess,
+ ButtonsType.Ok,
+ message);
+ md.SetPosition(WindowPosition.Center);
+ md.ShowAll();
+ md.Run();
+ md.Destroy();
+ });
+ }
+
+ public MainWindow() : base(Gtk.WindowType.Toplevel)
+ {
+ /* Set up ghostscript calls for progress update */
+ m_ghostscript = new ghostsharp();
+ m_ghostscript.gsUpdateMain += new ghostsharp.gsCallBackMain(gsProgress);
+ m_ghostscript.gsIOUpdateMain += new ghostsharp.gsIOCallBackMain(gsIO);
+ m_ghostscript.gsDLLProblemMain += new ghostsharp.gsDLLProblem(gsDLL);
+
+ DeleteEvent += delegate { Application.Quit(); };
+
+ m_currpage = 0;
+ m_gsoutput = new gsOutput();
+ m_gsoutput.Activate();
+ m_tempfiles = new List<TempFile>();
+ m_thumbnails = new List<DocPage>();
+ m_docPages = new Pages();
+ m_page_sizes = new List<pagesizes_t>();
+ m_page_scroll_pos = new List<int>();
+ m_file_open = false;
+ m_doczoom = 1.0;
+ m_init_done = false;
+ m_busy_render = true;
+ m_firstime = true;
+ m_aa = true;
+ m_aa_change = false;
+ m_zoom_txt = "100";
+ m_page_txt = "1";
+ m_ignore_scroll_change = false;
+
+ /* Set up Vbox in main window */
+ this.SetDefaultSize(500, 700);
+ this.Title = "GhostPDL Mono GTK Demo";
+ m_GtkvBoxMain = new VBox(false, 0);
+ this.Add(m_GtkvBoxMain);
+
+ /* Add Menu Bar to vBox */
+ Gtk.MenuBar menu_bar = new MenuBar();
+ Menu filemenu = new Menu();
+ MenuItem file = new MenuItem("File");
+ file.Submenu = filemenu;
+
+ AccelGroup agr = new AccelGroup();
+ AddAccelGroup(agr);
+
+ ImageMenuItem openi = new ImageMenuItem(Stock.Open, agr);
+ openi.AddAccelerator("activate", agr, new AccelKey(
+ Gdk.Key.o, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
+ openi.Activated += OnOpen;
+ filemenu.Append(openi);
+
+ ImageMenuItem closei = new ImageMenuItem(Stock.Close, agr);
+ closei.AddAccelerator("activate", agr, new AccelKey(
+ Gdk.Key.w, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
+ closei.Activated += OnClose;
+ filemenu.Append(closei);
+
+ MenuItem messagesi = new MenuItem("Show Messages");
+ messagesi.Activated += OnShowMessages;
+ filemenu.Append(messagesi);
+
+ MenuItem about = new MenuItem("About");
+ about.Activated += OnAboutClicked;
+ filemenu.Append(about);
+
+ SeparatorMenuItem sep = new SeparatorMenuItem();
+ filemenu.Append(sep);
+
+ ImageMenuItem quiti = new ImageMenuItem(Stock.Quit, agr);
+ quiti.AddAccelerator("activate", agr, new AccelKey(
+ Gdk.Key.q, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
+ filemenu.Append(quiti);
+ quiti.Activated += OnQuit;
+
+ menu_bar.Append(file);
+
+ m_GtkvBoxMain.PackStart(menu_bar, false, false, 0);
+
+ /* Add a hbox with the page information, zoom control, and aa to vbox */
+ HBox pageBox = new HBox(false, 0);
+ m_GtkpageEntry = new Entry();
+ m_GtkpageEntry.Activated += PageChanged;
+ m_GtkpageEntry.WidthChars = 4;
+ m_GtkpageTotal = new Label("/0");
+ pageBox.PackStart(m_GtkpageEntry, false, false, 0);
+ pageBox.PackStart(m_GtkpageTotal, false, false, 0);
+
+ HBox zoomBox = new HBox(false, 0);
+ m_GtkZoomPlus = new Button();
+ m_GtkZoomPlus.Label = "+";
+ m_GtkZoomPlus.Clicked += ZoomIn;
+ m_GtkZoomMinus = new Button();
+ m_GtkZoomMinus.Label = "–";
+ m_GtkZoomMinus.Clicked += ZoomOut;
+ m_GtkzoomEntry = new Entry();
+ m_GtkzoomEntry.WidthChars = 3;
+ m_GtkzoomEntry.Activated += ZoomChanged;
+ Label precentLabel = new Label("%");
+ zoomBox.PackStart(m_GtkZoomPlus, false, false, 0);
+ zoomBox.PackStart(m_GtkZoomMinus, false, false, 0);
+ zoomBox.PackStart(m_GtkzoomEntry, false, false, 0);
+ zoomBox.PackStart(precentLabel, false, false, 0);
+
+ HBox hBoxControls = new HBox(false, 0);
+ m_GtkaaCheck = new CheckButton("Enable Antialias:");
+ m_GtkaaCheck.Active = true;
+ m_GtkaaCheck.Clicked += AaCheck_Clicked;
+ hBoxControls.PackStart(pageBox, false, false, 0);
+ hBoxControls.PackStart(zoomBox, false, false, 20);
+ hBoxControls.PackStart(m_GtkaaCheck, false, false, 0);
+
+ m_GtkvBoxMain.PackStart(hBoxControls, false, false, 0);
+
+ /* Tree view containing thumbnail and main images */
+ HBox hBoxPages = new HBox(false, 0);
+
+ /* Must be scrollable */
+ m_GtkthumbScroll = new ScrolledWindow();
+ m_GtkthumbScroll.BorderWidth = 5;
+ m_GtkthumbScroll.ShadowType = ShadowType.In;
+
+ m_GtkmainScroll = new ScrolledWindow();
+ m_GtkmainScroll.BorderWidth = 5;
+ m_GtkmainScroll.ShadowType = ShadowType.In;
+ m_GtkmainScroll.Vadjustment.ValueChanged += Vadjustment_Changed;
+
+ m_GtkTreeThumb = new Gtk.TreeView();
+ m_GtkTreeThumb.HeadersVisible = false;
+ m_GtkimageStoreThumb = new Gtk.ListStore(typeof(Gdk.Pixbuf));
+ m_GtkTreeThumb.AppendColumn("Thumb", new Gtk.CellRendererPixbuf(), "pixbuf", 0);
+ m_GtkTreeThumb.Style.YThickness = 100;
+ m_GtkthumbScroll.Add(m_GtkTreeThumb);
+ m_GtkTreeThumb.RowActivated += M_GtkTreeThumb_RowActivated;
+
+ m_GtkTreeMain = new Gtk.TreeView();
+ m_GtkTreeMain.HeadersVisible = false;
+ m_GtkTreeMain.RulesHint = false;
+
+ m_GtkimageStoreMain = new Gtk.ListStore(typeof(Gdk.Pixbuf));
+ m_GtkTreeMain.AppendColumn("Main", new Gtk.CellRendererPixbuf(), "pixbuf", 0);
+ m_GtkmainScroll.Add(m_GtkTreeMain);
+
+ // Separate with gridlines
+ m_GtkTreeMain.EnableGridLines = TreeViewGridLines.Horizontal;
+
+ //To disable selections, set the selection mode to None:
+ m_GtkTreeMain.Selection.Mode = SelectionMode.None;
+
+ hBoxPages.PackStart(m_GtkthumbScroll, false, false, 0);
+ hBoxPages.PackStart(m_GtkmainScroll, true, true, 0);
+
+ m_GtkTreeThumb.Model = m_GtkimageStoreThumb;
+ m_GtkTreeMain.Model = m_GtkimageStoreMain;
+
+ m_GtkvBoxMain.PackStart(hBoxPages, true, true, 0);
+
+ /* For case of opening another file */
+ string[] arguments = Environment.GetCommandLineArgs();
+ if (arguments.Length > 1)
+ {
+ m_currfile = arguments[1];
+ ProcessFile();
+ }
+ }
+
+ void Vadjustment_Changed(object sender, EventArgs e)
+ {
+ if (!m_init_done)
+ {
+ return;
+ }
+
+ if (m_ignore_scroll_change)
+ {
+ m_ignore_scroll_change = false;
+ return;
+ }
+
+ Gtk.Adjustment zz = sender as Gtk.Adjustment;
+ double val = zz.Value;
+ int page = 1;
+
+ for (int k = 0; k < m_numpages; k++)
+ {
+ if (val <= m_page_scroll_pos[k])
+ {
+ m_GtkpageEntry.Text = page.ToString();
+ m_page_txt = m_GtkpageEntry.Text;
+ return;
+ }
+ page += 1;
+ }
+ m_GtkpageEntry.Text = m_numpages.ToString();
+ m_page_txt = m_GtkpageEntry.Text;
+ }
+
+ void OnAboutClicked(object sender, EventArgs args)
+ {
+ AboutDialog about = new AboutDialog();
+ about.ProgramName = "GhostPDL Gtk# Viewer";
+ about.Version = "0.1";
+ about.Copyright = "(c) Artifex Software";
+ about.Comments = @"A demo of GhostPDL API with C# Mono";
+ about.Website = "http://www.artifex.com";
+ about.Run();
+ about.Destroy();
+ }
+
+ /* C# Insanity to get the index of the selected item */
+ void M_GtkTreeThumb_RowActivated(object o, RowActivatedArgs args)
+ {
+ TreeModel treeModel;
+ TreeSelection my_selected_row = m_GtkTreeThumb.Selection;
+
+ var TreePath = my_selected_row.GetSelectedRows(out treeModel);
+ var item = TreePath.GetValue(0);
+ var result = item.ToString();
+ int index = System.Convert.ToInt32(result);
+
+ ScrollMainTo(index);
+ }
+
+ void ScrollMainTo(int index)
+ {
+ m_ignore_scroll_change = true;
+
+ if (index < 0 || index > m_numpages - 1)
+ return;
+
+ if (index == 0)
+ m_GtkmainScroll.Vadjustment.Value = 0;
+ else
+ m_GtkmainScroll.Vadjustment.Value = m_page_scroll_pos[index-1];
+
+ m_currpage = index + 1;
+ m_GtkpageEntry.Text = m_currpage.ToString();
+ m_page_txt = m_GtkpageEntry.Text;
+ }
+
+ void AddProgressBar(String text)
+ {
+ /* Progress bar */
+ m_GtkProgressBox = new HBox(false, 0);
+ m_GtkProgressBar = new ProgressBar();
+ m_GtkProgressBar.Orientation = ProgressBarOrientation.LeftToRight;
+ m_GtkProgressBox.PackStart(m_GtkProgressBar, true, true, 0);
+ m_GtkProgressLabel = new Label(text);
+ m_GtkProgressBox.PackStart(m_GtkProgressLabel, false, false, 0);
+ m_GtkvBoxMain.PackStart(m_GtkProgressBox, false, false, 0);
+ m_GtkProgressBar.Fraction = 0.0;
+ this.ShowAll();
+ }
+
+ void RemoveProgressBar()
+ {
+ m_GtkvBoxMain.Remove(m_GtkProgressBox);
+ this.ShowAll();
+ }
+
+ void AaCheck_Clicked(object sender, EventArgs e)
+ {
+ m_aa = !m_aa;
+ m_aa_change = true;
+ if (m_init_done && !m_busy_render)
+ RenderMainAll();
+ }
+
+ private void OnQuit(object sender, EventArgs e)
+ {
+ Application.Quit();
+ }
+
+ private void OnShowMessages(object sender, EventArgs e)
+ {
+ m_gsoutput.Show();
+ }
+
+ private void OnClose(object sender, EventArgs e)
+ {
+ if (m_init_done)
+ CleanUp();
+ }
+
+ private void gsIO(String mess, int len)
+ {
+ Gtk.TextBuffer buffer = m_gsoutput.m_textView.Buffer;
+ Gtk.TextIter ti = buffer.EndIter;
+
+ var part = mess.Substring(0, len);
+ buffer.Insert(ref ti, part);
+ }
+
+ private void gsDLL(String mess)
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, mess);
+ }
+
+ private void gsThumbRendered(int width, int height, int raster,
+ IntPtr data, gsParamState_t state)
+ {
+ ThumbPageCallback(width, height, raster, state.zoom, state.currpage, data);
+ }
+
+ private void gsPageRendered(int width, int height, int raster,
+ IntPtr data, gsParamState_t state)
+ {
+ MainPageCallback(width, height, raster, state.zoom, state.currpage, data);
+ }
+
+ private void gsProgress(gsThreadCallBack info)
+ {
+ if (info.Completed)
+ {
+ switch (info.Params.task)
+ {
+ case GS_Task_t.PS_DISTILL:
+ m_GtkProgressBar.Fraction = 1.0;
+ RemoveProgressBar();
+ break;
+
+ case GS_Task_t.SAVE_RESULT:
+ break;
+
+ case GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_PDF:
+ ThumbsDone();
+ break;
+
+ case GS_Task_t.DISPLAY_DEV_PDF:
+ case GS_Task_t.DISPLAY_DEV_NON_PDF:
+ RenderingDone();
+ break;
+ }
+
+ if (info.Params.result == GS_Result_t.gsFAILED)
+ {
+ switch (info.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;
+
+ default:
+ ShowMessage(NotifyType_t.MESS_STATUS, "Ghostscript failed.");
+ break;
+ }
+ return;
+ }
+ GSResult(info.Params);
+ }
+ else
+ {
+ switch (info.Params.task)
+ {
+ case GS_Task_t.CREATE_XPS:
+ case GS_Task_t.PS_DISTILL:
+ m_GtkProgressBar.Fraction = (double)(info.Progress) / 100.0;
+ break;
+
+ case GS_Task_t.SAVE_RESULT:
+ break;
+ }
+ }
+ }
+
+ /* GS Result*/
+ public void GSResult(gsParamState_t gs_result)
+ {
+ TempFile tempfile = null;
+
+ if (gs_result.outputfile != null)
+ tempfile = new TempFile(gs_result.outputfile);
+
+ if (gs_result.result == GS_Result_t.gsCANCELLED)
+ {
+ if (tempfile != null)
+ {
+ try
+ {
+ tempfile.DeleteFile();
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ }
+ return;
+ }
+ if (gs_result.result == GS_Result_t.gsFAILED)
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "GS Failed Conversion");
+ if (tempfile != null)
+ {
+ try
+ {
+ tempfile.DeleteFile();
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ }
+ return;
+ }
+
+ switch (gs_result.task)
+ {
+ case GS_Task_t.PS_DISTILL:
+ m_origfile = gs_result.inputfile;
+
+ Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog("Save Document",
+ (Gtk.Window)this.Toplevel,
+ Gtk.FileChooserAction.Save);
+ dialog.LocalOnly = true;
+ dialog.AddButton(Gtk.Stock.Cancel, Gtk.ResponseType.Cancel);
+ dialog.AddButton(Gtk.Stock.Save, Gtk.ResponseType.Yes);
+ dialog.SetFilename(System.IO.Path.GetFileNameWithoutExtension(m_origfile) + ".pdf");
+
+ Gtk.FileFilter filter = new Gtk.FileFilter();
+ filter.Name = "doc/pdf";
+ filter.AddMimeType("application/pdf");
+ filter.AddPattern("*.pdf");
+ dialog.Filter = filter;
+
+ int response = dialog.Run();
+ if (response == (int)Gtk.ResponseType.Yes)
+ {
+ m_currfile = dialog.Filename;
+ try
+ {
+ if (System.IO.File.Exists(m_currfile))
+ {
+ System.IO.File.Delete(m_currfile);
+ }
+
+ var res = System.IO.File.Exists(tempfile.Filename);
+ System.IO.File.Copy(tempfile.Filename, dialog.Filename);
+ }
+ catch (Exception except)
+ {
+ ShowMessage(NotifyType_t.MESS_ERROR, "Exception Saving Distilled Result:" + except.Message);
+ }
+ }
+
+ dialog.Destroy();
+ tempfile.DeleteFile();
+ break;
+ }
+ }
+
+ protected void OnDeleteEvent(object sender, DeleteEventArgs a)
+ {
+ Application.Quit();
+ a.RetVal = true;
+ }
+
+ protected void OnOpen(object sender, EventArgs e)
+ {
+ Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog("Open Document",
+ (Gtk.Window)this.Toplevel,
+ Gtk.FileChooserAction.Open);
+
+ dialog.AddButton(Gtk.Stock.Cancel, Gtk.ResponseType.Cancel);
+ dialog.AddButton(Gtk.Stock.Open, Gtk.ResponseType.Accept);
+
+ dialog.DefaultResponse = Gtk.ResponseType.Cancel;
+ dialog.LocalOnly = true;
+
+ Gtk.FileFilter filter = new Gtk.FileFilter();
+ filter.Name = "doc/pdf";
+ filter.AddMimeType("application/pdf");
+ filter.AddMimeType("application/ps");
+ filter.AddMimeType("application/eps");
+ filter.AddMimeType("application/pcl");
+ filter.AddMimeType("application/xps");
+ filter.AddMimeType("application/oxps");
+ filter.AddPattern("*.pdf");
+ filter.AddPattern("*.ps");
+ filter.AddPattern("*.eps");
+ filter.AddPattern("*.bin");
+ filter.AddPattern("*.xps");
+ filter.AddPattern("*.oxps");
+ dialog.Filter = filter;
+ int response = dialog.Run();
+
+ if (response != (int)Gtk.ResponseType.Accept)
+ {
+ dialog.Destroy();
+ return;
+ }
+
+ if (m_file_open)
+ {
+ /* launch a new process */
+ string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
+
+ Process p = new Process();
+ try
+ {
+ String name = dialog.Filename;
+ Process.Start(path, name);
+ }
+ catch (Exception except)
+ {
+ Console.WriteLine("Problem opening file: " + except.Message);
+ }
+ dialog.Destroy();
+ return;
+ }
+
+ m_currfile = dialog.Filename;
+ dialog.Destroy();
+ ProcessFile();
+ }
+
+ public void ProcessFile()
+ {
+ m_extension = m_currfile.Split('.')[1];
+ int result;
+ if (!(m_extension.ToUpper() == "PDF" || m_extension.ToUpper() == "pdf"))
+ {
+ MessageDialog md = new MessageDialog(this,
+ DialogFlags.DestroyWithParent, MessageType.Question,
+ ButtonsType.YesNo, "Would you like to Distill this file ?");
+
+ result = md.Run();
+ md.Destroy();
+ if ((ResponseType)result == ResponseType.Yes)
+ {
+ AddProgressBar("Distilling");
+ if (m_ghostscript.DistillPS(m_currfile, Constants.DEFAULT_GS_RES) == gsStatus.GS_BUSY)
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "GS currently busy");
+ return;
+ }
+ return;
+ }
+ }
+ m_file_open = true;
+ RenderThumbs();
+ }
+
+ private void CleanUp()
+ {
+ /* Clear out everything */
+ if (m_docPages != null && m_docPages.Count > 0)
+ m_docPages.Clear();
+ if (m_thumbnails != null && m_thumbnails.Count > 0)
+ m_thumbnails.Clear();
+ if (m_page_sizes != null && m_page_sizes.Count > 0)
+ m_page_sizes.Clear();
+ if (m_page_scroll_pos != null && m_page_scroll_pos.Count > 0)
+ m_page_scroll_pos.Clear();
+
+ m_GtkimageStoreThumb.Clear();
+ m_GtkimageStoreMain.Clear();
+
+ m_currpage = 0;
+ m_thumbnails = new List<DocPage>();
+ m_docPages = new Pages();
+ m_page_sizes = new List<pagesizes_t>();
+ m_page_scroll_pos = new List<int>();
+
+ m_file_open = false;
+ m_doczoom = 1.0;
+ m_init_done = false;
+ m_busy_render = true;
+ m_firstime = true;
+ m_aa = true;
+ m_aa_change = false;
+ m_zoom_txt = "100";
+ m_page_txt = "0";
+ m_GtkpageTotal.Text = "/ 0";
+ m_ignore_scroll_change = false;
+ m_currfile = null;
+ m_origfile = null;
+ m_numpages = -1;
+ m_file_open = false;
+ CleanUpTempFiles();
+ m_tempfiles = new List<TempFile>();
+ return;
+ }
+
+ private void CleanUpTempFiles()
+ {
+ for (int k = 0; k < m_tempfiles.Count; k++)
+ {
+ try
+ {
+ m_tempfiles[k].DeleteFile();
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ }
+ m_tempfiles.Clear();
+ }
+ }
+}
diff --git a/demos/csharp/linux/gtk_viewer/src/MainZoom.cs b/demos/csharp/linux/gtk_viewer/src/MainZoom.cs
new file mode 100644
index 00000000..5d415351
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/MainZoom.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace gs_mono_example
+{
+ public partial class MainWindow
+ {
+ static double[] ZoomSteps = new double[]
+ {0.25, 0.333, 0.482, 0.667, 0.75, 1.0, 1.25, 1.37,
+ 1.50, 2.00, 3.00, 4.00};
+
+ public double GetNextZoom(double curr_zoom, int direction)
+ {
+ int k = 0;
+
+ /* Find segement we are in */
+ for (k = 0; k < ZoomSteps.Length - 1; k++)
+ {
+ if (curr_zoom >= ZoomSteps[k] && curr_zoom <= ZoomSteps[k + 1])
+ break;
+ }
+
+ /* Handling increasing zoom case. Look at upper boundary */
+ if (curr_zoom < ZoomSteps[k + 1] && direction > 0)
+ return ZoomSteps[k + 1];
+
+ if (Math.Abs(curr_zoom - ZoomSteps[k + 1]) <= Single.Epsilon && direction > 0)
+ {
+ if (k + 1 < ZoomSteps.Length - 1)
+ return ZoomSteps[k + 2];
+ else
+ return ZoomSteps[k + 1];
+ }
+
+ /* Handling decreasing zoom case. Look at lower boundary */
+ if (curr_zoom > ZoomSteps[k] && direction < 0)
+ return ZoomSteps[k];
+
+ if (Math.Abs(curr_zoom - ZoomSteps[k]) <= Single.Epsilon && direction < 0)
+ {
+ if (k > 0)
+ return ZoomSteps[k - 1];
+ else
+ return ZoomSteps[k];
+ }
+ return curr_zoom;
+ }
+
+ private bool ZoomDisabled()
+ {
+ if (!m_init_done || m_busy_render)
+ return true;
+ else
+ return false;
+ }
+
+ private void ZoomOut(object sender, EventArgs e)
+ {
+ if (ZoomDisabled())
+ return;
+ if (!m_init_done || m_doczoom <= Constants.ZOOM_MIN)
+ return;
+
+ m_busy_render = true;
+ m_doczoom = GetNextZoom(m_doczoom, -1);
+ m_GtkzoomEntry.Text = Math.Round(m_doczoom * 100.0).ToString();
+ m_zoom_txt = m_GtkzoomEntry.Text;
+ ResizePages();
+ RenderMainAll();
+ }
+
+ private void ZoomIn(object sender, EventArgs e)
+ {
+ if (ZoomDisabled())
+ return;
+ if (!m_init_done || m_doczoom >= Constants.ZOOM_MAX)
+ return;
+
+ m_busy_render = true;
+ m_doczoom = GetNextZoom(m_doczoom, 1);
+ m_GtkzoomEntry.Text = Math.Round(m_doczoom * 100.0).ToString();
+ m_zoom_txt = m_GtkzoomEntry.Text;
+ ResizePages();
+ RenderMainAll();
+ }
+
+ void ZoomChanged(object sender, EventArgs e)
+ {
+ Regex regex = new Regex("[^0-9.]+");
+
+ var text_entered = m_GtkzoomEntry.Text;
+ if (text_entered == "")
+ {
+ return;
+ }
+
+ if (!m_init_done)
+ {
+ m_GtkzoomEntry.Text = "100";
+ return;
+ }
+
+ bool ok = !regex.IsMatch(text_entered);
+ if (!ok)
+ {
+ m_GtkzoomEntry.Text = m_zoom_txt;
+ return;
+ }
+
+ double zoom = (double)System.Convert.ToDouble(text_entered);
+ zoom = zoom / 100.0;
+
+ if (zoom > Constants.ZOOM_MAX)
+ {
+ zoom = Constants.ZOOM_MAX;
+ m_GtkzoomEntry.Text = (zoom * 100).ToString();
+ }
+ if (zoom < Constants.ZOOM_MIN)
+ {
+ zoom = Constants.ZOOM_MIN;
+ m_GtkzoomEntry.Text = (zoom * 100).ToString();
+ }
+
+ m_doczoom = zoom;
+ m_busy_render = true;
+ m_zoom_txt = m_GtkzoomEntry.Text;
+ ResizePages();
+ RenderMainAll();
+ }
+
+ void PageChanged(object sender, EventArgs e)
+ {
+ Regex regex = new Regex("[^0-9.]+");
+
+ var text_entered = m_GtkpageEntry.Text;
+ if (text_entered == "")
+ {
+ return;
+ }
+
+ if (!m_init_done)
+ {
+ m_GtkpageEntry.Text = "";
+ return;
+ }
+
+ bool ok = !regex.IsMatch(text_entered);
+ if (!ok)
+ {
+ m_GtkpageEntry.Text = m_page_txt;
+ return;
+ }
+
+ int page = (Int32)System.Convert.ToInt32(text_entered);
+ if (page > m_numpages)
+ {
+ page = m_numpages;
+ }
+ if (page < 1)
+ {
+ page = 1;
+ }
+
+ m_GtkpageEntry.Text = page.ToString();
+ m_page_txt = m_GtkpageEntry.Text;
+ ScrollMainTo(page - 1);
+ }
+
+ private void ResizePages()
+ {
+ Gtk.TreeIter tree_iter;
+ m_GtkimageStoreMain.GetIterFirst(out tree_iter);
+
+ if (m_page_sizes.Count < 1)
+ return;
+
+ for (int k = 0; k < m_numpages; k++)
+ {
+ var doc_page = m_docPages[k];
+ if (Math.Abs(doc_page.Zoom - m_doczoom) <= Single.Epsilon)
+ continue;
+ else
+ {
+ /* Resize it now */
+ doc_page.PixBuf =
+ doc_page.PixBuf.ScaleSimple((int)(m_doczoom * m_page_sizes[k].width),
+ (int)(m_doczoom * m_page_sizes[k].height), Gdk.InterpType.Nearest);
+ doc_page.Width = (int)(m_doczoom * m_page_sizes[k].width);
+ doc_page.Height = (int)(m_doczoom * m_page_sizes[k].height);
+ doc_page.Zoom = m_doczoom;
+ doc_page.Content= Page_Content_t.OLD_RESOLUTION;
+ m_GtkimageStoreMain.SetValue(tree_iter, 0, doc_page.PixBuf);
+ m_GtkimageStoreMain.IterNext(ref tree_iter);
+
+ if (k == 0)
+ m_page_scroll_pos[0] = doc_page.Height;
+ else
+ m_page_scroll_pos[k] = doc_page.Height + m_page_scroll_pos[k - 1];
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/demos/csharp/linux/gtk_viewer/src/Program.cs b/demos/csharp/linux/gtk_viewer/src/Program.cs
new file mode 100644
index 00000000..7446f0bc
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/Program.cs
@@ -0,0 +1,25 @@
+using System;
+using Gtk;
+
+namespace gs_mono_example
+{
+
+ class MainClass
+ {
+ public static void Main(string[] args)
+ {
+ Application.Init();
+ MainWindow win = new MainWindow();
+ win.ShowAll();
+
+ try
+ {
+ Application.Run();
+ }
+ catch(Exception except)
+ {
+ var mess = except.Message;
+ }
+ }
+ }
+}
diff --git a/demos/csharp/linux/gtk_viewer/src/TempFile.cs b/demos/csharp/linux/gtk_viewer/src/TempFile.cs
new file mode 100644
index 00000000..c6c313de
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/TempFile.cs
@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+
+/* A class to help in the management of temp files */
+namespace gs_mono_example
+{
+ class TempFile
+ {
+ private String m_filename;
+
+ public TempFile(String Name)
+ {
+ m_filename = Name;
+ }
+
+ public String Filename
+ {
+ get { return m_filename; }
+ }
+
+ public void DeleteFile()
+ {
+ try
+ {
+ if (File.Exists(m_filename))
+ File.Delete(m_filename);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+ }
+}
diff --git a/demos/csharp/linux/gtk_viewer/src/gsOutput.cs b/demos/csharp/linux/gtk_viewer/src/gsOutput.cs
new file mode 100644
index 00000000..c74dc7a3
--- /dev/null
+++ b/demos/csharp/linux/gtk_viewer/src/gsOutput.cs
@@ -0,0 +1,41 @@
+using System;
+using Gtk;
+
+namespace gs_mono_example
+{
+ public class gsOutput : Gtk.Window
+ {
+ public Gtk.TextView m_textView;
+ public Gtk.ScrolledWindow m_scrolledWindow;
+
+ public gsOutput() : base(Gtk.WindowType.Toplevel)
+ {
+ this.SetDefaultSize(500, 500);
+ this.Title = "GhostPDL Standard Output";
+
+ m_scrolledWindow = new ScrolledWindow();
+ m_scrolledWindow.BorderWidth = 5;
+ m_scrolledWindow.ShadowType = ShadowType.In;
+
+ DeleteEvent += Handle_DeleteEvent;
+
+ m_textView = new Gtk.TextView();
+ m_textView.Editable = false;
+
+ /* To force resize and scroll to bottom when text view is update */
+ m_scrolledWindow.Add(m_textView);
+
+ Add(m_scrolledWindow);
+ ShowAll();
+ Hide();
+ }
+
+ /* Don't actually destroy this window */
+ void Handle_DeleteEvent(object o, DeleteEventArgs args)
+ {
+ this.Hide();
+ args.RetVal = true;
+ }
+
+ }
+}
diff --git a/demos/csharp/windows/ghostnet.sln b/demos/csharp/windows/ghostnet.sln
new file mode 100644
index 00000000..e073dd1e
--- /dev/null
+++ b/demos/csharp/windows/ghostnet.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30104.148
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ghostnet_simple_viewer", "ghostnet_wpf_example\ghostnet_simple_viewer.csproj", "{8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Debug|x64.ActiveCfg = Debug|x64
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Debug|x64.Build.0 = Debug|x64
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Debug|x86.ActiveCfg = Debug|x86
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Debug|x86.Build.0 = Debug|x86
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Release|x64.ActiveCfg = Release|x64
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Release|x64.Build.0 = Release|x64
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Release|x86.ActiveCfg = Release|x86
+ {8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {80DE35D1-60F8-4E3C-A76B-3BDEFEA3E288}
+ EndGlobalSection
+EndGlobal
diff --git a/demos/csharp/windows/ghostnet_wpf_example/About.xaml b/demos/csharp/windows/ghostnet_wpf_example/About.xaml
new file mode 100644
index 00000000..0221ebc6
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/About.xaml
@@ -0,0 +1,94 @@
+<Window
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="ghostnet_wpf_example.About"
+ Title="ghostnet_wpf_example"
+ x:Uid="idAboutBox"
+ Style="{DynamicResource AboutDialogStyle}" WindowStartupLocation="CenterOwner" Height="353" Width="501">
+ <Window.Resources>
+ <XmlDataProvider x:Key="aboutProvider" XPath="ApplicationInfo" IsAsynchronous="False" IsInitialLoadEnabled="True">
+ <x:XData>
+ <ApplicationInfo xmlns="">
+ <Link Uri="http://www.artifex.com">More Info</Link>
+ </ApplicationInfo>
+ </x:XData>
+ </XmlDataProvider>
+ <Style x:Key="AboutDialogStyle" TargetType="{x:Type Window}">
+ <Setter Property="Height" Value="Auto" />
+ <Setter Property="Width" Value="500" />
+ <Setter Property="ShowInTaskbar" Value="False" />
+ <Setter Property="ResizeMode" Value="NoResize" />
+ <Setter Property="WindowStyle" Value="SingleBorderWindow" />
+ <Setter Property="SizeToContent" Value="Height" />
+ </Style>
+ <Style x:Key="DisplayAreaStyle" TargetType="{x:Type StackPanel}">
+ <Setter Property="Margin" Value="10,10,10,5" />
+ </Style>
+ <Style x:Key="BackgroundStyle" TargetType="{x:Type StackPanel}">
+ <Setter Property="Background">
+ <Setter.Value>
+ <LinearGradientBrush EndPoint="0,1">
+ <GradientStop Offset="0" Color="#FF317896" />
+ <GradientStop Offset="0.27" Color="White" />
+ <GradientStop Offset="0.85" Color="#FF317896" />
+ <GradientStop Offset="1" Color="#FF317896" />
+ </LinearGradientBrush>
+ </Setter.Value>
+ </Setter>
+ </Style>
+ <Style TargetType="{x:Type Label}">
+ <Setter Property="Padding" Value="0" />
+ </Style>
+ <Style x:Key="ParagraphSeparator" TargetType="{x:Type Label}">
+ <Setter Property="Padding" Value="0,10,0,0" />
+ </Style>
+ <Style x:Key="LinkLabelStyle">
+ <Setter Property="Control.Padding" Value="0" />
+ <Setter Property="FrameworkElement.VerticalAlignment" Value="Center" />
+ </Style>
+ <Style x:Key="ReadOnlyDescStyle" TargetType="{x:Type TextBox}">
+ <Setter Property="MinLines" Value="6" />
+ <Setter Property="MaxLines" Value="6" />
+ <Setter Property="IsReadOnly" Value="True" />
+ <Setter Property="TextWrapping" Value="WrapWithOverflow" />
+ <Setter Property="VerticalScrollBarVisibility" Value="Visible" />
+ </Style>
+ <Style x:Key="OkButtonStyle" TargetType="{x:Type Button}">
+ <Setter Property="MinWidth" Value="75" />
+ <Setter Property="Margin" Value="0,5" />
+ <Setter Property="DockPanel.Dock" Value="Right" />
+ <Setter Property="IsDefault" Value="True" />
+ <Setter Property="IsCancel" Value="True" />
+ </Style>
+ </Window.Resources>
+ <StackPanel x:Uid="clientArea" Style="{StaticResource BackgroundStyle}">
+ <StackPanel x:Uid="displayArea" Style="{StaticResource DisplayAreaStyle}"
+ DataContext="{Binding Mode=OneTime, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
+ <Label x:Name="productName" x:Uid="productName"
+ Content="{Binding Product, Mode=OneTime}" />
+ <StackPanel x:Uid="versionArea" Orientation="Horizontal">
+ <Label x:Name="versionLabel" x:Uid="VersionLabel" Content="Version - " />
+ <Label x:Name="version" x:Uid="version" Content="{Binding Version, Mode=OneTime}" />
+ </StackPanel>
+ <Label x:Name="copyright" x:Uid="copyright" Content="{Binding Copyright, Mode=OneTime}" />
+ <Label x:Name="company" x:Uid="company" Content="{Binding Company, Mode=OneTime}" />
+ <Label x:Name="reserved" x:Uid="reserved" Content="All Rights Reserved." />
+ <Label x:Name="info" x:Uid="info" Style="{StaticResource ParagraphSeparator}"
+ Content="Product details:" />
+ <TextBox x:Name="description" x:Uid="description" Text=""
+ Style="{StaticResource ReadOnlyDescStyle}" Height="140" />
+ <DockPanel x:Uid="buttonArea">
+ <Button x:Name="okButton" x:Uid="okButton" Style="{StaticResource OkButtonStyle}"
+ Content="OK" />
+ <Label x:Name="productLink" x:Uid="productLink" Style="{StaticResource LinkLabelStyle}" >
+ <Hyperlink x:Name="hyperlink" x:Uid="hyperlink" NavigateUri="{Binding LinkUri, Mode=OneTime}" Style="{StaticResource LinkLabelStyle}"
+ RequestNavigate="hyperlink_RequestNavigate">
+ <InlineUIContainer>
+ <TextBlock Text="{Binding LinkText, Mode=OneTime}" />
+ </InlineUIContainer>
+ </Hyperlink>
+ </Label>
+ </DockPanel>
+ </StackPanel>
+ </StackPanel>
+</Window> \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/About.xaml.cs b/demos/csharp/windows/ghostnet_wpf_example/About.xaml.cs
new file mode 100644
index 00000000..3b4a7285
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/About.xaml.cs
@@ -0,0 +1,254 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Data;
+using System.Xml;
+using System.ComponentModel;
+
+namespace ghostnet_wpf_example
+{
+ /// <summary>
+ /// Interaction logic for About.xaml
+ /// </summary>
+ public partial class About : Window
+ {
+ /// <summary>
+ /// Default constructor is protected so callers must use one with a parent.
+ /// </summary>
+ protected About()
+ {
+ InitializeComponent();
+ }
+
+
+ /// <summary>
+ /// Constructor that takes a parent for this About dialog.
+ /// </summary>
+ /// <param name="parent">Parent window for this dialog.</param>
+ public About(Window parent)
+ : this()
+ {
+ this.Owner = parent;
+ }
+
+ /// <summary>
+ /// Handles click navigation on the hyperlink in the About dialog.
+ /// </summary>
+ /// <param name="sender">Object the sent the event.</param>
+ /// <param name="e">Navigation events arguments.</param>
+ private void hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ {
+ if (e.Uri != null && string.IsNullOrEmpty(e.Uri.OriginalString) == false)
+ {
+ string uri = e.Uri.AbsoluteUri;
+ Process.Start(new ProcessStartInfo(uri));
+ e.Handled = true;
+ }
+ }
+
+ #region AboutData Provider
+ #region Member data
+ private XmlDocument xmlDoc = null;
+ private const string propertyNameTitle = "Title";
+ private const string propertyNameDescription = "Description";
+ private const string propertyNameProduct = "Product";
+ private const string propertyNameCopyright = "Copyright";
+ private const string propertyNameCompany = "Company";
+ private const string xPathRoot = "ApplicationInfo/";
+ private const string xPathTitle = xPathRoot + propertyNameTitle;
+ private const string xPathVersion = xPathRoot + "Version";
+ private const string xPathDescription = xPathRoot + propertyNameDescription;
+ private const string xPathProduct = xPathRoot + propertyNameProduct;
+ private const string xPathCopyright = xPathRoot + propertyNameCopyright;
+ private const string xPathCompany = xPathRoot + propertyNameCompany;
+ private const string xPathLink = xPathRoot + "Link";
+ private const string xPathLinkUri = xPathRoot + "Link/@Uri";
+ #endregion
+
+ #region Properties
+ /// <summary>
+ /// Gets the title property, which is display in the About dialogs window title.
+ /// </summary>
+ public string ProductTitle
+ {
+ get
+ {
+ string result = CalculatePropertyValue<AssemblyTitleAttribute>(propertyNameTitle, xPathTitle);
+ if (string.IsNullOrEmpty(result))
+ {
+ // otherwise, just get the name of the assembly itself.
+ result = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase);
+ }
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Gets the application's version information to show.
+ /// </summary>
+ public string Version
+ {
+ get
+ {
+ string result = string.Empty;
+ // first, try to get the version string from the assembly.
+ Version version = Assembly.GetExecutingAssembly().GetName().Version;
+ if (version != null)
+ {
+ result = version.ToString();
+ }
+ else
+ {
+ // if that fails, try to get the version from a resource in the Application.
+ result = GetLogicalResourceString(xPathVersion);
+ }
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Gets the description about the application.
+ /// </summary>
+ public string Description
+ {
+ get { return CalculatePropertyValue<AssemblyDescriptionAttribute>(propertyNameDescription, xPathDescription);}
+ }
+
+ public string VariableDescription
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets the product's full name.
+ /// </summary>
+ public string Product
+ {
+ get { return CalculatePropertyValue<AssemblyProductAttribute>(propertyNameProduct, xPathProduct); }
+ }
+
+ /// <summary>
+ /// Gets the copyright information for the product.
+ /// </summary>
+ public string Copyright
+ {
+ get { return CalculatePropertyValue<AssemblyCopyrightAttribute>(propertyNameCopyright, xPathCopyright); }
+ }
+
+ /// <summary>
+ /// Gets the product's company name.
+ /// </summary>
+ public string Company
+ {
+ get { return CalculatePropertyValue<AssemblyCompanyAttribute>(propertyNameCompany, xPathCompany); }
+ }
+
+ /// <summary>
+ /// Gets the link text to display in the About dialog.
+ /// </summary>
+ public string LinkText
+ {
+ get { return GetLogicalResourceString(xPathLink); }
+ }
+
+ /// <summary>
+ /// Gets the link uri that is the navigation target of the link.
+ /// </summary>
+ public string LinkUri
+ {
+ get { return GetLogicalResourceString(xPathLinkUri); }
+ }
+ #endregion
+
+ #region Resource location methods
+ /// <summary>
+ /// Gets the specified property value either from a specific attribute, or from a resource dictionary.
+ /// </summary>
+ /// <typeparam name="T">Attribute type that we're trying to retrieve.</typeparam>
+ /// <param name="propertyName">Property name to use on the attribute.</param>
+ /// <param name="xpathQuery">XPath to the element in the XML data resource.</param>
+ /// <returns>The resulting string to use for a property.
+ /// Returns null if no data could be retrieved.</returns>
+ private string CalculatePropertyValue<T>(string propertyName, string xpathQuery)
+ {
+ string result = string.Empty;
+ // first, try to get the property value from an attribute.
+ object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(T), false);
+ if (attributes.Length > 0)
+ {
+ T attrib = (T)attributes[0];
+ PropertyInfo property = attrib.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
+ if (property != null)
+ {
+ result = property.GetValue(attributes[0], null) as string;
+ }
+ }
+
+ // if the attribute wasn't found or it did not have a value, then look in an xml resource.
+ if (result == string.Empty)
+ {
+ // if that fails, try to get it from a resource.
+ result = GetLogicalResourceString(xpathQuery);
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Gets the XmlDataProvider's document from the resource dictionary.
+ /// </summary>
+ protected virtual XmlDocument ResourceXmlDocument
+ {
+ get
+ {
+ if (xmlDoc == null)
+ {
+ // if we haven't already found the resource XmlDocument, then try to find it.
+ XmlDataProvider provider = this.TryFindResource("aboutProvider") as XmlDataProvider;
+ if (provider != null)
+ {
+ // save away the XmlDocument, so we don't have to get it multiple times.
+ xmlDoc = provider.Document;
+ }
+ }
+ return xmlDoc;
+ }
+ }
+
+ /// <summary>
+ /// Gets the specified data element from the XmlDataProvider in the resource dictionary.
+ /// </summary>
+ /// <param name="xpathQuery">An XPath query to the XML element to retrieve.</param>
+ /// <returns>The resulting string value for the specified XML element.
+ /// Returns empty string if resource element couldn't be found.</returns>
+ protected virtual string GetLogicalResourceString(string xpathQuery)
+ {
+ string result = string.Empty;
+ // get the About xml information from the resources.
+ XmlDocument doc = this.ResourceXmlDocument;
+ if (doc != null)
+ {
+ // if we found the XmlDocument, then look for the specified data.
+ XmlNode node = doc.SelectSingleNode(xpathQuery);
+ if (node != null)
+ {
+ if (node is XmlAttribute)
+ {
+ // only an XmlAttribute has a Value set.
+ result = node.Value;
+ }
+ else
+ {
+ // otherwise, need to just return the inner text.
+ result = node.InnerText;
+ }
+ }
+ }
+ return result;
+ }
+ #endregion
+ #endregion
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/App.config b/demos/csharp/windows/ghostnet_wpf_example/App.config
new file mode 100644
index 00000000..56efbc7b
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/App.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+ </startup>
+</configuration> \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/App.xaml b/demos/csharp/windows/ghostnet_wpf_example/App.xaml
new file mode 100644
index 00000000..8f382dcc
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/App.xaml
@@ -0,0 +1,9 @@
+<Application x:Class="ghostnet_wpf_example.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:ghostnet_wpf_example"
+ StartupUri="MainWindow.xaml">
+ <Application.Resources>
+
+ </Application.Resources>
+</Application>
diff --git a/demos/csharp/windows/ghostnet_wpf_example/App.xaml.cs b/demos/csharp/windows/ghostnet_wpf_example/App.xaml.cs
new file mode 100644
index 00000000..79444fe4
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace ghostnet_wpf_example
+{
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ public partial class App : Application
+ {
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/DocPage.cs b/demos/csharp/windows/ghostnet_wpf_example/DocPage.cs
new file mode 100644
index 00000000..c28bd62d
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/DocPage.cs
@@ -0,0 +1,109 @@
+using System;
+using System.ComponentModel;
+using System.Windows.Media.Imaging;
+using System.Collections.ObjectModel;
+
+namespace ghostnet_wpf_example
+{
+ public class DocPage : INotifyPropertyChanged
+ {
+ private int height;
+ private int width;
+ private double zoom;
+ private BitmapSource bitmap;
+ private String pagename;
+ private int pagenum;
+ private Page_Content_t content;
+
+ public int Height
+ {
+ get { return height; }
+ set
+ {
+ height = value;
+ OnPropertyChanged("Height");
+ }
+ }
+
+ public int Width
+ {
+ get { return width; }
+ set
+ {
+ width = value;
+ OnPropertyChanged("Width");
+ }
+ }
+
+ public double Zoom
+ {
+ get { return zoom; }
+ set { zoom = value; }
+ }
+
+ public BitmapSource BitMap
+ {
+ get { return bitmap; }
+ set
+ {
+ bitmap = value;
+ OnPropertyChanged("BitMap");
+ }
+ }
+
+ public String PageName
+ {
+ get { return pagename; }
+ set { pagename = value; }
+ }
+
+ public int PageNum
+ {
+ get { return pagenum; }
+ set { pagenum = value; }
+ }
+ public Page_Content_t Content
+ {
+ get { return content; }
+ set { content = value; }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ // Create the OnPropertyChanged method to raise the event
+ protected void OnPropertyChanged(string name)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ {
+ handler(this, new PropertyChangedEventArgs(name));
+ }
+ }
+
+ public DocPage()
+ {
+ this.height = 0;
+ this.width = 0;
+ this.zoom = 0;
+ this.bitmap = null;
+ this.pagenum = -1;
+ this.pagename = "";
+ }
+
+ public DocPage(int Height, int Width, double Zoom, BitmapSource BitMap, int PageNum)
+ {
+ this.height = Height;
+ this.width = Width;
+ this.zoom = Zoom;
+ this.bitmap = BitMap;
+ this.pagename = ("Page " + (pagenum + 1));
+ }
+ };
+ public class Pages : ObservableCollection<DocPage>
+ {
+ public Pages()
+ : base()
+ {
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/MainPrint.cs b/demos/csharp/windows/ghostnet_wpf_example/MainPrint.cs
new file mode 100644
index 00000000..e1c7a1e3
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/MainPrint.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Windows;
+using System.Collections.Generic;
+using System.Threading;
+using System.Windows.Xps.Packaging;
+using System.Printing;
+using System.Windows.Input;
+using System.IO;
+using GhostNET;
+
+namespace ghostnet_wpf_example
+{
+ public partial class MainWindow
+ {
+ Print m_printcontrol = null;
+ private xpsprint m_xpsprint = null;
+
+ public void PrintDiagPrint(object PrintDiag)
+ {
+ bool print_all = false;
+ int first_page = 1;
+ int last_page = 1;
+
+ if (!m_printcontrol.RangeOK())
+ return;
+
+ /* Create only the page range that is needed. */
+ switch (m_printcontrol.m_pages_setting)
+ {
+ case PrintPages_t.RANGE:
+ first_page = m_printcontrol.m_from;
+ last_page = m_printcontrol.m_to;
+ break;
+ case PrintPages_t.CURRENT:
+ first_page = m_currpage + 1;
+ last_page = m_currpage + 1;
+ break;
+ case PrintPages_t.ALL:
+ print_all = true;
+ first_page = 1;
+ last_page = m_numpages + 1;
+ break;
+ }
+
+ /* If file is already xps then gs need not do this */
+ if (!(m_document_type == doc_t.XPS))
+ {
+ xaml_DistillProgress.Value = 0;
+
+ if (m_ghostscript.CreateXPS(m_currfile, Constants.DEFAULT_GS_RES,
+ last_page - first_page + 1, m_printcontrol, first_page, last_page) == 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, print_all, first_page, last_page, false);
+ }
+
+ 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, ExecutedRoutedEventArgs e)
+ {
+ if (!m_file_open)
+ return;
+
+ if (m_printcontrol == null)
+ {
+ m_printcontrol = new Print(this, m_numpages);
+ m_printcontrol.Activate();
+ m_printcontrol.Show(); /* Makes it modal */
+ }
+ else
+ m_printcontrol.Show();
+ return;
+ }
+
+ /* Do the actual printing on a different thread. This is an STA
+ thread due to the UI like commands that the XPS document
+ processing performs. */
+ private void PrintXPS(String file, bool print_all, int from, int to,
+ bool istempfile)
+ {
+ Thread PrintThread = new Thread(MainWindow.PrintWork);
+ PrintThread.SetApartmentState(ApartmentState.STA);
+
+ var arguments = new List<object>();
+ arguments.Add(file);
+ arguments.Add(print_all);
+ arguments.Add(from);
+ arguments.Add(to);
+ arguments.Add(m_printcontrol.m_selectedPrinter.FullName);
+ arguments.Add(m_printcontrol);
+ arguments.Add(istempfile);
+
+ m_xpsprint = new xpsprint();
+ m_xpsprint.PrintUpdate += PrintProgress;
+ arguments.Add(m_xpsprint);
+
+ xaml_PrintGrid.Visibility = System.Windows.Visibility.Visible;
+ xaml_PrintProgress.Value = 0;
+ PrintThread.Start(arguments);
+ }
+
+ /* The thread that is actually doing the print work */
+ public static void PrintWork(object data)
+ {
+ List<object> genericlist = data as List<object>;
+ String file = (String)genericlist[0];
+ bool print_all = (bool)genericlist[1];
+ int from = (int)genericlist[2];
+ int to = (int)genericlist[3];
+ String printer_name = (String)genericlist[4];
+ Print printcontrol = (Print)genericlist[5];
+ bool istempfile = (bool)genericlist[6];
+ xpsprint xps_print = (xpsprint)genericlist[7];
+ String filename = "";
+
+ if (istempfile == true)
+ filename = file;
+
+ /* We have to get our own copy of the print queue for the thread */
+ LocalPrintServer m_printServer = new LocalPrintServer();
+ PrintQueue m_selectedPrinter = m_printServer.GetPrintQueue(printer_name);
+
+ XpsDocument xpsDocument = new XpsDocument(file, FileAccess.Read);
+ xps_print.Print(m_selectedPrinter, xpsDocument, printcontrol, print_all,
+ from, to, filename, istempfile);
+
+ /* Once we are done, go ahead and delete the file */
+ if (istempfile == true)
+ xpsDocument.Close();
+ xps_print.Done();
+ }
+
+ private void PrintProgress(object printHelper, gsPrintEventArgs Information)
+ {
+ switch (Information.Status)
+ {
+ case PrintStatus_t.PRINT_ERROR:
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
+ {
+ ShowMessage(NotifyType_t.MESS_ERROR, "Printer Driver Error");
+ xaml_PrintGrid.Visibility = System.Windows.Visibility.Collapsed;
+ }));
+ break;
+ case PrintStatus_t.PRINT_CANCELLED:
+ case PrintStatus_t.PRINT_READY:
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
+ {
+ xaml_PrintGrid.Visibility = System.Windows.Visibility.Collapsed;
+ }));
+ break;
+ case PrintStatus_t.PRINT_DONE:
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
+ {
+ xaml_PrintGrid.Visibility = System.Windows.Visibility.Collapsed;
+ DeleteTempFile(Information.FileName);
+ }));
+ break;
+ case PrintStatus_t.PRINT_BUSY:
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
+ {
+ xaml_PrintProgress.Value = 100.0 * (double)(Information.Page - Information.PageStart) / (double)Information.NumPages;
+ if (Information.Page == Information.NumPages)
+ {
+ xaml_PrintGrid.Visibility = System.Windows.Visibility.Collapsed;
+ }
+ }));
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/MainRender.cs b/demos/csharp/windows/ghostnet_wpf_example/MainRender.cs
new file mode 100644
index 00000000..016c86bb
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/MainRender.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Windows.Media;
+using System.Collections.Generic;
+using System.Windows.Media.Imaging;
+using System.Runtime.InteropServices;
+using GhostNET;
+
+namespace ghostnet_wpf_example
+{
+ public partial class MainWindow
+ {
+ bool m_busy_rendering;
+ int m_firstpage;
+ int m_lastpage;
+
+ /* For PDF optimization */
+ private void PageRangeRender(int first_page, int last_page)
+ {
+ bool render_pages = false;
+ for (int k = first_page; k <= last_page; k++)
+ {
+ if (m_docPages[k].Content != Page_Content_t.FULL_RESOLUTION)
+ {
+ render_pages = true;
+ break;
+ }
+ }
+ if (!render_pages)
+ return;
+
+ m_busy_rendering = true;
+ m_firstpage = first_page;
+ m_lastpage = last_page;
+ //m_ghostscript.gsDisplayDeviceRender(m_currfile, first_page + 1, last_page + 1, 1.0);
+ }
+
+ /* Callback from ghostscript with the rendered image. */
+ private void MainPageCallback(int width, int height, int raster, double zoom_in,
+ int page_num, IntPtr data)
+ {
+ Byte[] bitmap = new byte[raster * height];
+ idata_t image_data = new idata_t();
+
+ Marshal.Copy(data, bitmap, 0, raster * height);
+
+ image_data.bitmap = bitmap;
+ image_data.page_num = page_num;
+ image_data.width = width;
+ image_data.height = height;
+ image_data.raster = raster;
+ image_data.zoom = zoom_in;
+ m_images_rendered.Add(image_data);
+
+ /* Get the 1.0 page scalings */
+ if (m_firstime)
+ {
+ pagesizes_t page_size = new pagesizes_t();
+ page_size.size.X = width;
+ page_size.size.Y = height;
+ m_page_sizes.Add(page_size);
+ }
+
+ /* Dispatch progress bar update on UI thread */
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new Action(() =>
+ {
+ m_page_progress_count += 1;
+ xaml_RenderProgress.Value = ((double)m_page_progress_count / (double) m_numpages) * 100.0;
+ }));
+ }
+
+ /* Done rendering. Update the pages with the new raster information if needed */
+ private void RenderingDone()
+ {
+ int page_index = m_firstpage - 1;
+ m_toppage_pos = new List<int>(m_images_rendered.Count + 1);
+ int offset = 0;
+
+ for (int k = 0; k < m_images_rendered.Count; k++)
+ {
+ DocPage doc_page = m_docPages[page_index + k];
+
+ if (doc_page.Content != Page_Content_t.FULL_RESOLUTION ||
+ m_aa_change)
+ {
+ doc_page.Width = m_images_rendered[k].width;
+ doc_page.Height = m_images_rendered[k].height;
+ doc_page.Content = Page_Content_t.FULL_RESOLUTION;
+
+ doc_page.Zoom = m_doczoom;
+ doc_page.BitMap = BitmapSource.Create(doc_page.Width, doc_page.Height,
+ 72, 72, PixelFormats.Bgr24, BitmapPalettes.Halftone256, m_images_rendered[k].bitmap, m_images_rendered[k].raster);
+ }
+ m_toppage_pos.Add(offset + Constants.PAGE_VERT_MARGIN);
+ offset += doc_page.Height + Constants.PAGE_VERT_MARGIN;
+ }
+
+ xaml_ProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
+ xaml_RenderProgress.Value = 0;
+ m_aa_change = false;
+ m_firstime = false;
+ m_toppage_pos.Add(offset);
+ m_busy_rendering = false;
+ m_images_rendered.Clear();
+ m_file_open = true;
+ m_busy_render = false;
+ m_ghostscript.gsPageRenderedMain -= new ghostsharp.gsCallBackPageRenderedMain(gsPageRendered);
+ }
+
+ /* Render all pages full resolution */
+ private void RenderMainFirst()
+ {
+ m_firstpage = 1;
+ m_busy_render = true;
+ xaml_RenderProgress.Value = 0;
+ xaml_ProgressGrid.Visibility = System.Windows.Visibility.Visible;
+ m_page_progress_count = 0;
+ xaml_RenderProgressText.Text = "Rendering";
+ if (m_firstime)
+ {
+ xaml_Zoomsize.Text = "100";
+ }
+
+ m_ghostscript.gsPageRenderedMain += new ghostsharp.gsCallBackPageRenderedMain(gsPageRendered);
+ m_ghostscript.gsDisplayDeviceRenderAll(m_currfile, m_doczoom, m_aa, GS_Task_t.DISPLAY_DEV_NON_PDF);
+ }
+
+ /* Render all, but only if not already busy, called via zoom or aa changes */
+ private void RenderMainAll()
+ {
+ if (m_busy_render || !m_init_done)
+ return;
+ RenderMainFirst();
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/MainThumbRendering.cs b/demos/csharp/windows/ghostnet_wpf_example/MainThumbRendering.cs
new file mode 100644
index 00000000..5db50389
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/MainThumbRendering.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Runtime.InteropServices;
+using GhostNET;
+
+namespace ghostnet_wpf_example
+{
+ public partial class MainWindow
+ {
+ private static List<DocPage> m_thumbnails;
+
+ /* Assign current pages to blown up thumbnail images */
+ private void ThumbAssignMain(int page_num, int width, int height, double zoom_in, ref int offset)
+ {
+ m_pageType.Add(Page_Content_t.THUMBNAIL);
+ DocPage doc_page = new DocPage();
+ doc_page.Content = Page_Content_t.THUMBNAIL;
+ doc_page.Zoom = zoom_in;
+ doc_page.BitMap = m_thumbnails[page_num - 1].BitMap;
+ doc_page.Width = (int)(width / (Constants.SCALE_THUMB));
+ doc_page.Height = (int)(height / (Constants.SCALE_THUMB));
+ doc_page.PageNum = page_num;
+ m_docPages.Add(doc_page);
+ m_toppage_pos.Add(offset + Constants.PAGE_VERT_MARGIN);
+ offset += doc_page.Height + Constants.PAGE_VERT_MARGIN;
+ }
+
+ /* Rendered all the thumbnail pages. Stick them in the appropriate lists */
+ private void ThumbsDone()
+ {
+ int offset = 0;
+ m_toppage_pos = new List<int>(m_list_thumb.Count);
+
+ for (int k = 0; k < m_list_thumb.Count; k++)
+ {
+ DocPage doc_page = new DocPage();
+ m_thumbnails.Add(doc_page);
+
+ doc_page.Width = m_list_thumb[k].width;
+ doc_page.Height = m_list_thumb[k].height;
+ doc_page.Content = Page_Content_t.THUMBNAIL;
+ doc_page.Zoom = m_list_thumb[k].zoom;
+ doc_page.BitMap = BitmapSource.Create(doc_page.Width, doc_page.Height,
+ 72, 72, PixelFormats.Bgr24, BitmapPalettes.Halftone256, m_list_thumb[k].bitmap, m_list_thumb[k].raster);
+ doc_page.PageNum = m_list_thumb[k].page_num;
+ ThumbAssignMain(m_list_thumb[k].page_num, m_list_thumb[k].width, m_list_thumb[k].height, 1.0, ref offset);
+ }
+
+ m_toppage_pos.Add(offset);
+ xaml_ProgressGrid.Visibility = System.Windows.Visibility.Collapsed;
+ xaml_RenderProgress.Value = 0;
+ xaml_PageList.ItemsSource = m_docPages;
+ xaml_ThumbList.ItemsSource = m_thumbnails;
+ xaml_ThumbList.Items.Refresh();
+ xaml_ThumbGrid.Visibility = System.Windows.Visibility.Visible;
+
+ m_ghostscript.gsPageRenderedMain -= new ghostsharp.gsCallBackPageRenderedMain(gsThumbRendered);
+
+
+ m_numpages = m_list_thumb.Count;
+ if (m_numpages < 1)
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "File failed to open properly");
+ CleanUp();
+ }
+ else
+ {
+ m_init_done = true;
+ xaml_TotalPages.Text = "/" + m_numpages;
+ xaml_currPage.Text = m_currpage.ToString();
+ m_list_thumb.Clear();
+
+ /* If non-pdf, kick off full page rendering */
+ RenderMainFirst();
+ }
+ }
+
+ /* Callback from ghostscript with the rendered thumbnail. Also update progress */
+ private void ThumbPageCallback(int width, int height, int raster, double zoom_in,
+ int page_num, IntPtr data)
+ {
+ Byte[] bitmap = new byte[raster * height];
+ idata_t thumb = new idata_t();
+
+ Marshal.Copy(data, bitmap, 0, raster * height);
+
+ thumb.bitmap = bitmap;
+ thumb.page_num = page_num;
+ thumb.width = width;
+ thumb.height = height;
+ thumb.raster = raster;
+ thumb.zoom = zoom_in;
+ m_list_thumb.Add(thumb);
+
+ /* Dispatch progress bar update on UI thread */
+ System.Windows.Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new Action(() =>
+ {
+ /* Logrithmic but it will show progress */
+ xaml_RenderProgress.Value = ((double) page_num / ((double) page_num + 1))* 100.0;
+ }));
+ }
+
+ /* Render the thumbnail images */
+ private void RenderThumbs()
+ {
+ xaml_RenderProgress.Value = 0;
+ xaml_RenderProgressText.Text = "Creating Thumbs";
+ xaml_ProgressGrid.Visibility = System.Windows.Visibility.Visible;
+
+ m_ghostscript.gsPageRenderedMain += new ghostsharp.gsCallBackPageRenderedMain(gsThumbRendered);
+ m_ghostscript.gsDisplayDeviceRenderAll(m_currfile, Constants.SCALE_THUMB, false, GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF);
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml b/demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml
new file mode 100644
index 00000000..d4f752d4
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml
@@ -0,0 +1,214 @@
+<Window x:Class="ghostnet_wpf_example.MainWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:local="clr-namespace:ghostnet_wpf_example"
+ mc:Ignorable="d" UseLayoutRounding="True"
+ Title="GhostNet WPF example" Height="880" Width="870">
+ <!-- UseLayoutRounding needed to avoid funny interpolation effects on pages -->
+ <Window.Resources>
+ <ItemsPanelTemplate x:Key="MenuItemPanelTemplateNoIcon">
+ <StackPanel Margin="-20,0,0,0" Background="WhiteSmoke"/>
+ </ItemsPanelTemplate>
+ <DataTemplate x:Key="PageTemplate">
+ <Canvas Tag="{Binding Path=pagename}" HorizontalAlignment="Center" VerticalAlignment="Top" Height="{Binding Height}" Width="{Binding Width}" Margin="0,0,0,0" ClipToBounds="True">
+ <Image Width="{Binding Width}" Height="{Binding Height}" Stretch="Fill" HorizontalAlignment="Center" Source="{Binding BitMap}">
+ <Image.BitmapEffect>
+ <DropShadowBitmapEffect Color="Black" Direction="-50" ShadowDepth="40" Softness=".7" />
+ </Image.BitmapEffect>
+ </Image>
+ </Canvas>
+ </DataTemplate>
+
+ <DataTemplate x:Key="ThumbTemplate">
+ <Image Width="{Binding Width}" Height="{Binding Height}" Stretch="Fill" HorizontalAlignment="Center" Source="{Binding BitMap}" Margin="24,24,0,0">
+ <Image.BitmapEffect>
+ <DropShadowBitmapEffect Color="Black" Direction="-50"
+ ShadowDepth="5" Softness=".7" />
+ </Image.BitmapEffect>
+ </Image>
+ </DataTemplate>
+
+ </Window.Resources>
+
+ <!-- The following is needed to set up all the keyboard short cuts -->
+ <Window.CommandBindings>
+ <CommandBinding Command="Open" Executed="OpenFileCommand"></CommandBinding>
+ <CommandBinding Command="Close" Executed="CloseCommand"></CommandBinding>
+ <CommandBinding Command="Print" Executed="PrintCommand"></CommandBinding>
+ </Window.CommandBindings>
+ <Window.InputBindings>
+ <KeyBinding Key="O" Modifiers="Control" Command="Open"></KeyBinding>
+ <KeyBinding Key="W" Modifiers="Control" Command="Close"></KeyBinding>
+ <KeyBinding Key="P" Modifiers="Control" Command="Print"></KeyBinding>
+ </Window.InputBindings>
+
+ <DockPanel LastChildFill="True">
+ <Menu IsMainMenu="True" DockPanel.Dock="Top" Background="WhiteSmoke" FocusManager.IsFocusScope="False">
+ <MenuItem Header="_File" x:Name="xaml_file" VerticalAlignment="Center">
+ <MenuItem VerticalAlignment="Center" Padding="5" InputGestureText="Ctrl+O" Command="Open" >
+ <MenuItem.Header>
+ <TextBlock Margin="5,0,0,0" VerticalAlignment="Center" Text="Open..." ></TextBlock>
+ </MenuItem.Header>
+ </MenuItem>
+ <MenuItem Padding="5" Command="Close" InputGestureText="Ctrl+W" x:Name="xaml_closefile" VerticalAlignment="Center">
+ <MenuItem.Header>
+ <TextBlock Margin="5,0,0,0" Text="Close" VerticalAlignment="Center"></TextBlock>
+ </MenuItem.Header>
+ </MenuItem>
+ <MenuItem Padding="5" Command="Print" InputGestureText="Ctrl+W" x:Name="xaml_printfile" VerticalAlignment="Center">
+ <MenuItem.Header>
+ <TextBlock Margin="5,0,0,0" Text="Print" VerticalAlignment="Center"></TextBlock>
+ </MenuItem.Header>
+ </MenuItem>
+ <MenuItem Padding="5" Click="ShowGSMessage" x:Name="xaml_gsmessage" VerticalAlignment="Center" >
+ <MenuItem.Header>
+ <TextBlock Margin="5,0,0,0" Text="Show Messages" VerticalAlignment="Center"></TextBlock>
+ </MenuItem.Header>
+ </MenuItem>
+ </MenuItem>
+ <MenuItem Header="About" Click="OnAboutClick"/>
+ </Menu>
+ <WrapPanel Orientation="Horizontal" DockPanel.Dock="Top" Background="WhiteSmoke">
+ <TextBox x:Name="xaml_currPage" Grid.Row="0" Width="40" Height="20" VerticalScrollBarVisibility="Hidden" Padding="0"
+ HorizontalScrollBarVisibility="Hidden" TextAlignment="Center" Margin="20,2,0,2" PreviewKeyDown="PageEnterClicked" VerticalAlignment="Center"/>
+ <TextBlock Margin="2,0,0,0" Height="20" Text="/ 0" x:Name="xaml_TotalPages" Padding="0" VerticalAlignment="Center" Focusable="False">
+ <TextBlock.FontSize>12</TextBlock.FontSize>
+ </TextBlock>
+
+ <Button Margin="20 0 2 0" Width="20" Height="20" Click="ZoomIn" Background="Transparent" BorderBrush="DarkBlue" x:Name="xaml_zoomIn" Focusable="False">
+ <TextBlock Margin="0,0,0,0" Height="20" Text="+" FontWeight="Bold">
+ <TextBlock.FontSize>12</TextBlock.FontSize>
+ </TextBlock>
+ </Button>
+ <Button Margin="0 0 2 0" x:Name="xaml_zoomOut" Width="20" Height="20" Click="ZoomOut" Background="Transparent" BorderBrush="DarkBlue">
+ <TextBlock Margin="0,0,0,0" Height="20" Text="–" FontWeight="Bold">
+ <TextBlock.FontSize>12</TextBlock.FontSize>
+ </TextBlock>
+ </Button>
+ <TextBox Grid.Row="0" Grid.Column="2" Margin="0 0 0 0" Width="30" Height="20" VerticalScrollBarVisibility="Hidden"
+ HorizontalScrollBarVisibility="Hidden" TextAlignment="Left" x:Name="xaml_Zoomsize"
+ PreviewKeyDown="ZoomEnterClicked" TextChanged="ZoomTextChanged" VerticalAlignment="Center" Padding="0"/>
+ <TextBlock Margin="2,0,0,0" Height="20" Text="%">
+ <TextBlock.FontSize>12</TextBlock.FontSize>
+ </TextBlock>
+
+ <TextBlock Margin="20,0,0,0" Height="20" Text="Enable Antialias:">
+ <TextBlock.FontSize>12</TextBlock.FontSize>
+ </TextBlock>
+ <CheckBox x:Name="xaml_aa" Margin="2,2,0,0" Unchecked="AA_uncheck" Checked="AA_check" IsChecked="True"></CheckBox>
+ </WrapPanel>
+
+ <!-- The progress bar that runs during GS distilling -->
+ <Grid x:Name="xaml_DistillGrid" DockPanel.Dock="Bottom" Visibility="Collapsed">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <ProgressBar x:Name="xaml_DistillProgress" Grid.Row="0" Grid.Column="0" Margin="3" Minimum="0"
+ Maximum="100" Height="20" HorizontalAlignment="Stretch"/>
+ <TextBlock x:Name="xaml_DistillName" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="5, 0, 5, 0"><Bold>Distilling</Bold></TextBlock>
+ <Button Grid.Row="0" Grid.Column="2" Width="50" Height="20" Name="xaml_CancelDistill" Click="CancelDistillClick" Background="Transparent" BorderBrush="Transparent" Margin="5,0,5,0">
+ <Button.Template>
+ <ControlTemplate TargetType="{x:Type Button}">
+ <Grid>
+ <Rectangle Height="Auto" RadiusX="5" RadiusY="5">
+ <Rectangle.Fill >
+ <SolidColorBrush Color="LightSlateGray"></SolidColorBrush>
+ </Rectangle.Fill>
+ </Rectangle>
+ <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+ </Grid>
+ </ControlTemplate>
+ </Button.Template>
+ <TextBlock><Bold>Cancel</Bold></TextBlock>
+ </Button>
+ </Grid>
+
+ <Grid x:Name="xaml_PrintGrid" DockPanel.Dock="Bottom" Visibility="Collapsed">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <ProgressBar x:Name="xaml_PrintProgress" Grid.Row="0" Grid.Column="0" Margin="3" Minimum="0"
+ Maximum="100" Height="20" HorizontalAlignment="Stretch" />
+ <TextBlock Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="5, 0, 5, 0"><Bold>Printing</Bold></TextBlock>
+ </Grid>
+
+ <!-- The progress bar that runs while the pages are rendered -->
+ <Grid x:Name="xaml_ProgressGrid" DockPanel.Dock="Bottom" Visibility="Collapsed">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <ProgressBar x:Name="xaml_RenderProgress" Grid.Row="0" Grid.Column="0" Margin="3" Minimum="0"
+ Maximum="100" Height="20" HorizontalAlignment="Stretch" />
+ <TextBlock x:Name="xaml_RenderProgressText" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Margin="5, 0, 5, 0"><Bold>Creating Thumbs</Bold></TextBlock>
+ </Grid>
+
+ <!-- Thumb viewer/selector -->
+ <Grid x:Name="xaml_ThumbGrid" Width="150" Background="DarkGray" DockPanel.Dock="Left" Visibility="Collapsed">
+ <ListView x:Name="xaml_ThumbList" HorizontalAlignment="Stretch"
+ ItemTemplate="{StaticResource ThumbTemplate}"
+ ScrollViewer.CanContentScroll="False"
+ Background="DarkGray"
+ PreviewMouseLeftButtonUp="ThumbSelected">
+ </ListView>
+ </Grid>
+
+ <!-- Pages are last child fill. This goes in the center of dock panel ScrollViewer.CanContentScroll False allows continuous scrolling-->
+ <!-- <Grid x:Name="xaml_PageGrid" HorizontalAlignment="Stretch" Background="DarkGray" DockPanel.Dock="Left" AllowDrop="True"> -->
+ <ListView x:Name="xaml_PageList" HorizontalAlignment="Stretch"
+ ItemTemplate="{StaticResource PageTemplate}"
+ ScrollViewer.CanContentScroll="False"
+ Background="DarkGray"
+ ScrollViewer.IsDeferredScrollingEnabled="False"
+ IsHitTestVisible="True"
+ SelectionMode="Single"
+ ScrollViewer.HorizontalScrollBarVisibility="Auto"
+ ScrollViewer.VerticalScrollBarVisibility="Visible"
+ ScrollViewer.ScrollChanged="PageScrollChanged"
+ DockPanel.Dock="Left" AllowDrop="True"
+ >
+
+ <!-- This keeps the pages in the center of the panel -->
+ <ListView.ItemContainerStyle>
+ <Style TargetType="ListViewItem">
+ <Setter Property="BorderThickness" Value="0"/>
+ <Setter Property="Margin" Value="10"/>
+ <!-- This should be changed with PAGE_MARGIN -->
+ <Setter Property="Padding" Value="0"/>
+ <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+ <!-- This overrides the blue back ground color of the current selection or mouse over -->
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="ListViewItem">
+ <Border x:Name="border" Background="Transparent">
+ <VisualStateManager.VisualStateGroups>
+ <VisualStateGroup x:Name="CommonStates">
+ <VisualState x:Name="Normal" />
+ <VisualState x:Name="Disabled" />
+ </VisualStateGroup>
+ <VisualStateGroup x:Name="SelectionStates">
+ <VisualState x:Name="Unselected" />
+ <VisualState x:Name="Selected">
+ </VisualState>
+ <VisualState x:Name="SelectedUnfocused">
+ </VisualState>
+ </VisualStateGroup>
+ </VisualStateManager.VisualStateGroups>
+ <ContentPresenter/>
+ </Border>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+ </ListView.ItemContainerStyle>
+ </ListView>
+ </DockPanel>
+
+</Window>
diff --git a/demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml.cs b/demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml.cs
new file mode 100644
index 00000000..7ac077c2
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/MainWindow.xaml.cs
@@ -0,0 +1,739 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.ComponentModel;
+using System.Diagnostics;
+using Microsoft.Win32;
+using GhostNET;
+using System.IO;
+
+static class Constants
+{
+ public const double SCALE_THUMB = 0.1;
+ public const int BLANK_WIDTH = 17;
+ public const int BLANK_HEIGHT = 22;
+ public const int DEFAULT_GS_RES = 300;
+ public const int PAGE_VERT_MARGIN = 10;
+ public const int MAX_PRINT_PREVIEW_LENGTH = 250;
+ public const int ZOOM_MAX = 4;
+ public const double ZOOM_MIN = 0.25;
+}
+
+namespace ghostnet_wpf_example
+{
+ /// <summary>
+ /// Interaction logic for MainWindow.xaml
+ /// </summary>
+ ///
+ public enum NotifyType_t
+ {
+ MESS_STATUS,
+ MESS_ERROR
+ };
+
+ public enum status_t
+ {
+ S_ISOK,
+ E_FAILURE,
+ E_OUTOFMEM,
+ E_NEEDPASSWORD
+ };
+
+ public enum Page_Content_t
+ {
+ FULL_RESOLUTION = 0,
+ THUMBNAIL,
+ OLD_RESOLUTION,
+ LOW_RESOLUTION,
+ NOTSET,
+ BLANK
+ };
+ public enum zoom_t
+ {
+ NO_ZOOM,
+ ZOOM_IN,
+ ZOOM_OUT
+ }
+
+ public enum doc_t
+ {
+ UNKNOWN,
+ PDF,
+ PS,
+ PCL,
+ XPS
+ }
+
+ public struct idata_t
+ {
+ public int page_num;
+ public Byte[] bitmap;
+ public int height;
+ public int width;
+ public int raster;
+ public double zoom;
+ }
+
+ public struct pagesizes_t
+ {
+ public Point size;
+ public double cummulative_y;
+ }
+
+ public partial class MainWindow : Window
+ {
+
+ ghostsharp m_ghostscript;
+ bool m_file_open;
+ doc_t m_document_type;
+ String m_currfile;
+ List<TempFile> m_tempfiles;
+ String m_origfile;
+ int m_currpage;
+ gsOutput m_gsoutput;
+ public int m_numpages;
+ private static Pages m_docPages;
+ private static double m_doczoom;
+ public List<pagesizes_t> m_page_sizes;
+ List<idata_t> m_list_thumb;
+ List<idata_t> m_images_rendered;
+ bool m_init_done;
+ bool m_busy_render;
+ bool m_firstime;
+ bool m_validZoom;
+ bool m_aa;
+ bool m_aa_change;
+ List<int> m_toppage_pos;
+ int m_page_progress_count;
+
+ private static List<Page_Content_t> m_pageType;
+
+ public void ShowMessage(NotifyType_t type, String Message)
+ {
+ if (type == NotifyType_t.MESS_ERROR)
+ {
+ MessageBox.Show(Message, "Error", MessageBoxButton.OK);
+ }
+ else
+ {
+ MessageBox.Show(Message, "Notice", MessageBoxButton.OK);
+ }
+ }
+ public MainWindow()
+ {
+ InitializeComponent();
+ this.Closing += new System.ComponentModel.CancelEventHandler(Window_Closing);
+
+ /* Set up ghostscript calls for progress update */
+ m_ghostscript = new ghostsharp();
+ m_ghostscript.gsUpdateMain += new ghostsharp.gsCallBackMain(gsProgress);
+ m_ghostscript.gsIOUpdateMain += new ghostsharp.gsIOCallBackMain(gsIO);
+ m_ghostscript.gsDLLProblemMain += new ghostsharp.gsDLLProblem(gsDLL);
+
+ m_currpage = 0;
+ m_gsoutput = new gsOutput();
+ m_gsoutput.Activate();
+ m_tempfiles = new List<TempFile>();
+ m_thumbnails = new List<DocPage>();
+ m_docPages = new Pages();
+ m_pageType = new List<Page_Content_t>();
+ m_page_sizes = new List<pagesizes_t>();
+ m_file_open = false;
+ m_document_type = doc_t.UNKNOWN;
+ m_doczoom = 1.0;
+ m_init_done = false;
+ m_busy_render = true;
+ m_validZoom = true;
+ m_firstime = true;
+ m_list_thumb = new List<idata_t>();
+ m_images_rendered = new List<idata_t>();
+ m_busy_rendering = false;
+ m_aa = true;
+ m_aa_change = false;
+
+ xaml_PageList.AddHandler(Grid.DragOverEvent, new System.Windows.DragEventHandler(Grid_DragOver), true);
+ xaml_PageList.AddHandler(Grid.DropEvent, new System.Windows.DragEventHandler(Grid_Drop), true);
+
+ /* For case of opening another file */
+ string[] arguments = Environment.GetCommandLineArgs();
+ if (arguments.Length > 1)
+ {
+ string filePath = arguments[1];
+ ProcessFile(filePath);
+ }
+ }
+ private void gsIO(String mess, int len)
+ {
+ m_gsoutput.Update(mess, len);
+ }
+ private void ShowGSMessage(object sender, RoutedEventArgs e)
+ {
+ m_gsoutput.Show();
+ }
+ private void gsDLL(String mess)
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, mess);
+ }
+
+ private void gsThumbRendered(int width, int height, int raster,
+ IntPtr data, gsParamState_t state)
+ {
+ ThumbPageCallback(width, height, raster, state.zoom, state.currpage, data);
+ }
+
+ private void gsPageRendered(int width, int height, int raster,
+ IntPtr data, gsParamState_t state)
+ {
+ MainPageCallback(width, height, raster, state.zoom, state.currpage, data);
+ }
+
+ private void gsProgress(gsEventArgs asyncInformation)
+ {
+ if (asyncInformation.Completed)
+ {
+ switch (asyncInformation.Params.task)
+ {
+
+ case GS_Task_t.CREATE_XPS:
+ xaml_DistillProgress.Value = 100;
+ xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
+ break;
+
+ case GS_Task_t.PS_DISTILL:
+ xaml_DistillProgress.Value = 100;
+ xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
+ break;
+
+ case GS_Task_t.SAVE_RESULT:
+ break;
+
+ case GS_Task_t.DISPLAY_DEV_THUMBS_NON_PDF:
+ case GS_Task_t.DISPLAY_DEV_THUMBS_PDF:
+ ThumbsDone();
+ break;
+
+ case GS_Task_t.DISPLAY_DEV_PDF:
+ case GS_Task_t.DISPLAY_DEV_NON_PDF:
+ RenderingDone();
+ break;
+
+ }
+ 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;
+
+ default:
+ ShowMessage(NotifyType_t.MESS_STATUS, "Ghostscript failed.");
+ break;
+
+ }
+ return;
+ }
+ GSResult(asyncInformation.Params);
+ }
+ else
+ {
+ switch (asyncInformation.Params.task)
+ {
+ case GS_Task_t.CREATE_XPS:
+ this.xaml_DistillProgress.Value = asyncInformation.Progress;
+ break;
+
+ case GS_Task_t.PS_DISTILL:
+ this.xaml_DistillProgress.Value = asyncInformation.Progress;
+ break;
+
+ case GS_Task_t.SAVE_RESULT:
+ break;
+ }
+ }
+ }
+
+ /* GS Result*/
+ public void GSResult(gsParamState_t gs_result)
+ {
+ TempFile tempfile = null;
+
+ if (gs_result.outputfile != null)
+ tempfile = new TempFile(gs_result.outputfile);
+
+ if (gs_result.result == GS_Result_t.gsCANCELLED)
+ {
+ xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
+ if (tempfile != null)
+ {
+ try
+ {
+ tempfile.DeleteFile();
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ }
+ return;
+ }
+ if (gs_result.result == GS_Result_t.gsFAILED)
+ {
+ xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
+ ShowMessage(NotifyType_t.MESS_STATUS, "GS Failed Conversion");
+ if (tempfile != null)
+ {
+ try
+ {
+ tempfile.DeleteFile();
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ }
+ return;
+ }
+ switch (gs_result.task)
+ {
+ case GS_Task_t.CREATE_XPS:
+ xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
+ /* Always do print all from xps conversion as it will do
+ * the page range handling for us */
+ /* Add file to temp file list */
+ m_tempfiles.Add(tempfile);
+ PrintXPS(gs_result.outputfile, true, -1, -1, true);
+ break;
+
+ case GS_Task_t.PS_DISTILL:
+ xaml_DistillGrid.Visibility = System.Windows.Visibility.Collapsed;
+ m_origfile = gs_result.inputfile;
+
+ /* Save distilled result */
+ SaveFileDialog dlg = new SaveFileDialog();
+ dlg.Filter = "PDF file (*.pdf)|*.pdf";
+ dlg.FileName = System.IO.Path.GetFileNameWithoutExtension(m_origfile) + ".pdf";
+ if (dlg.ShowDialog() == true)
+ {
+ try
+ {
+ if (File.Exists(dlg.FileName))
+ {
+ File.Delete(dlg.FileName);
+ }
+ File.Copy(tempfile.Filename, dlg.FileName);
+ }
+ catch (Exception except)
+ {
+ ShowMessage(NotifyType_t.MESS_ERROR, "Exception Saving Distilled Result:" + except.Message);
+ }
+
+ }
+ tempfile.DeleteFile();
+ break;
+
+ case GS_Task_t.SAVE_RESULT:
+ /* Don't delete file in this case as this was our output! */
+ ShowMessage(NotifyType_t.MESS_STATUS, "GS Completed Conversion");
+ break;
+ }
+ }
+
+ private void OpenFileCommand(object sender, ExecutedRoutedEventArgs e)
+ {
+ OpenFile(sender, e);
+ }
+
+ private void CleanUp()
+ {
+ m_init_done = false;
+
+ /* Collapse this stuff since it is going to be released */
+ xaml_ThumbGrid.Visibility = System.Windows.Visibility.Collapsed;
+
+ /* Clear out everything */
+ if (m_docPages != null && m_docPages.Count > 0)
+ m_docPages.Clear();
+ if (m_pageType != null && m_pageType.Count > 0)
+ m_pageType.Clear();
+ if (m_thumbnails != null && m_thumbnails.Count > 0)
+ m_thumbnails.Clear();
+ if (m_toppage_pos != null && m_toppage_pos.Count > 0)
+ m_toppage_pos.Clear();
+ if (m_list_thumb != null && m_list_thumb.Count > 0)
+ m_list_thumb.Clear();
+ if (m_images_rendered != null && m_images_rendered.Count > 0)
+ m_images_rendered.Clear();
+ if (m_page_sizes != null && m_page_sizes.Count > 0)
+ m_page_sizes.Clear();
+
+ m_currfile = null;
+ m_origfile = null;
+ m_numpages = -1;
+ m_file_open = false;
+ m_firstime = true;
+ m_document_type = doc_t.UNKNOWN;
+ m_origfile = null;
+ CleanUpTempFiles();
+ m_file_open = false;
+ m_busy_render = true;
+ xaml_TotalPages.Text = "/ 0";
+ xaml_currPage.Text = "0";
+ CloseExtraWindows(false);
+
+ return;
+ }
+
+ private void CloseCommand(object sender, ExecutedRoutedEventArgs e)
+ {
+ if (m_init_done)
+ CleanUp();
+ }
+
+ private bool ReadyForOpen()
+ {
+ /* 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 false;
+ }
+ return true;
+ }
+
+ private void OpenFile(object sender, RoutedEventArgs e)
+ {
+ if (!ReadyForOpen())
+ return;
+
+ OpenFileDialog dlg = new OpenFileDialog();
+ dlg.Filter = "Document Files(*.ps;*.eps;*.pdf)|*.ps;*.eps;*.pdf;|All files (*.*)|*.*";
+ dlg.FilterIndex = 1;
+ if (dlg.ShowDialog() == true)
+ ProcessFile(dlg.FileName);
+ }
+
+ public void ProcessFile(String FileName)
+ {
+ /* Before we even get started check for issues */
+ /* First check if file exists and is available */
+ if (!System.IO.File.Exists(FileName))
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "File not found!");
+ return;
+ }
+ if (m_file_open)
+ {
+ /* In this case, we want to go ahead and launch a new process
+ * handing it the filename */
+ /* We need to get the location */
+ string path = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
+ try
+ {
+ Process.Start(path, FileName);
+ }
+ catch (InvalidOperationException)
+ {
+ Console.WriteLine("InvalidOperationException");
+ }
+ catch (Win32Exception)
+ {
+ Console.WriteLine("Win32 Exception: There was an error in opening the associated file. ");
+ }
+ return;
+ }
+
+ /* If we have a ps or eps file then launch the distiller first
+ * and then we will get a temp pdf file which we will open. This is done
+ * to demo both methods of doing callbacks from gs worker thread. Either
+ * progress as we distill the stream for PS or page by page for PDF */
+ 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 = doc_t.PS;
+ break;
+ case ".EPS":
+ m_document_type = doc_t.PS;
+ break;
+ case ".PDF":
+ m_document_type = doc_t.PDF;
+ break;
+ case ".XPS":
+ m_document_type = doc_t.XPS;
+ break;
+ case ".BIN":
+ m_document_type = doc_t.PCL;
+ break;
+ default:
+ {
+ m_document_type = doc_t.UNKNOWN;
+ ShowMessage(NotifyType_t.MESS_STATUS, "Unknown File Type");
+ return;
+ }
+ }
+ if (extension.ToUpper() != ".PDF")
+ {
+
+ MessageBoxResult result = MessageBox.Show("Would you like to Distill this file?", "ghostnet", MessageBoxButton.YesNoCancel);
+ switch (result)
+ {
+ case MessageBoxResult.Yes:
+ 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;
+ case MessageBoxResult.No:
+ break;
+ case MessageBoxResult.Cancel:
+ return;
+ }
+ }
+ m_currfile = FileName;
+ //m_numpages = m_ghostscript.GetPageCount(m_currfile);
+ RenderThumbs();
+ return;
+
+ }
+ private void CancelDistillClick(object sender, RoutedEventArgs e)
+ {
+
+ }
+ private void DeleteTempFile(String file)
+ {
+ for (int k = 0; k < m_tempfiles.Count; k++)
+ {
+ if (String.Compare(file, m_tempfiles[k].Filename) == 0)
+ {
+ try
+ {
+ m_tempfiles[k].DeleteFile();
+ m_tempfiles.RemoveAt(k);
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ break;
+ }
+ }
+ }
+
+ private void CleanUpTempFiles()
+ {
+ for (int k = 0; k < m_tempfiles.Count; k++)
+ {
+ try
+ {
+ m_tempfiles[k].DeleteFile();
+ }
+ catch
+ {
+ ShowMessage(NotifyType_t.MESS_STATUS, "Problem Deleting Temp File");
+ }
+ }
+ m_tempfiles.Clear();
+ }
+ private void OnAboutClick(object sender, RoutedEventArgs e)
+ {
+ About about = new About(this);
+ var desc_static = about.Description;
+ String desc;
+
+ String gs_vers = m_ghostscript.GetVersion();
+ if (gs_vers == null)
+ desc = "\nGhostscript DLL: Not Found";
+ else
+ desc = "\nGhostscript DLL: Using " + gs_vers + " 64 bit\n";
+
+ about.description.Text = desc;
+ about.ShowDialog();
+ }
+
+ private static DocPage InitDocPage()
+ {
+ DocPage doc_page = new DocPage();
+
+ doc_page.BitMap = null;
+ doc_page.Height = Constants.BLANK_HEIGHT;
+ doc_page.Width = Constants.BLANK_WIDTH;
+ return doc_page;
+ }
+
+ private void ThumbSelected(object sender, MouseButtonEventArgs e)
+ {
+ var item = ((FrameworkElement)e.OriginalSource).DataContext as DocPage;
+
+ if (item != null)
+ {
+ if (item.PageNum < 0)
+ return;
+
+ var obj = xaml_PageList.Items[item.PageNum - 1];
+ xaml_PageList.ScrollIntoView(obj);
+ }
+ }
+
+ void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ CloseExtraWindows(true);
+ }
+
+ void CloseExtraWindows(bool shutdown)
+ {
+ if (shutdown)
+ {
+ if (m_gsoutput != null)
+ m_gsoutput.RealWindowClosing();
+ if (m_printcontrol != null)
+ m_printcontrol.RealWindowClosing();
+ }
+ else
+ {
+ if (m_gsoutput != null)
+ m_gsoutput.Hide();
+ if (m_printcontrol != null)
+ m_printcontrol.Hide();
+ }
+ }
+
+ private void PageScrollChanged(object sender, ScrollChangedEventArgs e)
+ {
+ e.Handled = true;
+
+ if (!m_init_done || m_busy_rendering || m_toppage_pos == null)
+ return;
+
+ /* Find the pages that are visible. */
+ double top_window = e.VerticalOffset;
+ double bottom_window = top_window + e.ViewportHeight;
+ int first_page = -1;
+ int last_page = -1;
+
+ if (top_window > m_toppage_pos[m_numpages - 1])
+ {
+ first_page = m_numpages - 1;
+ last_page = first_page;
+ }
+ else
+ {
+ for (int k = 0; k < (m_toppage_pos.Count() - 1); k++)
+ {
+ if (top_window <= m_toppage_pos[k + 1] && bottom_window >= m_toppage_pos[k])
+ {
+ if (first_page == -1)
+ first_page = k;
+ else
+ {
+ last_page = k;
+ break;
+ }
+ }
+ else
+ {
+ if (first_page != -1)
+ {
+ last_page = first_page;
+ break;
+ }
+ }
+ }
+ }
+
+ m_currpage = first_page;
+ xaml_currPage.Text = (m_currpage + 1).ToString();
+
+ /* Only PDF does this page sensitive approach */
+ if (m_document_type != doc_t.PDF)
+ return;
+
+ /* Disable for now. All do full doc rendering. NB implement
+ * for XPS and PDF which allow direct page access */
+ //PageRangeRender(first_page, last_page);
+ return;
+ }
+ 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));
+ }
+ }
+ private void PageEnterClicked(object sender, 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_numpages + 1))
+ {
+ var obj = xaml_PageList.Items[page - 1];
+ xaml_PageList.ScrollIntoView(obj);
+ }
+ }
+ catch (FormatException)
+ {
+ Console.WriteLine("String is not a sequence of digits.");
+ }
+ catch (OverflowException)
+ {
+ Console.WriteLine("The number cannot fit in an Int32.");
+ }
+ }
+ }
+
+ private void AA_uncheck(object sender, RoutedEventArgs e)
+ {
+ m_aa = false;
+ m_aa_change = true;
+ RenderMainAll();
+ }
+
+ private void AA_check(object sender, RoutedEventArgs e)
+ {
+ m_aa = true;
+ m_aa_change = true;
+ RenderMainAll();
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/MainZoom.cs b/demos/csharp/windows/ghostnet_wpf_example/MainZoom.cs
new file mode 100644
index 00000000..0502e6c7
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/MainZoom.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Text.RegularExpressions;
+using System.Windows.Input;
+
+namespace ghostnet_wpf_example
+{
+ public partial class MainWindow
+ {
+ static double[] ZoomSteps = new double[]
+ {0.25, 0.333, 0.482, 0.667, 0.75, 1.0, 1.25, 1.37,
+ 1.50, 2.00, 3.00, 4.00};
+
+ public double GetNextZoom(double curr_zoom, int direction)
+ {
+ int k = 0;
+
+ /* Find segement we are in */
+ for (k = 0; k < ZoomSteps.Length - 1; k++)
+ {
+ if (curr_zoom >= ZoomSteps[k] && curr_zoom <= ZoomSteps[k + 1])
+ break;
+ }
+
+ /* Handling increasing zoom case. Look at upper boundary */
+ if (curr_zoom < ZoomSteps[k + 1] && direction > 0)
+ return ZoomSteps[k + 1];
+
+ if (curr_zoom == ZoomSteps[k + 1] && direction > 0)
+ {
+ if (k + 1 < ZoomSteps.Length - 1)
+ return ZoomSteps[k + 2];
+ else
+ return ZoomSteps[k + 1];
+ }
+
+ /* Handling decreasing zoom case. Look at lower boundary */
+ if (curr_zoom > ZoomSteps[k] && direction < 0)
+ return ZoomSteps[k];
+
+ if (curr_zoom == ZoomSteps[k] && direction < 0)
+ {
+ if (k > 0)
+ return ZoomSteps[k - 1];
+ else
+ return ZoomSteps[k];
+ }
+ return curr_zoom;
+ }
+
+ private bool ZoomDisabled()
+ {
+ if (!m_init_done || m_busy_render)
+ return true;
+ else
+ return false;
+ }
+
+ private void ZoomOut(object sender, RoutedEventArgs e)
+ {
+ if (ZoomDisabled())
+ return;
+ if (!m_init_done || m_doczoom <= Constants.ZOOM_MIN)
+ return;
+
+ m_doczoom = GetNextZoom(m_doczoom, -1);
+ xaml_Zoomsize.Text = Math.Round(m_doczoom * 100.0).ToString();
+ ResizePages();
+ RenderMainAll();
+ }
+
+ private void ZoomIn(object sender, RoutedEventArgs e)
+ {
+ if (ZoomDisabled())
+ return;
+ if (!m_init_done || m_doczoom >= Constants.ZOOM_MAX)
+ return;
+
+ m_doczoom = GetNextZoom(m_doczoom, 1);
+ xaml_Zoomsize.Text = Math.Round(m_doczoom * 100.0).ToString();
+ ResizePages();
+ RenderMainAll();
+ }
+
+ private void ZoomTextChanged(object sender, TextChangedEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9.]+");
+ System.Windows.Controls.TextBox tbox =
+ (System.Windows.Controls.TextBox)sender;
+
+ if (tbox.Text == "")
+ {
+ e.Handled = true;
+ return;
+ }
+
+ /* Need to check it again. back space does not cause PreviewTextInputTo
+ * to fire */
+ bool ok = !regex.IsMatch(tbox.Text);
+ if (ok)
+ m_validZoom = true;
+ else
+ {
+ m_validZoom = false;
+ tbox.Text = "";
+ }
+ }
+
+ private void ZoomEnterClicked(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (!m_validZoom)
+ return;
+
+ if (e.Key == Key.Return)
+ {
+ e.Handled = true;
+ var desired_zoom = xaml_Zoomsize.Text;
+ try
+ {
+ double zoom = (double)System.Convert.ToDouble(desired_zoom) / 100.0;
+ if (zoom > Constants.ZOOM_MAX)
+ zoom = Constants.ZOOM_MAX;
+ if (zoom < Constants.ZOOM_MIN)
+ zoom = Constants.ZOOM_MIN;
+
+ m_doczoom = zoom;
+ ResizePages();
+ RenderMainAll();
+ xaml_Zoomsize.Text = Math.Round(zoom * 100.0).ToString();
+ }
+ catch (FormatException)
+ {
+ xaml_Zoomsize.Text = "";
+ Console.WriteLine("String is not a sequence of digits.");
+ }
+ catch (OverflowException)
+ {
+ xaml_Zoomsize.Text = "";
+ Console.WriteLine("The number cannot fit");
+ }
+ }
+ }
+
+ private void ResizePages()
+ {
+ if (m_page_sizes.Count == 0)
+ return;
+
+ for (int k = 0; k < m_numpages; k++)
+ {
+ var doc_page = m_docPages[k];
+ if (doc_page.Zoom == m_doczoom &&
+ doc_page.Width == (int)(m_doczoom * m_page_sizes[k].size.X) &&
+ doc_page.Height == (int)(m_doczoom * m_page_sizes[k].size.Y))
+ continue;
+ else
+ {
+ /* Resize it now */
+ doc_page.Width = (int)(m_doczoom * m_page_sizes[k].size.X);
+ doc_page.Height = (int)(m_doczoom * m_page_sizes[k].size.Y);
+ doc_page.Zoom = m_doczoom;
+ doc_page.Content= Page_Content_t.OLD_RESOLUTION;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml b/demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml
new file mode 100644
index 00000000..cc7bb6b6
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml
@@ -0,0 +1,148 @@
+<Window x:Class="ghostnet_wpf_example.Print"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:local="clr-namespace:ghostnet_wpf_example"
+ mc:Ignorable="d"
+ Title="Ghostnet XPS Print" Height="325" Width="550"
+ SizeToContent="WidthAndHeight" ResizeMode="NoResize"
+ FontFamily="Segou UI" FontSize="12">
+
+
+ <Window.Resources>
+ <Style x:Key="MySimpleScrollBar" TargetType="{x:Type ScrollBar}">
+ <Setter Property="Stylus.IsFlicksEnabled" Value="false"/>
+ <Setter Property="Width" Value="Auto"/>
+ <Setter Property="MinHeight" Value="30"/>
+ <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type ScrollBar}">
+ <Border BorderThickness="1" BorderBrush="Gray">
+ <Grid Margin="2">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition/>
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <TextBox VerticalAlignment="Center" FontSize="12" MinWidth="25" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}}"/>
+ <Grid Grid.Column="1" x:Name="GridRoot" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Background="{TemplateBinding Background}">
+ <Grid.RowDefinitions>
+ <RowDefinition MaxHeight="18"/>
+ <RowDefinition Height="0.00001*"/>
+ <RowDefinition MaxHeight="18"/>
+ </Grid.RowDefinitions>
+ <RepeatButton x:Name="DecreaseRepeat" Command="ScrollBar.LineDownCommand" Focusable="False">
+ <Grid>
+ <Path x:Name="DecreaseArrow" Stroke="{TemplateBinding Foreground}" StrokeThickness="1" Data="M 0 4 L 8 4 L 4 0 Z"/>
+ </Grid>
+ </RepeatButton>
+ <RepeatButton Grid.Row="2" x:Name="IncreaseRepeat" Command="ScrollBar.LineUpCommand" Focusable="False">
+ <Grid>
+ <Path x:Name="IncreaseArrow" Stroke="{TemplateBinding Foreground}" StrokeThickness="1" Data="M 0 0 L 4 4 L 8 0 Z"/>
+ </Grid>
+ </RepeatButton>
+ </Grid>
+ </Grid>
+ </Border>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+ </Window.Resources>
+
+ <DockPanel Background="WhiteSmoke" LastChildFill="False" Margin="0,0,0,0">
+ <GroupBox Header="Printer" Height="100" Width="500" DockPanel.Dock="Top" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="15,15,0,0">
+ <Grid Background="WhiteSmoke" Visibility="Visible" >
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <Grid Grid.Column="0" Grid.Row="0" Background="WhiteSmoke">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="30" />
+ <RowDefinition Height="30" />
+ </Grid.RowDefinitions>
+ <TextBox Grid.Column="0" Grid.Row="0" Text="Name:" FontSize="12" FontFamily="Segoe UI" Background="WhiteSmoke" BorderThickness="0" VerticalAlignment="Center"/>
+ <TextBox Grid.Column="0" Grid.Row="1" Text="Status:" FontSize="12" FontFamily="Segoe UI" Background="WhiteSmoke" BorderThickness="0" VerticalAlignment="Center"/>
+ </Grid>
+ <Grid Grid.Column="1" Grid.Row="0" Background="WhiteSmoke">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="30" />
+ <RowDefinition Height="30" />
+ </Grid.RowDefinitions>
+ <ComboBox Grid.Column="0" Grid.Row="0" x:Name="xaml_selPrinter" SelectionChanged="selPrinterChanged" Width="225" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Center" >
+ <ComboBox.ItemTemplate>
+ <DataTemplate>
+ <StackPanel Orientation="Horizontal">
+ <TextBlock FontSize="11" FontFamily="Segoe UI" Text="{Binding Name}" />
+ </StackPanel>
+ </DataTemplate>
+ </ComboBox.ItemTemplate>
+ </ComboBox>
+ <TextBox Grid.Column="0" Grid.Row="1" x:Name="xaml_Status" FontSize="12" FontFamily="Segoe UI" Text="" Margin="105,92,497,301" Background="WhiteSmoke" BorderThickness="0"/>
+ </Grid>
+ <Grid Grid.Column="2" Grid.Row="0" Background="WhiteSmoke">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="30" />
+ </Grid.RowDefinitions>
+ <Button Grid.Column="0" Grid.Row="0" Content="Properties" FontSize="12" FontFamily="Segoe UI" HorizontalAlignment="Center" Margin="0, 0, 30, 0" VerticalAlignment="Center" Width="75" Click="ShowProperties"/>
+ </Grid>
+ </Grid>
+ </GroupBox>
+ <Grid DockPanel.Dock="Top" Background="WhiteSmoke" Visibility="Visible">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <GroupBox Grid.Column="0" Grid.Row="0" Header="Print Range" Height="130" Width="250" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="15,15,0,0">
+ <Grid Background="WhiteSmoke" Visibility="Visible" >
+ <Grid.RowDefinitions>
+ <RowDefinition Height="30" />
+ <RowDefinition Height="30" />
+ <RowDefinition Height="20" />
+ <RowDefinition Height="30" />
+ </Grid.RowDefinitions>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="90" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <RadioButton Grid.Column="0" Grid.Row="0" Name="xaml_rbAll" VerticalAlignment="Center" GroupName="PageRange" Checked="AllPages">All</RadioButton>
+ <RadioButton Grid.Column="0" Grid.Row="1" Name="xaml_rbCurrent" VerticalAlignment="Center" GroupName="PageRange" Checked="CurrentPage">Current page</RadioButton>
+ <RadioButton Grid.Column="0" Grid.Row="3" Name="xaml_rbPages" VerticalAlignment="Center" GroupName="PageRange" Checked="PageRange">Pages</RadioButton>
+
+ <TextBox Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" Text="From" FontSize="12" FontFamily="Segoe UI" Background="WhiteSmoke" BorderThickness="0" />
+ <TextBox Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" Text="To" FontSize="12" Margin="70,0,0,0" FontFamily="Segoe UI" Background="WhiteSmoke" BorderThickness="0" />
+ <TextBox x:Name="xaml_pagestart" Grid.Column="1" Grid.Row="3" Width="50" Height="20" VerticalScrollBarVisibility="Hidden" HorizontalAlignment="Left"
+ HorizontalScrollBarVisibility="Hidden" VerticalAlignment="Center" FontSize="12" FontFamily="Segoe UI" Margin="0,0,0,0" PreviewTextInput="PreviewTextInputFrom"
+ TextChanged="FromChanged"/>
+ <TextBox x:Name="xaml_pageend" Grid.Column="1" Grid.Row="3" Width="50" Height="20" VerticalScrollBarVisibility="Hidden" HorizontalAlignment="Left"
+ HorizontalScrollBarVisibility="Hidden" VerticalAlignment="Center" FontSize="12" FontFamily="Segoe UI" Margin="70,0,0,0" PreviewTextInput="PreviewTextInputTo"
+ TextChanged="ToChanged"/>
+ </Grid>
+ </GroupBox>
+
+ <GroupBox Grid.Column="1" Grid.Row="0" Header="Page Handling" Height="90" Width="250" Margin="0,-25,0,0" DockPanel.Dock="Right" HorizontalAlignment="Center" >
+ <Grid Background="WhiteSmoke" Visibility="Visible" Height="103" VerticalAlignment="Top">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="90" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <TextBox Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" Text="Copies:" FontSize="12" FontFamily="Segoe UI" Background="WhiteSmoke" BorderThickness="0" />
+ <ScrollBar Grid.Column="1" Grid.Row="0" x:Name="xaml_Copies" HorizontalAlignment="Left" Style="{DynamicResource MySimpleScrollBar}" VerticalAlignment="Top" Value="1" Maximum="999" SmallChange="1" Height="15" ValueChanged="xaml_Copies_ValueChanged"/>
+ <CheckBox x:Name="xaml_autofit" Grid.Column="0" Grid.Row="2" Content="Auto-Fit" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Checked="AutoFit_Checked" Unchecked="AutoFit_Unchecked" Grid.ColumnSpan="2"/>
+ </Grid>
+ </GroupBox>
+ </Grid>
+
+ <Button Content="OK" FontSize="12" FontFamily="Segoe UI" HorizontalAlignment="Center" VerticalAlignment="Top" Width="74" Click="ClickOK" Margin="300,-30,0,0"/>
+ <Button Content="Cancel" FontSize="12" FontFamily="Segoe UI" HorizontalAlignment="Center" VerticalAlignment="Top" Width="74" Click="ClickCancel" Margin="40,-30,0,0"/>
+
+
+ </DockPanel>
+</Window>
diff --git a/demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml.cs b/demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml.cs
new file mode 100644
index 00000000..bd5454b5
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/PrintControl.xaml.cs
@@ -0,0 +1,517 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Printing;
+using System.Drawing.Printing;
+using System.Text.RegularExpressions;
+using System.Runtime.InteropServices; /* DLLImport */
+using System.Windows.Interop;
+
+namespace ghostnet_wpf_example
+{
+
+ internal enum fModes
+ {
+ DM_SIZEOF = 0,
+ DM_UPDATE = 1,
+ DM_COPY = 2,
+ DM_PROMPT = 4,
+ DM_MODIFY = 8,
+ DM_OUT_DEFAULT = DM_UPDATE,
+ DM_OUT_BUFFER = DM_COPY,
+ DM_IN_PROMPT = DM_PROMPT,
+ DM_IN_BUFFER = DM_MODIFY,
+ }
+
+ /* Needed native methods for more advanced printing control */
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal class PRINTER_INFO_9
+ {
+ /// <summary>
+ /// A pointer to a DEVMODE structure that defines the per-user
+ /// default printer data such as the paper orientation and the resolution.
+ /// The DEVMODE is stored in the user's registry.
+ /// </summary>
+ public IntPtr pDevMode;
+ }
+
+ static class print_nativeapi
+ {
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr GlobalLock(IntPtr hMem);
+
+ [DllImport("kernel32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool GlobalUnlock(IntPtr hMem);
+
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr GlobalFree(IntPtr hMem);
+
+ [DllImport("winspool.drv", SetLastError = true)]
+ public static extern int OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);
+
+ [DllImport("winspool.drv", SetLastError = true)]
+ public static extern int SetPrinter(IntPtr phPrinter, UInt32 Level , IntPtr pPrinter, UInt32 Command);
+
+ [DllImport("winspool.Drv", EntryPoint = "DocumentPropertiesW", SetLastError = true, ExactSpelling = true,
+ CallingConvention = CallingConvention.StdCall)]
+ public static extern int DocumentProperties(IntPtr hwnd, IntPtr hPrinter, [MarshalAs(UnmanagedType.LPWStr)] string pDeviceName,
+ IntPtr pDevModeOutput, IntPtr pDevModeInput, int fMode);
+
+ [DllImport("winspool.drv", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern int DocumentProperties(IntPtr hWnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput,
+ IntPtr pDevModeInput, fModes fMode);
+ }
+
+ static class NATIVEWIN
+ {
+ public const int IDOK = 1;
+ public const int IDCANCEL = 2;
+ public const int DM_OUT_BUFFER = 2;
+ public const int DM_IN_BUFFER = 8;
+ public const int DM_IN_PROMPT = 4;
+ public const int DM_ORIENTATION = 1;
+ public const int DM_PAPERSIZE = 2;
+ public const int DM_PAPERLENGTH = 4;
+ public const int DM_WIDTH = 8;
+ public const int DMORIENT_PORTRAIT = 1;
+ public const int DMORIENT_LANDSCAPE = 2;
+ }
+ public enum PrintPages_t
+ {
+ RANGE = 2,
+ CURRENT = 1,
+ ALL = 0
+ }
+
+ public enum PageScale_t
+ {
+ NONE = 0,
+ FIT = 1,
+ }
+
+ public class PrintDiagEventArgs : EventArgs
+ {
+ public int m_page;
+
+ public PrintDiagEventArgs(int page)
+ {
+ m_page = page;
+ }
+ }
+
+ public class PrintRanges
+ {
+ public List<bool> ToPrint;
+ public bool HasEvens;
+ public bool HasOdds;
+ public int NumberPages;
+
+ public PrintRanges(int number_pages)
+ {
+ ToPrint = new List<bool>(number_pages);
+ NumberPages = 0;
+ HasEvens = false;
+ HasOdds = false;
+ }
+
+ public void InitRange(Match match)
+ {
+ NumberPages = 0;
+ HasEvens = false;
+ HasOdds = false;
+
+ for (int k = 0; k < ToPrint.Count; k++)
+ {
+ if (CheckValue(match, k))
+ {
+ NumberPages = NumberPages + 1;
+ ToPrint[k] = true;
+ if ((k + 1) % 2 != 0)
+ HasOdds = true;
+ else
+ HasEvens = true;
+ }
+ else
+ ToPrint[k] = false;
+ }
+ }
+
+ private bool CheckValue(Match match, int k)
+ {
+ return false;
+ }
+ }
+
+ public partial class Print : Window
+ {
+ private LocalPrintServer m_printServer;
+ public PrintQueue m_selectedPrinter = null;
+ String m_status;
+ public PrintPages_t m_pages_setting;
+ public double m_page_scale;
+ int m_numpages;
+ PrintCapabilities m_printcap;
+ public PageSettings m_pagedetails;
+ TranslateTransform m_trans_pap;
+ TranslateTransform m_trans_doc;
+ public bool m_isrotated;
+ PrintRanges m_range_pages;
+ public int m_numcopies;
+ bool m_invalidTo;
+ bool m_invalidFrom;
+ public int m_from;
+ public int m_to;
+ ghostnet_wpf_example.MainWindow main;
+ PrinterSettings m_ps;
+
+ public Print(ghostnet_wpf_example.MainWindow main_in, int num_pages)
+ {
+ InitializeComponent();
+
+ m_ps = new PrinterSettings();
+ main = main_in;
+
+ this.Closing += new System.ComponentModel.CancelEventHandler(FakeWindowClosing);
+ InitializeComponent();
+ m_printServer = new LocalPrintServer();
+ m_selectedPrinter = LocalPrintServer.GetDefaultPrintQueue();
+ m_ps.PrinterName = m_selectedPrinter.FullName;
+ m_pagedetails = m_ps.DefaultPageSettings;
+
+
+ xaml_rbAll.IsChecked = true;
+ m_pages_setting = PrintPages_t.ALL;
+
+ xaml_autofit.IsChecked = false;
+
+ m_numpages = num_pages;
+
+ m_printcap = m_selectedPrinter.GetPrintCapabilities();
+
+ m_trans_pap = new TranslateTransform(0, 0);
+ m_trans_doc = new TranslateTransform(0, 0);
+ m_isrotated = false;
+
+ /* Data range case */
+ m_range_pages = new PrintRanges(m_numpages);
+ m_page_scale = 1.0;
+
+ m_numcopies = 1;
+
+ m_invalidTo = true;
+ m_invalidFrom = true;
+ m_from = -1;
+ m_to = -1;
+
+ InitPrinterList();
+ }
+ void FakeWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ e.Cancel = true;
+ this.Hide();
+ }
+
+ public void RealWindowClosing()
+ {
+ this.Closing -= new System.ComponentModel.CancelEventHandler(FakeWindowClosing);
+ this.Close();
+ }
+
+ private void InitPrinterList()
+ {
+ PrintQueueCollection printQueuesOnLocalServer =
+ m_printServer.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections });
+
+ this.xaml_selPrinter.ItemsSource = printQueuesOnLocalServer;
+ if (m_selectedPrinter != null)
+ {
+ foreach (PrintQueue pq in printQueuesOnLocalServer)
+ {
+ if (pq.FullName == m_selectedPrinter.FullName)
+ {
+ this.xaml_selPrinter.SelectedItem = pq;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Printer Status */
+ private void GetPrinterStatus()
+ {
+ if (m_selectedPrinter != null)
+ {
+ if (m_selectedPrinter.IsBusy)
+ m_status = "Busy";
+ else if (m_selectedPrinter.IsNotAvailable)
+ m_status = "Not Available";
+ else if (m_selectedPrinter.IsOffline)
+ m_status = "Offline";
+ else if (m_selectedPrinter.IsOutOfMemory)
+ m_status = "Out Of Memory";
+ else if (m_selectedPrinter.IsOutOfPaper)
+ m_status = "Out Of Paper";
+ else if (m_selectedPrinter.IsOutputBinFull)
+ m_status = "Output Bin Full";
+ else if (m_selectedPrinter.IsPaperJammed)
+ m_status = "Paper Jam";
+ else if (m_selectedPrinter.IsPaused)
+ m_status = "Paused";
+ else if (m_selectedPrinter.IsPendingDeletion)
+ m_status = "Paused";
+ else if (m_selectedPrinter.IsPrinting)
+ m_status = "Printing";
+ else if (m_selectedPrinter.IsProcessing)
+ m_status = "Processing";
+ else if (m_selectedPrinter.IsWaiting)
+ m_status = "Waiting";
+ else if (m_selectedPrinter.IsWarmingUp)
+ m_status = "Warming Up";
+ else
+ m_status = "Ready";
+ xaml_Status.Text = m_status;
+ }
+ }
+
+ private void selPrinterChanged(object sender, SelectionChangedEventArgs e)
+ {
+ m_selectedPrinter = this.xaml_selPrinter.SelectedItem as PrintQueue;
+ GetPrinterStatus();
+ m_ps.PrinterName = m_selectedPrinter.FullName;
+ m_pagedetails = m_ps.DefaultPageSettings;
+ }
+
+ /* We have to do some calling into native methods to deal with and show
+ * the advanced properties referenced by the DEVMODE struture. Ugly,
+ * but I could not figure out how to do with direct WPF C# methods */
+ private void ShowProperties(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ /* First try to open the printer */
+ IntPtr phPrinter;
+ int result = print_nativeapi.OpenPrinter(m_ps.PrinterName, out phPrinter, IntPtr.Zero);
+ if (result == 0)
+ {
+ return;
+ }
+
+ /* Get a pointer to the DEVMODE */
+ IntPtr hDevMode = m_ps.GetHdevmode(m_ps.DefaultPageSettings);
+ IntPtr pDevMode = print_nativeapi.GlobalLock(hDevMode);
+
+ /* Native method wants a handle to our main window */
+ IntPtr hwin = new WindowInteropHelper(this).Handle;
+
+ /* Get size of DEVMODE */
+ int sizeNeeded = print_nativeapi.DocumentProperties(hwin, IntPtr.Zero, m_ps.PrinterName, IntPtr.Zero, pDevMode, 0);
+
+ /* Allocate */
+ IntPtr devModeData = Marshal.AllocHGlobal(sizeNeeded);
+
+ /* Get devmode and show properties window */
+ print_nativeapi.DocumentProperties(hwin, IntPtr.Zero, m_ps.PrinterName, devModeData, pDevMode, fModes.DM_IN_PROMPT | fModes.DM_OUT_BUFFER);
+
+ /* Set the properties, 9 = PRINTER_INFO_9. This was
+ tricky to figure out how to do */
+ PRINTER_INFO_9 info = new PRINTER_INFO_9();
+ info.pDevMode = devModeData;
+ IntPtr infoPtr = Marshal.AllocHGlobal(Marshal.SizeOf<PRINTER_INFO_9>());
+ Marshal.StructureToPtr<PRINTER_INFO_9>(info, infoPtr, false);
+ result = print_nativeapi.SetPrinter(phPrinter, 9, infoPtr, 0);
+
+ /* Clean up */
+ print_nativeapi.GlobalUnlock(hDevMode);
+ print_nativeapi.GlobalFree(hDevMode);
+ Marshal.FreeHGlobal(infoPtr);
+ //Marshal.FreeHGlobal(devModeData); /* NB: Freeing this causes bad things for some reason. */
+ }
+ catch (Exception except)
+ {
+ main.ShowMessage(NotifyType_t.MESS_ERROR, "Exception in native print interface:" + except.Message);
+ }
+ }
+
+ private void AllPages(object sender, RoutedEventArgs e)
+ {
+ m_pages_setting = PrintPages_t.ALL;
+ }
+
+ private void CurrentPage(object sender, RoutedEventArgs e)
+ {
+ m_pages_setting = PrintPages_t.CURRENT;
+ }
+
+ private void UpdatePageRange()
+ {
+ PrintDiagEventArgs info = new PrintDiagEventArgs(m_from - 1);
+ }
+ public bool RangeOK()
+ {
+ if (m_pages_setting == PrintPages_t.ALL ||
+ m_pages_setting == PrintPages_t.CURRENT)
+ return true;
+
+ if (!m_invalidFrom && !m_invalidTo &&
+ m_pages_setting == PrintPages_t.RANGE &&
+ m_to >= m_from && m_to > 0 && m_from > 0)
+ return true;
+
+ return false;
+ }
+
+ private void PageRange(object sender, RoutedEventArgs e)
+ {
+ m_pages_setting = PrintPages_t.RANGE;
+ if (RangeOK())
+ {
+ UpdatePageRange();
+ }
+ }
+
+ private void PreviewTextInputFrom(object sender, TextCompositionEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9]+");
+ bool ok = !regex.IsMatch(e.Text);
+
+ if (!ok)
+ m_invalidFrom = true;
+ else
+ m_invalidFrom = false;
+ }
+
+ private void PreviewTextInputTo(object sender, TextCompositionEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9]+");
+ bool ok = !regex.IsMatch(e.Text);
+
+ if (!ok)
+ m_invalidTo = true;
+ else
+ m_invalidTo = false;
+ }
+ private void FromChanged(object sender, TextChangedEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9]+");
+ TextBox tbox = (TextBox)sender;
+
+ if (tbox.Text == "")
+ {
+ e.Handled = true;
+ return;
+ }
+
+ /* Need to check it again. back space does not cause PreviewTextInputFrom
+ * to fire */
+ bool ok = !regex.IsMatch(tbox.Text);
+ if (!ok)
+ m_invalidFrom = true;
+ else
+ m_invalidFrom = false;
+
+ if (m_invalidFrom)
+ {
+ xaml_pagestart.Text = "";
+ e.Handled = true;
+ m_from = -1;
+ }
+ else
+ {
+ m_from = System.Convert.ToInt32(xaml_pagestart.Text);
+ if (m_from > m_numpages)
+ {
+ m_from = m_numpages;
+ xaml_pagestart.Text = System.Convert.ToString(m_numpages);
+ }
+ if (m_from < 1)
+ {
+ m_from = 1;
+ xaml_pagestart.Text = System.Convert.ToString(m_numpages);
+ }
+ if (!m_invalidFrom && !m_invalidTo &&
+ m_pages_setting == PrintPages_t.RANGE &&
+ m_to >= m_from)
+ {
+ UpdatePageRange();
+ }
+ }
+ }
+
+ private void ToChanged(object sender, TextChangedEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9]+");
+ TextBox tbox = (TextBox)sender;
+
+ if (tbox.Text == "")
+ {
+ e.Handled = true;
+ return;
+ }
+
+ /* Need to check it again. back space does not cause PreviewTextInputTo
+ * to fire */
+ bool ok = !regex.IsMatch(tbox.Text);
+ if (!ok)
+ m_invalidTo = true;
+ else
+ m_invalidTo = false;
+
+ if (m_invalidTo)
+ {
+ xaml_pageend.Text = "";
+ e.Handled = true;
+ m_to = -1;
+ }
+ else
+ {
+ m_to = System.Convert.ToInt32(xaml_pageend.Text);
+ if (m_to > m_numpages)
+ {
+ m_to = m_numpages;
+ xaml_pageend.Text = System.Convert.ToString(m_numpages);
+ }
+ if (m_to < 1)
+ {
+ m_to = 1;
+ xaml_pagestart.Text = System.Convert.ToString(m_numpages);
+ }
+ if (!m_invalidFrom && !m_invalidTo &&
+ m_pages_setting == PrintPages_t.RANGE &&
+ m_to >= m_from)
+ {
+ UpdatePageRange();
+ }
+ }
+ }
+
+ private void xaml_Copies_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
+ {
+ m_numcopies = (int)e.NewValue;
+ }
+
+ private void AutoFit_Checked(object sender, RoutedEventArgs e)
+ {
+
+ }
+
+ private void ClickOK(object sender, RoutedEventArgs e)
+ {
+ main.PrintDiagPrint(this);
+ this.Hide();
+ }
+
+ private void ClickCancel(object sender, RoutedEventArgs e)
+ {
+ this.Hide();
+ }
+
+ private void AutoFit_Unchecked(object sender, RoutedEventArgs e)
+ {
+
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/Properties/AssemblyInfo.cs b/demos/csharp/windows/ghostnet_wpf_example/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..c869281c
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ghostnet_wpf_example")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ghostnet_wpf_example")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.Designer.cs b/demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.Designer.cs
new file mode 100644
index 00000000..f322f66b
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ghostnet_wpf_example.Properties
+{
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ghostnet_wpf_example.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.resx b/demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.resx
new file mode 100644
index 00000000..af7dbebb
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.Designer.cs b/demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.Designer.cs
new file mode 100644
index 00000000..10b32c2c
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ghostnet_wpf_example.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.settings b/demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.settings
new file mode 100644
index 00000000..033d7a5e
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/TempFile.cs b/demos/csharp/windows/ghostnet_wpf_example/TempFile.cs
new file mode 100644
index 00000000..db0c50cb
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/TempFile.cs
@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+
+/* A class to help in the management of temp files */
+namespace ghostnet_wpf_example
+{
+ class TempFile
+ {
+ private String m_filename;
+
+ public TempFile(String Name)
+ {
+ m_filename = Name;
+ }
+
+ public String Filename
+ {
+ get { return m_filename; }
+ }
+
+ public void DeleteFile()
+ {
+ try
+ {
+ if (File.Exists(m_filename))
+ File.Delete(m_filename);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/XPSprint.cs b/demos/csharp/windows/ghostnet_wpf_example/XPSprint.cs
new file mode 100644
index 00000000..f6934467
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/XPSprint.cs
@@ -0,0 +1,587 @@
+using System;
+using System.Printing;
+using System.Windows.Documents;
+using System.Windows.Documents.Serialization;
+using System.Windows.Media;
+using System.Windows.Xps;
+using System.Windows.Xps.Packaging;
+using System.Drawing.Printing;
+using System.Reflection;
+
+namespace ghostnet_wpf_example
+{
+ public enum PrintStatus_t
+ {
+ PRINT_READY,
+ PRINT_BUSY,
+ PRINT_ERROR,
+ PRINT_CANCELLED,
+ PRINT_DONE
+ };
+
+ /* Class for handling async print progress callback */
+ public class gsPrintEventArgs : EventArgs
+ {
+ private PrintStatus_t m_status;
+ private bool m_completed;
+ private int m_page;
+ private int m_page_start;
+ private int m_num_pages;
+ private String m_filename;
+
+ public String FileName
+ {
+ get { return m_filename; }
+ }
+ public PrintStatus_t Status
+ {
+ get { return m_status; }
+ }
+
+ public bool Completed
+ {
+ get { return m_completed; }
+ }
+
+ public int Page
+ {
+ get { return m_page; }
+ }
+
+ public int PageStart
+ {
+ get { return m_page_start; }
+ }
+
+ public int NumPages
+ {
+ get { return m_num_pages; }
+ }
+
+ public gsPrintEventArgs(PrintStatus_t status, bool completed, int page,
+ int page_start, int num_pages, String filename)
+ {
+ m_completed = completed;
+ m_status = status;
+ m_page = page;
+ m_page_start = page_start;
+ m_num_pages = num_pages;
+ m_filename = filename;
+ }
+ }
+
+ /* A page range paginator to override the DocumentPaginator */
+ public class GSDocumentPaginator : DocumentPaginator
+ {
+ private int _first;
+ private int _last;
+ private DocumentPaginator _basepaginator;
+ public GSDocumentPaginator(DocumentPaginator inpaginator, int firstpage,
+ int lastpage)
+ {
+ _first = firstpage - 1;
+ _last = lastpage - 1;
+ _basepaginator = inpaginator;
+
+ _last = Math.Min(_last, _basepaginator.PageCount - 1);
+ }
+
+ /* Wrap fixed page to avoid exception of fixed page in fixed page */
+ public override DocumentPage GetPage(int page_num)
+ {
+ var page = _basepaginator.GetPage(page_num + _first);
+
+ var vis_cont = new ContainerVisual();
+ if (page.Visual is FixedPage)
+ {
+ foreach (var child in ((FixedPage)page.Visual).Children)
+ {
+ var clone = (System.Windows.UIElement)child.GetType().GetMethod("MemberwiseClone",
+ BindingFlags.Instance | BindingFlags.NonPublic).Invoke(child, null);
+
+ var parentField = clone.GetType().GetField("_parent",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ if (parentField != null)
+ {
+ parentField.SetValue(clone, null);
+ vis_cont.Children.Add(clone);
+ }
+ }
+ return new DocumentPage(vis_cont, page.Size, page.BleedBox,
+ page.ContentBox);
+ }
+ return page;
+ }
+
+ public override bool IsPageCountValid
+ {
+ get { return true; }
+ }
+
+ public override int PageCount
+ {
+ get
+ {
+ if (_first > _basepaginator.PageCount - 1)
+ return 0;
+ if (_first > _last)
+ return 0;
+ return _last - _first + 1;
+ }
+ }
+
+ public override System.Windows.Size PageSize
+ {
+ get { return _basepaginator.PageSize; }
+ set { _basepaginator.PageSize = value; }
+ }
+
+ public override IDocumentPaginatorSource Source
+ {
+ get { return _basepaginator.Source; }
+ }
+
+ public override void ComputePageCount()
+ {
+ base.ComputePageCount();
+ }
+
+ protected override void OnGetPageCompleted(GetPageCompletedEventArgs e)
+ {
+ base.OnGetPageCompleted(e);
+ }
+ }
+
+ public class xpsprint
+ {
+ private XpsDocumentWriter m_docWriter = null;
+ internal delegate void AsyncPrintCallBack(object printObject, gsPrintEventArgs info);
+ internal event AsyncPrintCallBack PrintUpdate;
+ private bool m_busy;
+ private int m_num_pages;
+ private int m_first_page;
+ private String m_filename;
+
+ public bool IsBusy()
+ {
+ return m_busy;
+ }
+
+ public xpsprint()
+ {
+ m_busy = false;
+ }
+
+ public void Done()
+ {
+ gsPrintEventArgs info = new gsPrintEventArgs(PrintStatus_t.PRINT_DONE,
+ true, 0, 0, 0, this.m_filename);
+ PrintUpdate(this, info);
+ m_busy = false;
+ }
+
+ /* Main print entry point */
+ public void Print(PrintQueue queu, XpsDocument xpsDocument, Print printcontrol,
+ bool print_all, int from, int to, String filename, bool tempfile)
+ {
+ XpsDocumentWriter docwrite;
+ FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
+ DocumentReference docReference = fixedDocSeq.References[0] ;
+ FixedDocument doc = docReference.GetDocument(false);
+
+ PrintTicket Ticket = SetUpTicket(queu, printcontrol, fixedDocSeq, tempfile);
+ docwrite = GetDocWriter(queu);
+ m_busy = true;
+ m_filename = filename;
+#if DISABLED_FOR_NOW
+ docwrite.WritingPrintTicketRequired +=
+ new WritingPrintTicketRequiredEventHandler(PrintTicket);
+#endif
+ PrintPages(docwrite, doc, print_all, from, to, Ticket);
+ }
+
+ /* Set up the print ticket */
+ private PrintTicket SetUpTicket(PrintQueue queue, Print printcontrol,
+ FixedDocumentSequence fixdoc, bool tempfile)
+ {
+ PrintTicket Ticket = new PrintTicket();
+
+ PageMediaSizeName name = PaperKindToPageMediaSize(printcontrol.m_pagedetails.PaperSize.Kind);
+ PageMediaSize mediasize = new PageMediaSize(name, printcontrol.m_pagedetails.PaperSize.Width, printcontrol.m_pagedetails.PaperSize.Height);
+
+ /* Media size */
+ Ticket.PageMediaSize = mediasize;
+ /* Scale to fit */
+ Ticket.PageScalingFactor = (int)Math.Round(printcontrol.m_page_scale * 100.0);
+
+ System.Windows.Size page_size = new System.Windows.Size(mediasize.Width.Value, mediasize.Height.Value);
+ DocumentPaginator paginator = fixdoc.DocumentPaginator;
+ paginator.PageSize = page_size;
+
+ /* Copy Count */
+ Ticket.CopyCount = printcontrol.m_numcopies;
+
+ if (printcontrol.m_pagedetails.Landscape)
+ Ticket.PageOrientation = PageOrientation.Landscape;
+ else
+ Ticket.PageOrientation = PageOrientation.Portrait;
+
+ /* Orientation. If we had a tempfile, then gs did a conversion
+ * and adjusted everything for us. Other wise we may need to
+ * rotate the document if it was xps to start with. */
+ if (printcontrol.m_isrotated && !tempfile)
+ if (printcontrol.m_pagedetails.Landscape)
+ Ticket.PageOrientation = PageOrientation.Portrait;
+ else
+ Ticket.PageOrientation = PageOrientation.Landscape;
+
+ System.Printing.ValidationResult result = queue.MergeAndValidatePrintTicket(queue.UserPrintTicket, Ticket);
+ queue.UserPrintTicket = result.ValidatedPrintTicket;
+ queue.Commit();
+ return result.ValidatedPrintTicket;
+ }
+
+ /* Send it */
+ private void PrintPages(XpsDocumentWriter xpsdw, FixedDocument fixdoc,
+ bool print_all, int from, int to, PrintTicket Ticket)
+ {
+ m_docWriter = xpsdw;
+ xpsdw.WritingCompleted +=
+ new WritingCompletedEventHandler(AsyncCompleted);
+ xpsdw.WritingProgressChanged +=
+ new WritingProgressChangedEventHandler(AsyncProgress);
+
+ DocumentPaginator paginator = fixdoc.DocumentPaginator;
+ try
+ {
+ if (print_all)
+ {
+ m_first_page = 1;
+ m_num_pages = paginator.PageCount;
+ xpsdw.Write(paginator, Ticket);
+ }
+ else
+ {
+ /* Create an override paginator to pick only the pages we want */
+ GSDocumentPaginator gspaginator =
+ new GSDocumentPaginator(paginator, from, to);
+ m_first_page = from;
+ m_num_pages = paginator.PageCount;
+ xpsdw.Write(gspaginator, Ticket);
+ }
+ }
+ catch (Exception)
+ {
+ /* Something went wrong with this particular print driver
+ * simply notify the user and clean up everything */
+ gsPrintEventArgs info = new gsPrintEventArgs(PrintStatus_t.PRINT_ERROR,
+ false, 0, this.m_first_page, this.m_num_pages, this.m_filename);
+ PrintUpdate(this, info);
+ return;
+ }
+ }
+ public void CancelAsync()
+ {
+ /* ick. This does not work in windows 8. causes crash. */
+ /* https://connect.microsoft.com/VisualStudio/feedback/details/778145/xpsdocumentwriter-cancelasync-cause-crash-in-win8 */
+ m_docWriter.CancelAsync();
+ }
+
+ /* Done */
+ private void AsyncCompleted(object sender, WritingCompletedEventArgs e)
+ {
+ PrintStatus_t status;
+
+ if (e.Cancelled)
+ status = PrintStatus_t.PRINT_CANCELLED;
+ else if (e.Error != null)
+ status = PrintStatus_t.PRINT_ERROR;
+ else
+ status = PrintStatus_t.PRINT_READY;
+
+ if (PrintUpdate != null)
+ {
+ gsPrintEventArgs info = new gsPrintEventArgs(status, true, 0,
+ this.m_first_page, this.m_num_pages, this.m_filename);
+ PrintUpdate(this, info);
+ }
+ m_busy = false;
+ }
+
+ /* Do this update with each fixed document (page) that is handled */
+ private void AsyncProgress(object sender, WritingProgressChangedEventArgs e)
+ {
+ if (PrintUpdate != null)
+ {
+ gsPrintEventArgs info = new gsPrintEventArgs(PrintStatus_t.PRINT_BUSY,
+ false, e.Number, this.m_first_page, this.m_num_pages,
+ this.m_filename);
+ PrintUpdate(this, info);
+ }
+ }
+#if DISABLED_FOR_NOW
+ /* Print ticket handling. You can customize for PrintTicketLevel at
+ FixedDocumentSequencePrintTicket, FixedDocumentPrintTicket,
+ or FixedPagePrintTicket. We may want to play around with this some */
+ private void PrintTicket(Object sender, WritingPrintTicketRequiredEventArgs e)
+ {
+ if (e.CurrentPrintTicketLevel ==
+ PrintTicketLevel.FixedDocumentSequencePrintTicket)
+ {
+ PrintTicket pts = new PrintTicket();
+ e.CurrentPrintTicket = pts;
+ }
+ }
+#endif
+ /* Create the document write */
+ private XpsDocumentWriter GetDocWriter(PrintQueue pq)
+ {
+ XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(pq);
+ return xpsdw;
+ }
+
+ /* Two paths for designating printing = a pain in the ass.*/
+ static PageMediaSizeName PaperKindToPageMediaSize(PaperKind paperKind)
+ {
+ switch (paperKind)
+ {
+ case PaperKind.Custom:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Letter:
+ return PageMediaSizeName.NorthAmericaLetter;
+ case PaperKind.Legal:
+ return PageMediaSizeName.NorthAmericaLegal;
+ case PaperKind.A4:
+ return PageMediaSizeName.ISOA4;
+ case PaperKind.CSheet:
+ return PageMediaSizeName.NorthAmericaCSheet;
+ case PaperKind.DSheet:
+ return PageMediaSizeName.NorthAmericaDSheet;
+ case PaperKind.ESheet:
+ return PageMediaSizeName.NorthAmericaESheet;
+ case PaperKind.LetterSmall:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Tabloid:
+ return PageMediaSizeName.NorthAmericaTabloid;
+ case PaperKind.Ledger:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Statement:
+ return PageMediaSizeName.NorthAmericaStatement;
+ case PaperKind.Executive:
+ return PageMediaSizeName.NorthAmericaExecutive;
+ case PaperKind.A3:
+ return PageMediaSizeName.ISOA3;
+ case PaperKind.A4Small:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.A5:
+ return PageMediaSizeName.ISOA5;
+ case PaperKind.B4:
+ return PageMediaSizeName.ISOB4;
+ case PaperKind.B5:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Folio:
+ return PageMediaSizeName.OtherMetricFolio;
+ case PaperKind.Quarto:
+ return PageMediaSizeName.NorthAmericaQuarto;
+ case PaperKind.Standard10x14:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Standard11x17:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Note:
+ return PageMediaSizeName.NorthAmericaNote;
+ case PaperKind.Number9Envelope:
+ return PageMediaSizeName.NorthAmericaNumber9Envelope;
+ case PaperKind.Number10Envelope:
+ return PageMediaSizeName.NorthAmericaNumber10Envelope;
+ case PaperKind.Number11Envelope:
+ return PageMediaSizeName.NorthAmericaNumber11Envelope;
+ case PaperKind.Number12Envelope:
+ return PageMediaSizeName.NorthAmericaNumber12Envelope;
+ case PaperKind.Number14Envelope:
+ return PageMediaSizeName.NorthAmericaNumber14Envelope;
+ case PaperKind.DLEnvelope:
+ return PageMediaSizeName.ISODLEnvelope;
+ case PaperKind.C5Envelope:
+ return PageMediaSizeName.ISOC5Envelope;
+ case PaperKind.C3Envelope:
+ return PageMediaSizeName.ISOC3Envelope;
+ case PaperKind.C4Envelope:
+ return PageMediaSizeName.ISOC4Envelope;
+ case PaperKind.C6Envelope:
+ return PageMediaSizeName.ISOC6Envelope;
+ case PaperKind.C65Envelope:
+ return PageMediaSizeName.ISOC6C5Envelope;
+ case PaperKind.B4Envelope:
+ return PageMediaSizeName.ISOB4Envelope;
+ case PaperKind.B5Envelope:
+ return PageMediaSizeName.ISOB5Envelope;
+ case PaperKind.B6Envelope:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.ItalyEnvelope:
+ return PageMediaSizeName.OtherMetricItalianEnvelope;
+ case PaperKind.MonarchEnvelope:
+ return PageMediaSizeName.NorthAmericaMonarchEnvelope;
+ case PaperKind.PersonalEnvelope:
+ return PageMediaSizeName.NorthAmericaPersonalEnvelope;
+ case PaperKind.USStandardFanfold:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.GermanStandardFanfold:
+ return PageMediaSizeName.NorthAmericaGermanStandardFanfold;
+ case PaperKind.GermanLegalFanfold:
+ return PageMediaSizeName.NorthAmericaGermanLegalFanfold;
+ case PaperKind.IsoB4:
+ return PageMediaSizeName.ISOB4;
+ case PaperKind.JapanesePostcard:
+ return PageMediaSizeName.JapanHagakiPostcard;
+ case PaperKind.Standard9x11:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Standard10x11:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.Standard15x11:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.InviteEnvelope:
+ return PageMediaSizeName.OtherMetricInviteEnvelope;
+ case PaperKind.LetterExtra:
+ return PageMediaSizeName.NorthAmericaLetterExtra;
+ case PaperKind.LegalExtra:
+ return PageMediaSizeName.NorthAmericaLegalExtra;
+ case PaperKind.TabloidExtra:
+ return PageMediaSizeName.NorthAmericaTabloidExtra;
+ case PaperKind.A4Extra:
+ return PageMediaSizeName.ISOA4Extra;
+ case PaperKind.LetterTransverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.A4Transverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.LetterExtraTransverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.APlus:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.BPlus:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.LetterPlus:
+ return PageMediaSizeName.NorthAmericaLetterPlus;
+ case PaperKind.A4Plus:
+ return PageMediaSizeName.OtherMetricA4Plus;
+ case PaperKind.A5Transverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.B5Transverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.A3Extra:
+ return PageMediaSizeName.ISOA3Extra;
+ case PaperKind.A5Extra:
+ return PageMediaSizeName.ISOA5Extra;
+ case PaperKind.B5Extra:
+ return PageMediaSizeName.ISOB5Extra;
+ case PaperKind.A2:
+ return PageMediaSizeName.ISOA2;
+ case PaperKind.A3Transverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.A3ExtraTransverse:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.JapaneseDoublePostcard:
+ return PageMediaSizeName.JapanDoubleHagakiPostcard;
+ case PaperKind.A6:
+ return PageMediaSizeName.ISOA6;
+ case PaperKind.JapaneseEnvelopeKakuNumber2:
+ return PageMediaSizeName.JapanKaku2Envelope;
+ case PaperKind.JapaneseEnvelopeKakuNumber3:
+ return PageMediaSizeName.JapanKaku3Envelope;
+ case PaperKind.JapaneseEnvelopeChouNumber3:
+ return PageMediaSizeName.JapanChou3Envelope;
+ case PaperKind.JapaneseEnvelopeChouNumber4:
+ return PageMediaSizeName.JapanChou4Envelope;
+ case PaperKind.LetterRotated:
+ return PageMediaSizeName.NorthAmericaLetterRotated;
+ case PaperKind.A3Rotated:
+ return PageMediaSizeName.ISOA3Rotated;
+ case PaperKind.A4Rotated:
+ return PageMediaSizeName.ISOA4Rotated;
+ case PaperKind.A5Rotated:
+ return PageMediaSizeName.ISOA5Rotated;
+ case PaperKind.B4JisRotated:
+ return PageMediaSizeName.JISB4Rotated;
+ case PaperKind.B5JisRotated:
+ return PageMediaSizeName.JISB5Rotated;
+ case PaperKind.JapanesePostcardRotated:
+ return PageMediaSizeName.JapanHagakiPostcardRotated;
+ case PaperKind.JapaneseDoublePostcardRotated:
+ return PageMediaSizeName.JapanHagakiPostcardRotated;
+ case PaperKind.A6Rotated:
+ return PageMediaSizeName.ISOA6Rotated;
+ case PaperKind.JapaneseEnvelopeKakuNumber2Rotated:
+ return PageMediaSizeName.JapanKaku2EnvelopeRotated;
+ case PaperKind.JapaneseEnvelopeKakuNumber3Rotated:
+ return PageMediaSizeName.JapanKaku3EnvelopeRotated;
+ case PaperKind.JapaneseEnvelopeChouNumber3Rotated:
+ return PageMediaSizeName.JapanChou3EnvelopeRotated;
+ case PaperKind.JapaneseEnvelopeChouNumber4Rotated:
+ return PageMediaSizeName.JapanChou4EnvelopeRotated;
+ case PaperKind.B6Jis:
+ return PageMediaSizeName.JISB6;
+ case PaperKind.B6JisRotated:
+ return PageMediaSizeName.JISB6Rotated;
+ case PaperKind.Standard12x11:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.JapaneseEnvelopeYouNumber4:
+ return PageMediaSizeName.JapanYou4Envelope;
+ case PaperKind.JapaneseEnvelopeYouNumber4Rotated:
+ return PageMediaSizeName.JapanYou4EnvelopeRotated;
+ case PaperKind.Prc16K:
+ return PageMediaSizeName.PRC16K;
+ case PaperKind.Prc32K:
+ return PageMediaSizeName.PRC32K;
+ case PaperKind.Prc32KBig:
+ return PageMediaSizeName.PRC32KBig;
+ case PaperKind.PrcEnvelopeNumber1:
+ return PageMediaSizeName.PRC1Envelope;
+ case PaperKind.PrcEnvelopeNumber2:
+ return PageMediaSizeName.PRC2Envelope;
+ case PaperKind.PrcEnvelopeNumber3:
+ return PageMediaSizeName.PRC3Envelope;
+ case PaperKind.PrcEnvelopeNumber4:
+ return PageMediaSizeName.PRC4Envelope;
+ case PaperKind.PrcEnvelopeNumber5:
+ return PageMediaSizeName.PRC5Envelope;
+ case PaperKind.PrcEnvelopeNumber6:
+ return PageMediaSizeName.PRC6Envelope;
+ case PaperKind.PrcEnvelopeNumber7:
+ return PageMediaSizeName.PRC7Envelope;
+ case PaperKind.PrcEnvelopeNumber8:
+ return PageMediaSizeName.PRC8Envelope;
+ case PaperKind.PrcEnvelopeNumber9:
+ return PageMediaSizeName.PRC9Envelope;
+ case PaperKind.PrcEnvelopeNumber10:
+ return PageMediaSizeName.PRC10Envelope;
+ case PaperKind.Prc16KRotated:
+ return PageMediaSizeName.PRC16KRotated;
+ case PaperKind.Prc32KRotated:
+ return PageMediaSizeName.PRC32KRotated;
+ case PaperKind.Prc32KBigRotated:
+ return PageMediaSizeName.Unknown;
+ case PaperKind.PrcEnvelopeNumber1Rotated:
+ return PageMediaSizeName.PRC1EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber2Rotated:
+ return PageMediaSizeName.PRC2EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber3Rotated:
+ return PageMediaSizeName.PRC3EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber4Rotated:
+ return PageMediaSizeName.PRC4EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber5Rotated:
+ return PageMediaSizeName.PRC5EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber6Rotated:
+ return PageMediaSizeName.PRC6EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber7Rotated:
+ return PageMediaSizeName.PRC7EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber8Rotated:
+ return PageMediaSizeName.PRC8EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber9Rotated:
+ return PageMediaSizeName.PRC9EnvelopeRotated;
+ case PaperKind.PrcEnvelopeNumber10Rotated:
+ return PageMediaSizeName.PRC10EnvelopeRotated;
+ default:
+ throw new ArgumentOutOfRangeException("paperKind");
+ }
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/ghostnet_simple_viewer.csproj b/demos/csharp/windows/ghostnet_wpf_example/ghostnet_simple_viewer.csproj
new file mode 100644
index 00000000..895bff82
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/ghostnet_simple_viewer.csproj
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{8BA5EDEE-8C5F-46A1-8471-EC234737AE7B}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <RootNamespace>ghostnet_wpf_example</RootNamespace>
+ <AssemblyName>ghostnet_wpf_example</AssemblyName>
+ <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x64\Debug\</OutputPath>
+ <DefineConstants>TRACE;DEBUG;WIN64;GHOSTPDL;WPF</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <LangVersion>7.3</LangVersion>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ <OutputPath>bin\x64\Release\</OutputPath>
+ <DefineConstants>TRACE;WIN64</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <LangVersion>7.3</LangVersion>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x86\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x86</PlatformTarget>
+ <LangVersion>7.3</LangVersion>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+ <OutputPath>bin\x86\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>x86</PlatformTarget>
+ <LangVersion>7.3</LangVersion>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="ReachFramework" />
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Printing" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <ApplicationDefinition Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </ApplicationDefinition>
+ <Compile Include="..\..\api\ghostapi.cs">
+ <Link>ghostapi.cs</Link>
+ </Compile>
+ <Compile Include="..\..\api\ghostnet.cs">
+ <Link>ghostnet.cs</Link>
+ </Compile>
+ <Compile Include="About.xaml.cs">
+ <DependentUpon>About.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="DocPage.cs" />
+ <Compile Include="gsIO.cs" />
+ <Compile Include="gsOutput.xaml.cs">
+ <DependentUpon>gsOutput.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="MainPrint.cs" />
+ <Compile Include="MainRender.cs" />
+ <Compile Include="MainThumbRendering.cs" />
+ <Compile Include="MainZoom.cs" />
+ <Compile Include="PrintControl.xaml.cs">
+ <DependentUpon>PrintControl.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="TempFile.cs" />
+ <Compile Include="XPSprint.cs" />
+ <Page Include="About.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Page Include="gsOutput.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Page Include="MainWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="MainWindow.xaml.cs">
+ <DependentUpon>MainWindow.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Page Include="PrintControl.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>if $(ConfigurationName) == Debug (
+ if exist $(SolutionDir)..\..\..\debugbin\gsdll64.dll copy $(SolutionDir)..\..\..\debugbin\gsdll64.dll $(ProjectDir)$(OutDir)
+ if exist $(SolutionDir)..\..\..\debugbin\gpdldll64.dll copy $(SolutionDir)..\..\..\debugbin\gpdldll64.dll $(ProjectDir)$(OutDir)
+ if exist $(SolutionDir)..\..\..\debugbin\gsdll32.dll copy $(SolutionDir)..\..\..\debugbin\gsdll32.dll $(ProjectDir)$(OutDir)
+ if exist $(SolutionDir)..\..\..\debugbin\gpdldll32.dll copy $(SolutionDir)..\..\..\debugbin\gpdldll32.dll $(ProjectDir)$(OutDir)
+ )
+
+if $(ConfigurationName) == Release (
+ if exist $(SolutionDir)..\..\..\bin\gsdll64.dll copy $(SolutionDir)..\..\..\bin\gsdll64.dll $(ProjectDir)$(OutDir)
+ if exist $(SolutionDir)..\..\..\bin\gpdldll64.dll copy $(SolutionDir)..\..\..\bin\gpdldll64.dll $(ProjectDir)$(OutDir)
+ if exist $(SolutionDir)..\..\..\bin\gsdll32.dll copy $(SolutionDir)..\..\..\bin\gsdll32.dll $(ProjectDir)$(OutDir)
+ if exist $(SolutionDir)..\..\..\bin\gpdldll32.dll copy $(SolutionDir)..\..\..\bin\gpdldll32.dll $(ProjectDir)$(OutDir)
+)
+
+
+</PostBuildEvent>
+ </PropertyGroup>
+</Project> \ No newline at end of file
diff --git a/demos/csharp/windows/ghostnet_wpf_example/gsIO.cs b/demos/csharp/windows/ghostnet_wpf_example/gsIO.cs
new file mode 100644
index 00000000..0b2d0fd8
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/gsIO.cs
@@ -0,0 +1,29 @@
+using System;
+using System.ComponentModel;
+
+namespace ghostnet_wpf_example
+{
+ class gsIO : INotifyPropertyChanged
+ {
+ public String gsIOString
+ {
+ get;
+ set;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public void PageRefresh()
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs("gsIOString"));
+ }
+ }
+
+ public gsIO()
+ {
+ this.gsIOString = "";
+ }
+ }
+}
diff --git a/demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml b/demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml
new file mode 100644
index 00000000..cb4261ba
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml
@@ -0,0 +1,28 @@
+<Window x:Class="ghostnet_wpf_example.gsOutput"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ Title="Ghostscript Messages" Height="500" Width="500"
+ FontFamily="Segou UI" FontSize="12" >
+
+ <DockPanel LastChildFill="True">
+ <Grid DockPanel.Dock="Bottom" Visibility="Visible" Background="WhiteSmoke" >
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="133*" />
+ <ColumnDefinition Width="43*"/>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <Button Grid.Row="0" Grid.Column="2" Width="50" Height="20" Click="ClearContents" Margin="5,0,15,0">
+ <TextBlock>Clear</TextBlock>
+ </Button>
+ <Button Grid.Row="0" Grid.Column="3" Width="50" Height="20" Click="HideWindow" Margin="5,0,15,0">
+ <TextBlock>OK</TextBlock>
+ </Button>
+ </Grid>
+ <!-- Pages are last child fill. This goes in the center of our dock panel -->
+ <Grid HorizontalAlignment="Stretch" Background="DarkGray">
+ <TextBox x:Name="xaml_gsText" Margin="1, 1, 1, 1" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" Text="{Binding gsIOString}" IsReadOnly="True"/>
+ </Grid>
+
+ </DockPanel>
+</Window>
diff --git a/demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml.cs b/demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml.cs
new file mode 100644
index 00000000..01075cd2
--- /dev/null
+++ b/demos/csharp/windows/ghostnet_wpf_example/gsOutput.xaml.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Windows;
+
+namespace ghostnet_wpf_example
+{
+ /// <summary>
+ /// Interaction logic for gsOutput.xaml
+ /// </summary>
+ public partial class gsOutput : Window
+ {
+ gsIO m_gsIO;
+ public gsOutput()
+ {
+ InitializeComponent();
+ this.Closing += new System.ComponentModel.CancelEventHandler(FakeWindowClosing);
+ m_gsIO = new gsIO();
+ xaml_gsText.DataContext = m_gsIO;
+ }
+
+ void FakeWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ e.Cancel = true;
+ this.Hide();
+ }
+
+ private void HideWindow(object sender, RoutedEventArgs e)
+ {
+ this.Hide();
+ }
+
+ public void RealWindowClosing()
+ {
+ this.Closing -= new System.ComponentModel.CancelEventHandler(FakeWindowClosing);
+ this.Close();
+ }
+
+ public void Update(String newstring, int len)
+ {
+ m_gsIO.gsIOString += newstring.Substring(0, len);
+ m_gsIO.PageRefresh();
+ }
+
+ private void ClearContents(object sender, RoutedEventArgs e)
+ {
+ m_gsIO.gsIOString = null;
+ m_gsIO.PageRefresh();
+ }
+ }
+}
diff --git a/demos/python/README.txt b/demos/python/README.txt
new file mode 100644
index 00000000..2e5d4052
--- /dev/null
+++ b/demos/python/README.txt
@@ -0,0 +1,17 @@
+The following projects show the use of the Ghostscript API
+in a Python environment. The file gsapi.py contains the
+API methods and they have the same names and usage as described
+in the Ghostscript API documentation.
+
+If you are working in a Linux based system, _libgs in gsapi.py is specified to
+use libgs.so . If you are working on Windows, you will be using either gpddll32.dll
+or gpdldll64.dll. You will need to build the appropriate library with the ghostscript
+build process.
+
+The file examples.py demonstrates several examples using the gsapi methods.
+These include, text extraction, object dependent color conversion, distillation,
+running of multiple files with the same GhostPDL instance and feeding
+chunks of data to the interpreter.
+
+
+
diff --git a/demos/python/examples.py b/demos/python/examples.py
new file mode 100755
index 00000000..d5c8b6ad
--- /dev/null
+++ b/demos/python/examples.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Jul 21 10:05:07 2020
+Example use of gsapi for various tasks.
+
+@author: Michael Vrhel
+"""
+
+try:
+ import gsapi
+except Exception:
+ print('Failure to import gsapi. Check shared library path')
+ raise
+
+import os
+
+ghostpdl_root = os.path.abspath('%s/../../..' % __file__)
+print('ghostpdl_root=%s' % ghostpdl_root)
+
+def run_gpdl(params, path):
+ instance = gsapi.gsapi_new_instance(0)
+ gsapi.gsapi_set_arg_encoding(instance, gsapi.GS_ARG_ENCODING_UTF8)
+ gsapi.gsapi_add_control_path(instance, gsapi.GS_PERMIT_FILE_READING, path)
+ gsapi.gsapi_init_with_args(instance, params)
+ end_gpdl(instance)
+
+def init_gpdl(params):
+ instance = gsapi.gsapi_new_instance(0)
+ gsapi.gsapi_set_arg_encoding(instance, gsapi.GS_ARG_ENCODING_UTF8)
+ gsapi.gsapi_init_with_args(instance, params)
+ return instance
+
+def run_file(instance, filename):
+ exitcode = gsapi.gsapi_run_file(instance, filename, None)
+ return exitcode
+
+def end_gpdl(instance):
+ gsapi.gsapi_exit(instance)
+ gsapi.gsapi_delete_instance(instance)
+
+# run multiple files through same instance
+def multiple_files():
+
+ out_filename = 'multi_file_output_%d.png'
+
+ params =['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pngalpha',
+ '-r72', '-o', out_filename]
+ instance = init_gpdl(params)
+ run_file(instance, '%s/examples/tiger.eps' % ghostpdl_root)
+ run_file(instance, '%s/examples/snowflak.ps' % ghostpdl_root)
+ run_file(instance, '%s/examples/annots.pdf' % ghostpdl_root)
+
+ end_gpdl(instance)
+
+# Extract text from source file
+def extract_text():
+
+ in_filename = '%s/examples/alphabet.ps' % ghostpdl_root
+ out_filename = 'alphabet.txt'
+ print('Extracting text from %s to %s' % (in_filename, out_filename))
+
+ params =['gs', '-dNOPAUSE', '-dBATCH','-sDEVICE=txtwrite',
+ '-dTextFormat=3','-o', out_filename, '-f', in_filename]
+ run_gpdl(params, in_filename)
+
+# Perform different color conversions on text, graphic, and image content
+# through the use of different destination ICC profiles
+def object_dependent_color_conversion():
+
+ in_filename = '%s/examples/text_graph_image_cmyk_rgb.pdf' % ghostpdl_root
+ out_filename = 'rendered_profile.tif'
+ image_icc = '%s/toolbin/color/icc_creator/effects/cyan_output.icc' % ghostpdl_root
+ graphic_icc = '%s/toolbin/color/icc_creator/effects/magenta_output.icc' % ghostpdl_root
+ text_icc = '%s/toolbin/color/icc_creator/effects/yellow_output.icc' % ghostpdl_root
+ print('Object dependent color conversion on %s to %s' % (in_filename, out_filename))
+
+ params =['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=tiff32nc',
+ '-r72','-sImageICCProfile=' + image_icc,
+ '-sTextICCProfile=' + text_icc,
+ '-sGraphicICCProfile=' + graphic_icc,
+ '-o', out_filename, '-f', in_filename]
+
+ # Include ICC profile location to readable path
+ run_gpdl(params, '../../toolbin/color/icc_creator/effects/')
+
+# Perform different color conversions on text, graphic, and image content
+# through the use of different rendering intents
+def object_dependent_rendering_intent():
+
+ in_filename = '%s/examples/text_graph_image_cmyk_rgb.pdf' % ghostpdl_root
+ out_filename = 'rendered_intent.tif'
+ output_icc_profile = '%s/toolbin/color/src_color/cmyk_des_renderintent.icc' % ghostpdl_root
+ print('Object dependent rendering intents on %s to %s' % (in_filename, out_filename))
+
+ params =['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=tiff32nc',
+ '-r72', '-sOutputICCProfile=' + output_icc_profile,
+ '-sImageICCIntent=0', '-sTextICCIntent=1',
+ '-sGraphicICCIntent=2', '-o', out_filename,
+ '-f', in_filename]
+
+ # Include ICC profile location to readable path
+ run_gpdl(params, '../../toolbin/color/src_color/')
+
+# Distill
+def distill():
+
+ in_filename = '%s/examples/tiger.eps' % ghostpdl_root
+ out_filename = 'tiger.pdf'
+ print('Distilling %s to %s' % (in_filename, out_filename))
+
+ params =['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pdfwrite',
+ '-o', out_filename, '-f', in_filename]
+ run_gpdl(params, in_filename)
+
+# Transparency in Postscript
+def trans_ps():
+
+ in_filename = '%s/examples/transparency_example.ps' % ghostpdl_root
+ out_filename = 'transparency.png'
+ print('Rendering Transparency PS file %s to %s' % (in_filename, out_filename))
+
+ params =['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pngalpha',
+ '-dALLOWPSTRANSPARENCY', '-o', out_filename, '-f', in_filename]
+ run_gpdl(params, in_filename)
+
+# Run string to feed chunks
+def run_string():
+
+ f = None
+ size = 1024;
+ in_filename = '%s/examples/tiger.eps' % ghostpdl_root
+ out_filename = 'tiger_byte_fed.png'
+ params =['gs', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pngalpha',
+ '-o', out_filename]
+
+ instance = gsapi.gsapi_new_instance(0)
+
+ gsapi.gsapi_set_arg_encoding(instance, gsapi.GS_ARG_ENCODING_UTF8)
+ gsapi.gsapi_init_with_args(instance, params)
+
+ exitcode = gsapi.gsapi_run_string_begin(instance, 0)
+
+ with open(in_filename,"rb") as f:
+ while True:
+ data = f.read(size)
+ if not data:
+ break
+ exitcode = gsapi.gsapi_run_string_continue(instance, data, 0)
+
+ exitcode = gsapi.gsapi_run_string_end(instance, 0)
+
+ end_gpdl(instance)
+
+
+# Examples
+print('***********Text extraction***********');
+extract_text()
+print('***********Color conversion***********')
+object_dependent_color_conversion()
+print('***********Rendering intent***********')
+object_dependent_rendering_intent()
+print('***********Distillation***************')
+distill()
+print('***********Postscript with transparency********')
+trans_ps()
+print('***********Multiple files********')
+multiple_files()
+print('***********Run string********')
+run_string()
+wait = input("press enter to exit")
diff --git a/demos/python/gsapi.py b/demos/python/gsapi.py
new file mode 100755
index 00000000..ea2fa6bb
--- /dev/null
+++ b/demos/python/gsapi.py
@@ -0,0 +1,1049 @@
+#! /usr/bin/env python3
+
+'''
+Python version of the C API in psi/iapi.h, using ctypes.
+
+Overview:
+
+ All functions have the same name as the C function that they wrap.
+
+ Functions raise a GSError exception if the underlying function returned a
+ negative error code.
+
+ Functions that don't have out-params return None. Out-params are returned
+ directly (using tuples if there are more than one).
+
+ See examples.py for sample usage.
+
+Usage:
+
+ make sodebug
+ LD_LIBRARY_PATH=sodebugbin ./demos/python/gsapi.py
+
+ On Windows perform Release build (x64 or Win32).
+
+Requirements:
+
+ Should work on python-2.5+ and python-3.0+, but this might change in
+ future.
+
+Limitations as of 2020-07-21:
+
+ Only very limited testing on has been done.
+
+ Tested on Linux, OpenBSD and Windows.
+
+ Only tested with python-3.7 and 2.7.
+
+ We don't provide gsapi_add_fs() or gsapi_remove_fs().
+
+ We only provide display_callback V2, without V3's
+ display_adjust_band_height and display_rectangle_request.
+
+'''
+
+import ctypes
+import platform
+import sys
+
+
+if platform.system() in ('Linux', 'OpenBSD'):
+ _libgs = ctypes.CDLL('libgs.so')
+
+elif platform.system() == 'Windows':
+ if sys.maxsize == 2**31 - 1:
+ _libgs = ctypes.CDLL('../../bin/gpdldll32.dll')
+ elif sys.maxsize == 2**63 - 1:
+ _libgs = ctypes.CDLL('../../bin/gpdldll64.dll')
+ else:
+ raise Exception('Unrecognised sys.maxsize=0x%x' % sys.maxsize)
+
+else:
+ raise Exception('Unrecognised platform.system()=%s' % platform.system())
+
+
+class GSError(Exception):
+ '''
+ Exception type for all errors from underlying C library.
+ '''
+ def __init__(self, gs_error):
+ self.gs_error = gs_error
+ def __str__(self):
+ return 'Ghostscript exception %i: %s' % (
+ self.gs_error,
+ _gs_error_text(self.gs_error),
+ )
+
+class gsapi_revision_t:
+ def __init__(self, product, copyright, revision, revisiondate):
+ self.product = product
+ self.copyright = copyright
+ self.revision = revision
+ self.revisiondate = revisiondate
+ def __str__(self):
+ return 'product=%r copyright=%r revision=%r revisiondate=%r' % (
+ self.product,
+ self.copyright,
+ self.revision,
+ self.revisiondate,
+ )
+
+def gsapi_revision():
+ '''
+ Returns (e, r) where <r> is a gsapi_revision_t.
+ '''
+ # [unicode: we assume that underlying gsapi_revision() returns utf-8
+ # strings.]
+ _r = _gsapi_revision_t()
+ e = _libgs.gsapi_revision(ctypes.byref(_r), ctypes.sizeof(_r))
+ if e < 0:
+ raise GSError(e)
+ r = gsapi_revision_t(
+ _r.product.decode('utf-8'),
+ _r.copyright.decode('utf-8'),
+ _r.revision,
+ _r.revisiondate,
+ )
+ return r
+
+
+def gsapi_new_instance(caller_handle):
+ '''
+ Returns (e, instance).
+ '''
+ instance = ctypes.c_void_p()
+ e = _libgs.gsapi_new_instance(
+ ctypes.byref(instance),
+ ctypes.c_void_p(caller_handle),
+ )
+ if e < 0:
+ raise GSError(e)
+ return instance
+
+
+def gsapi_delete_instance(instance):
+ e = _libgs.gsapi_delete_instance(instance)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_set_stdio(instance, stdin_fn, stdout_fn, stderr_fn):
+ '''
+ stdin_fn:
+ If not None, will be called with (caller_handle, text, len_)
+ where <text> is a ctypes.LP_c_char of length <len_>.
+
+ [todo: wrap this to be easier to use from Python?]
+
+ stdout_fn and stderr_fn:
+ If not None, called with (caller_handle, text):
+ caller_handle:
+ As passed originally to gsapi_new_instance().
+ text:
+ A Python bytes object.
+ Should return the number of bytes of <text> that they handled; for
+ convenience None is converted to len(text).
+ '''
+ # [unicode: we do not do any encoding or decoding; stdin_fn should encode
+ # and stdout_fn and stderr_fn should decode. ]
+ def make_out(fn):
+ if not fn:
+ return None
+ def out(caller_handle, text, len_):
+ text2 = text[:len_] # converts from ctypes.LP_c_char to bytes.
+ ret = fn(caller_handle, text2)
+ if ret is None:
+ return len_
+ return ret
+ return _stdio_fn(out)
+ def make_in(fn):
+ if not fn:
+ return None
+ return _stdio_fn(fn)
+
+ stdout_fn2 = make_out(stdout_fn)
+ stderr_fn2 = make_out(stderr_fn)
+ stdin_fn2 = make_in(stdin_fn)
+ e = _libgs.gsapi_set_stdio(instance, stdout_fn2, stdout_fn2, stdout_fn2)
+ if e < 0:
+ raise GSError(e)
+ # Need to keep references to call-back functions.
+ global _gsapi_set_stdio_refs
+ _gsapi_set_stdio_refs = stdin_fn2, stdout_fn2, stderr_fn2
+
+
+def gsapi_set_poll(instance, poll_fn):
+ poll_fn2 = _poll_fn(poll_fn)
+ e = _libgs.gsapi_set_poll(instance, poll_fn2)
+ if e < 0:
+ raise GSError(e)
+ global _gsapi_set_poll_refs
+ _gsapi_set_poll_refs = poll_fn2
+
+
+class display_callback:
+ def __init__(self,
+ version_major = 0,
+ version_minor = 0,
+ display_open = 0,
+ display_preclose = 0,
+ display_close = 0,
+ display_presize = 0,
+ display_size = 0,
+ display_sync = 0,
+ display_page = 0,
+ display_update = 0,
+ display_memalloc = 0,
+ display_memfree = 0,
+ display_separation = 0,
+ display_adjust_band_height = 0,
+ ):
+ self.version_major = version_major
+ self.version_minor = version_minor
+ self.display_open = display_open
+ self.display_preclose = display_preclose
+ self.display_close = display_close
+ self.display_presize = display_presize
+ self.display_size = display_size
+ self.display_sync = display_sync
+ self.display_page = display_page
+ self.display_update = display_update
+ self.display_memalloc = display_memalloc
+ self.display_memfree = display_memfree
+ self.display_separation = display_separation
+ self.display_adjust_band_height = display_adjust_band_height
+
+
+def gsapi_set_display_callback(instance, callback):
+ assert isinstance(callback, display_callback)
+ callback2 = _display_callback()
+ callback2.size = ctypes.sizeof(callback2)
+ # Copy from <callback> into <callback2>.
+ for name, type_ in _display_callback._fields_:
+ if name == 'size':
+ continue
+ value = getattr(callback, name)
+ value2 = type_(value)
+ setattr(callback2, name, value2)
+
+ e = _libgs.gsapi_set_display_callback(instance, ctypes.byref(callback2))
+ if e < 0:
+ raise GSError(e)
+ # Ensure that we keep references to callbacks.
+ global _gsapi_set_display_callback_refs
+ _gsapi_set_display_callback_refs = callback2
+
+
+def gsapi_set_default_device_list(instance, list_):
+ # [unicode: we assume that underlying gsapi_set_default_device_list() is
+ # expecting list_ to be in utf-8 encoding.]
+ assert isinstance(list_, str)
+ list_2 = list_.encode('utf-8')
+ e = _libgs.gsapi_set_default_device_list(instance, list_2, len(list_))
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_get_default_device_list(instance):
+ '''
+ Returns (e, list) where <list> is a string.
+ '''
+ # [unicode: we assume underlying gsapi_get_default_device_list() returns
+ # strings encoded as latin-1.]
+ list_ = ctypes.POINTER(ctypes.c_char)()
+ len_ = ctypes.c_int()
+ e = _libgs.gsapi_get_default_device_list(
+ instance,
+ ctypes.byref(list_),
+ ctypes.byref(len_),
+ )
+ if e < 0:
+ raise GSError(e)
+ return list_[:len_.value].decode('latin-1')
+
+
+GS_ARG_ENCODING_LOCAL = 0
+GS_ARG_ENCODING_UTF8 = 1
+GS_ARG_ENCODING_UTF16LE = 2
+
+
+def gsapi_set_arg_encoding(instance, encoding):
+ assert encoding in (
+ GS_ARG_ENCODING_LOCAL,
+ GS_ARG_ENCODING_UTF8,
+ GS_ARG_ENCODING_UTF16LE,
+ )
+ e = _libgs.gsapi_set_arg_encoding(instance, encoding)
+ if e < 0:
+ raise GSError(e)
+ if encoding == GS_ARG_ENCODING_LOCAL:
+ # This is probably wrong on Windows.
+ _encoding = 'utf-8'
+ elif encoding == GS_ARG_ENCODING_UTF8:
+ _encoding = 'utf-8'
+ elif encoding == GS_ARG_ENCODING_UTF16LE:
+ _encoding = 'utf-16-le'
+
+
+def gsapi_init_with_args(instance, args):
+ # [unicode: we assume that underlying gsapi_init_with_args()
+ # expects strings in args[] to be encoded in encoding set by
+ # gsapi_set_arg_encoding().]
+
+ # Create copy of args in format expected by C.
+ argc = len(args)
+ argv = (_pchar * (argc + 1))()
+ for i, arg in enumerate(args):
+ enc_arg = arg.encode(_encoding)
+ argv[i] = ctypes.create_string_buffer(enc_arg)
+ argv[argc] = None
+
+ e = _libgs.gsapi_init_with_args(instance, argc, argv)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_run_string_begin(instance, user_errors):
+ '''
+ Returns (e, exit_code).
+ '''
+ pexit_code = ctypes.c_int()
+ e = _libgs.gsapi_run_string_begin(instance, user_errors, ctypes.byref(pexit_code))
+ if e < 0:
+ raise GSError(e)
+ return pexit_code.value
+
+
+def gsapi_run_string_continue(instance, str_, user_errors):
+ '''
+ <str_> should be either a python string or a bytes object. If the former,
+ it is converted into a bytes object using utf-8 encoding.
+
+ We don't raise exception for gs_error_NeedInput.
+
+ Returns exit_code.
+ '''
+ if isinstance(str_, str):
+ str_ = str_.encode('utf-8')
+ assert isinstance(str_, bytes)
+ pexit_code = ctypes.c_int()
+ e = _libgs.gsapi_run_string_continue(
+ instance,
+ str_,
+ len(str_),
+ user_errors,
+ ctypes.byref(pexit_code),
+ )
+ if e == gs_error_NeedInput.num:
+ # This is normal, so we don't raise.
+ pass
+ elif e < 0:
+ raise GSError(e)
+ return pexit_code.value
+
+
+def gsapi_run_string_end(instance, user_errors):
+ '''
+ Returns (e, exit_code).
+ '''
+ pexit_code = ctypes.c_int()
+ e = _libgs.gsapi_run_string_end(
+ instance,
+ user_errors,
+ ctypes.byref(pexit_code),
+ )
+ if e < 0:
+ raise GSError(e)
+ return pexit_code.value
+
+
+def gsapi_run_string_with_length(instance, str_, length, user_errors):
+ '''
+ <str_> should be either a python string or a bytes object. If the former,
+ it is converted into a bytes object using utf-8 encoding.
+
+ Returns (e, exit_code).
+ '''
+ e = gsapi_run_string(instance, str_[:length], user_errors)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_run_string(instance, str_, user_errors):
+ '''
+ <str_> should be either a python string or a bytes object. If the former,
+ it is converted into a bytes object using utf-8 encoding.
+
+ Returns (e, exit_code).
+ '''
+ if isinstance(str_, str):
+ str_ = str_.encode('utf-8')
+ assert isinstance(str_, bytes)
+ pexit_code = ctypes.c_int()
+ # We use gsapi_run_string_with_length() because str_ might contain zeros.
+ e = _libgs.gsapi_run_string_with_length(
+ instance,
+ str_,
+ len(str_),
+ user_errors,
+ ctypes.byref(pexit_code),
+ )
+ if e < 0:
+ raise GSError(e)
+ return pexit_code.value
+
+
+def gsapi_run_file(instance, filename, user_errors):
+ '''
+ Returns (e, exit_code).
+ '''
+ # [unicode: we assume that underlying gsapi_run_file() expects <filename>
+ # to be encoded in encoding set by gsapi_set_arg_encoding().]
+ pexit_code = ctypes.c_int()
+ filename2 = filename.encode(_encoding)
+ e = _libgs.gsapi_run_file(instance, filename2, user_errors, ctypes.byref(pexit_code))
+ if e < 0:
+ raise GSError(e)
+ return pexit_code.value
+
+
+def gsapi_exit(instance):
+ e = _libgs.gsapi_exit(instance)
+ if e < 0:
+ raise GSError(e)
+
+
+gs_spt_invalid = -1
+gs_spt_null = 0 # void * is NULL.
+gs_spt_bool = 1 # void * is NULL (false) or non-NULL (true).
+gs_spt_int = 2 # void * is a pointer to an int.
+gs_spt_float = 3 # void * is a float *.
+gs_spt_name = 4 # void * is a char *.
+gs_spt_string = 5 # void * is a char *.
+gs_spt_long = 6 # void * is a long *.
+gs_spt_i64 = 7 # void * is an int64_t *.
+gs_spt_size_t = 8 # void * is a size_t *.
+gs_spt_parsed = 9 # void * is a pointer to a char * to be parsed.
+gs_spt__end = 10
+
+gs_spt_more_to_come = 2**31
+
+
+def gsapi_set_param(instance, param, value, type_=None):
+ '''
+ We behave much like the underlying gsapi_set_param() C function, except
+ that we also support automatic inference of type type_ arg by looking at
+ the type of <value>.
+
+ param:
+ Name of parameter, either a bytes or a str; if str it is encoded using
+ latin-1.
+ value:
+ A bool, int, float, bytes or str. If str, it is encoded into a bytes
+ using utf-8.
+
+ If <type_> is not None, <value> must be convertible to the Python type
+ implied by <type_>:
+
+ type_ Python type(s)
+ -----------------------------------------
+ gs_spt_null [Ignored]
+ gs_spt_bool bool
+ gs_spt_int int
+ gs_spt_float float
+ gs_spt_name [Error]
+ gs_spt_string (bytes, str)
+ gs_spt_long int
+ gs_spt_i64 int
+ gs_spt_size_t int
+ gs_spt_parsed (bytes, str)
+
+ We raise an exception if <type_> is an integer type and <value> is
+ outside its range.
+ type_:
+ If None, we choose something suitable for type of <value>:
+
+ Python type of <value> type_
+ -----------------------------
+ bool gs_spt_bool
+ int gs_spt_i64
+ float gs_spt_float
+ bytes gs_spt_parsed
+ str gs_spt_parsed (encoded with utf-8)
+
+ If <value> is None, we use gs_spt_null.
+
+ Otherwise type_ must be a gs_spt_* except for gs_spt_invalid and
+ gs_spt_name (we don't allow psapi_spt_name because the underlying C
+ does not copy the string, so cannot be safely used from Python).
+ '''
+ # [unicode: we assume that underlying gsapi_set_param() expects <param> and
+ # string <value> to be encoded as latin-1.]
+
+ if isinstance(param, str):
+ param = param.encode('latin-1')
+ assert isinstance(param, bytes)
+
+ if type_ is None:
+ # Choose a gs_spt_* that matches the Python type of <value>.
+ if 0: pass
+ elif value is None:
+ type_ = gs_spt_null
+ elif isinstance(value, bool):
+ type_ = gs_spt_bool
+ elif isinstance(value, int):
+ type_ = gs_spt_i64
+ elif isinstance(value, float):
+ type_ = gs_spt_float
+ elif isinstance(value, (bytes, str)):
+ type_ = gs_spt_parsed
+ else:
+ raise Exception('Unrecognised Python type (must be bool, int, float, bytes or str): %s' % type(value))
+
+ # Make a <value2> suitable for the underlying C gsapi_set_param() function.
+ #
+ if type_ == gs_spt_null:
+ # special-case, we pass value2=None.
+ value2 = None
+ elif type_ == gs_spt_name:
+ # Unsupported.
+ raise Exception('gs_spt_name is not supported from Python')
+ elif type_ in (gs_spt_string, gs_spt_parsed):
+ # String.
+ value2 = value
+ if isinstance(value2, str):
+ value2 = value2.encode('utf-8')
+ assert isinstance(value2, bytes)
+ else:
+ # Bool/int/float.
+ type2 = None
+ if 0: pass
+ elif type_ == gs_spt_bool:
+ type2 = ctypes.c_int
+ elif type_ == gs_spt_int:
+ type2 = ctypes.c_int
+ elif type_ == gs_spt_float:
+ type2 = ctypes.c_float
+ elif type_ == gs_spt_long:
+ type2 = ctypes.c_long
+ elif type_ == gs_spt_i64:
+ type2 = ctypes.c_int64
+ elif type_ == gs_spt_size_t:
+ type2 = ctypes.c_size_t
+ else:
+ assert 0, 'unrecognised gs_spt_ value: %s' % type_
+ value2 = type2(value)
+ if type_ not in (gs_spt_float, gs_spt_bool):
+ # Check for out-of-range integers.
+ if value2.value != value:
+ raise Exception('Value %s => %s is out of range for type %s (%s)' % (
+ value, value2.value, type_, type2))
+ value2 = ctypes.byref(value2)
+
+ e = _libgs.gsapi_set_param(instance, param, value2, type_)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_get_param(instance, param, type_=None, encoding=None):
+ '''
+ Returns value of specified parameter, or None if parameter type is
+ gs_spt_null.
+
+ param:
+ Name of parameter, either a bytes or str; if a str it is encoded using
+ latin-1.
+ type:
+ A gs_spt_* constant or None. If None we try each gs_spt_* until one
+ succeeds; if none succeeds we raise the last error.
+ encoding:
+ Only affects string values. If None we return a bytes object, otherwise
+ it should be the encoding to use to decode into a string, e.g. 'utf-8'.
+ '''
+ # [unicode: we assume that underlying gsapi_get_param() expects <param> to
+ # be encoded as latin-1.]
+ #
+ param2 = param
+ if isinstance(param2, str):
+ param2 = param2.encode('latin-1')
+ assert isinstance(param2, bytes)
+
+ def _get_simple(value_type):
+ value = value_type()
+ e = _libgs.gsapi_get_param(instance, param2, ctypes.byref(value), type_)
+ if e < 0:
+ raise GSError(e)
+ return value.value
+
+ if type_ is None:
+ # Try each type until one succeeds. We raise the last error if no type
+ # works.
+ for type_ in range(0, gs_spt__end):
+ try:
+ ret = gsapi_get_param(instance, param2, type_, encoding)
+ return ret
+ except GSError as e:
+ last_error = e
+ raise last_error
+
+ elif type_ == gs_spt_null:
+ e = _libgs.gsapi_get_param(instance, param2, None, type_)
+ if e < 0:
+ raise GSError(e)
+ return None
+
+ elif type_ == gs_spt_bool:
+ ret = _get_simple(ctypes.c_int)
+ return ret != 0
+ elif type_ == gs_spt_int:
+ return _get_simple(ctypes.c_int)
+ elif type_ == gs_spt_float:
+ return _get_simple(ctypes.c_float)
+ elif type_ == gs_spt_long:
+ return _get_simple(ctypes.c_long)
+ elif type_ == gs_spt_i64:
+ return _get_simple(ctypes.c_int64)
+ elif type_ == gs_spt_size_t:
+ return _get_simple(ctypes.c_size_t)
+
+ elif type_ in (gs_spt_name, gs_spt_string, gs_spt_parsed):
+ # Value is a string, so get required buffer size.
+ e = _libgs.gsapi_get_param(instance, param2, None, type_)
+ if e < 0:
+ raise GSError(e)
+ value = ctypes.create_string_buffer(e)
+ e = _libgs.gsapi_get_param(instance, param2, ctypes.byref(value), type_)
+ if e < 0:
+ raise GSError(e)
+ ret = value.value
+ if encoding:
+ ret = ret.decode(encoding)
+ return ret
+
+ else:
+ raise Exception('Unrecognised type_=%s' % type_)
+
+
+def gsapi_enumerate_params(instance):
+ '''
+ Yields (key, value) for each param. <key> is decoded as latin-1.
+ '''
+ # [unicode: we assume that param names are encoded as latin-1.]
+ iterator = ctypes.c_void_p()
+ key = ctypes.c_char_p()
+ type_ = ctypes.c_int()
+ while 1:
+ e = _libgs.gsapi_enumerate_params(
+ instance,
+ ctypes.byref(iterator),
+ ctypes.byref(key),
+ ctypes.byref(type_),
+ )
+ if e == 1:
+ break
+ if e:
+ raise GSError(e)
+ yield key.value.decode('latin-1'), type_.value
+
+
+GS_PERMIT_FILE_READING = 0
+GS_PERMIT_FILE_WRITING = 1
+GS_PERMIT_FILE_CONTROL = 2
+
+
+def gsapi_add_control_path(instance, type_, path):
+ # [unicode: we assume that underlying gsapi_add_control_path() expects
+ # <path> to be encoded in encoding set by gsapi_set_arg_encoding().]
+ path2 = path.encode(_encoding)
+ e = _libgs.gsapi_add_control_path(instance, type_, path2)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_remove_control_path(instance, type_, path):
+ # [unicode: we assume that underlying gsapi_remove_control_path() expects
+ # <path> to be encoded in encoding set by gsapi_set_arg_encoding().]
+ path2 = path.encode(_encoding)
+ e = _libgs.gsapi_remove_control_path(instance, type_, path2)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_purge_control_paths(instance, type_):
+ e = _libgs.gsapi_purge_control_paths(instance, type_)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_activate_path_control(instance, enable):
+ e = _libgs.gsapi_activate_path_control(instance, enable)
+ if e < 0:
+ raise GSError(e)
+
+
+def gsapi_is_path_control_active(instance):
+ e = _libgs.gsapi_is_path_control_active(instance)
+ if e < 0:
+ raise GSError(e)
+
+
+
+# Implementation details.
+#
+
+_Error_num_to_error = dict()
+class _Error:
+ def __init__(self, num, desc):
+ self.num = num
+ self.desc = desc
+ _Error_num_to_error[self.num] = self
+
+gs_error_ok = _Error( 0, 'ok')
+gs_error_unknownerror = _Error( -1, 'unknown error')
+gs_error_dictfull = _Error( -2, 'dict full')
+gs_error_dictstackoverflow = _Error( -3, 'dict stack overflow')
+gs_error_dictstackunderflow = _Error( -4, 'dict stack underflow')
+gs_error_execstackoverflow = _Error( -5, 'exec stack overflow')
+gs_error_interrupt = _Error( -6, 'interrupt')
+gs_error_invalidaccess = _Error( -7, 'invalid access')
+gs_error_invalidexit = _Error( -8, 'invalid exit')
+gs_error_invalidfileaccess = _Error( -9, 'invalid fileaccess')
+gs_error_invalidfont = _Error( -10, 'invalid font')
+gs_error_invalidrestore = _Error( -11, 'invalid restore')
+gs_error_ioerror = _Error( -12, 'ioerror')
+gs_error_limitcheck = _Error( -13, 'limit check')
+gs_error_nocurrentpoint = _Error( -14, 'no current point')
+gs_error_rangecheck = _Error( -15, 'range check')
+gs_error_stackoverflow = _Error( -16, 'stack overflow')
+gs_error_stackunderflow = _Error( -17, 'stack underflow')
+gs_error_syntaxerror = _Error( -18, 'syntax error')
+gs_error_timeout = _Error( -19, 'timeout')
+gs_error_typecheck = _Error( -20, 'type check')
+gs_error_undefined = _Error( -21, 'undefined')
+gs_error_undefinedfilename = _Error( -22, 'undefined filename')
+gs_error_undefinedresult = _Error( -23, 'undefined result')
+gs_error_unmatchedmark = _Error( -24, 'unmatched mark')
+gs_error_VMerror = _Error( -25, 'VMerror')
+
+gs_error_configurationerror = _Error( -26, 'configuration error')
+gs_error_undefinedresource = _Error( -27, 'undefined resource')
+gs_error_unregistered = _Error( -28, 'unregistered')
+gs_error_invalidcontext = _Error( -29, 'invalid context')
+gs_error_invalidid = _Error( -30, 'invalid id')
+
+gs_error_hit_detected = _Error( -99, 'hit detected')
+gs_error_Fatal = _Error(-100, 'Fatal')
+gs_error_Quit = _Error(-101, 'Quit')
+gs_error_InterpreterExit = _Error(-102, 'Interpreter Exit')
+gs_error_Remap_Color = _Error(-103, 'Remap Color')
+gs_error_ExecStackUnderflow = _Error(-104, 'Exec Stack Underflow')
+gs_error_VMreclaim = _Error(-105, 'VM reclaim')
+gs_error_NeedInput = _Error(-106, 'Need Input')
+gs_error_NeedFile = _Error(-107, 'Need File')
+gs_error_Info = _Error(-110, 'Info')
+gs_error_handled = _Error(-111, 'handled')
+
+def _gs_error_text(gs_error):
+ '''
+ Returns text description of <gs_error>. See base/gserrors.h.
+ '''
+ e = _Error_num_to_error.get(gs_error)
+ if e:
+ return e.desc
+ return 'no error'
+
+
+# The encoding that we use when passing strings to the underlying gsapi_*() C
+# functions. Changed by gsapi_set_arg_encoding().
+#
+# This default is probably incorrect on Windows.
+#
+_encoding = 'utf-8'
+
+class _gsapi_revision_t(ctypes.Structure):
+ _fields_ = [
+ ('product', ctypes.c_char_p),
+ ('copyright', ctypes.c_char_p),
+ ('revision', ctypes.c_long),
+ ('revisiondate', ctypes.c_long),
+ ]
+
+
+_stdio_fn = ctypes.CFUNCTYPE(
+ ctypes.c_int, # return
+ ctypes.c_void_p, # caller_handle
+ ctypes.POINTER(ctypes.c_char), # str
+ ctypes.c_int, # len
+ )
+
+_gsapi_set_stdio_refs = None
+
+
+# ctypes representation of int (*poll_fn)(void* caller_handle).
+#
+_poll_fn = ctypes.CFUNCTYPE(
+ ctypes.c_int, # return
+ ctypes.c_void_p, # caller_handle
+ )
+
+_gsapi_set_poll_refs = None
+
+
+# ctypes representation of display_callback.
+#
+class _display_callback(ctypes.Structure):
+ _fields_ = [
+ ('size', ctypes.c_int),
+ ('version_major', ctypes.c_int),
+ ('version_minor', ctypes.c_int),
+ ('display_open',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ )),
+ ('display_preclose',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ )),
+ ('display_close',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ )),
+ ('display_presize',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_int, # width
+ ctypes.c_int, # height
+ ctypes.c_int, # raster
+ ctypes.c_uint, # format
+ )),
+ ('display_size',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_int, # width
+ ctypes.c_int, # height
+ ctypes.c_int, # raster
+ ctypes.c_uint, # format
+ ctypes.c_char_p, # pimage
+ )),
+ ('display_sync',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ )),
+ ('display_page',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_int, # copies
+ ctypes.c_int, # flush
+ )),
+ ('display_update',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_int, # x
+ ctypes.c_int, # y
+ ctypes.c_int, # w
+ ctypes.c_int, # h
+ )),
+ ('display_memalloc',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_ulong, # size
+ )),
+ ('display_memfree',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_void_p, # mem
+ )),
+ ('display_separation',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p, # handle
+ ctypes.c_void_p, # device
+ ctypes.c_int, # component
+ ctypes.c_char_p, # component_name
+ ctypes.c_ushort, # c
+ ctypes.c_ushort, # m
+ ctypes.c_ushort, # y
+ ctypes.c_ushort, # k
+ )),
+ ]
+
+
+_libgs.gsapi_set_display_callback.argtypes = (
+ ctypes.c_void_p, # instance
+ ctypes.POINTER(_display_callback), # callback
+ )
+
+
+_gsapi_set_display_callback_refs = None
+
+
+# See:
+#
+# https://stackoverflow.com/questions/58598012/ctypes-errors-with-argv
+#
+_pchar = ctypes.POINTER(ctypes.c_char)
+_ppchar = ctypes.POINTER(_pchar)
+
+_libgs.gsapi_init_with_args.argtypes = (
+ ctypes.c_void_p, # instance
+ ctypes.c_int, # argc
+ _ppchar, # argv
+ )
+
+
+if 0:
+ # Not implemented yet:
+ # gsapi_add_fs()
+ # gsapi_remove_fs()
+ #
+ class gsapi_fs_t(ctypes.Structure):
+ _fields_ = [
+ ('open_file',
+ ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_pvoid, # const gs_memory_t *mem
+ ctypes.c_pvoid, # secret
+ ctypes.c_char_p, # fname
+ ctypes.c_char_p, # mode
+ )),
+ ]
+
+
+
+if __name__ == '__main__':
+
+ # test
+ #
+
+ print('Running some very simple and incomplete tests...')
+
+ print('libgs: %s' % _libgs)
+
+ revision = gsapi_revision()
+ print('libgs.gsapi_revision() ok: %s' % revision)
+
+ instance = gsapi_new_instance(1)
+ print('gsapi_new_instance() ok: %s' % instance)
+
+ gsapi_set_arg_encoding(instance, GS_ARG_ENCODING_UTF8)
+ print('gsapi_set_arg_encoding() ok.')
+
+ def stdout_fn(caller_handle, bytes_):
+ sys.stdout.write(bytes_.decode('latin-1'))
+ gsapi_set_stdio(instance, None, stdout_fn, None)
+ print('gsapi_set_stdio() ok.')
+
+ d = display_callback()
+ gsapi_set_display_callback(instance, d)
+ print('gsapi_set_display_callback() ok.')
+
+ gsapi_set_default_device_list(instance, 'bmp256 bmp32b bmpgray bmpmono bmpsep1 bmpsep8 ccr cdeskjet cdj1600 cdj500')
+ print('gsapi_set_default_device_list() ok.')
+
+ l = gsapi_get_default_device_list(instance)
+ print('gsapi_get_default_device_list() ok: l=%s' % l)
+
+ gsapi_init_with_args(instance, ['gs',])
+ print('gsapi_init_with_args() ok')
+
+ gsapi_set_param(instance, "foo", 100, gs_spt_i64)
+
+ try:
+ gsapi_get_param(instance, "foo", gs_spt_i64)
+ except GSError as e:
+ assert e.gs_error == gs_error_undefined.num, e.gs_error
+ else:
+ assert 0, 'expected gsapi_get_param() to fail'
+
+ # Check specifying invalid type raises exception.
+ try:
+ gsapi_get_param(instance, None, -1)
+ except Exception as e:
+ pass
+ else:
+ assert 0
+
+ # Check specifying invalid param name raises exception.
+ try:
+ gsapi_get_param(instance, -1, None)
+ except Exception as e:
+ pass
+ else:
+ assert 0
+
+ # Check we can write 64-bit value.
+ gsapi_set_param(instance, 'foo', 2**40, None)
+
+ # Check specifying out-of-range raises exception.
+ try:
+ gsapi_set_param(instance, 'foo', 2**40, gs_spt_int)
+ except Exception as e:
+ print(e)
+ assert 'out of range' in str(e)
+ else:
+ assert 0
+
+ # Check specifying out-of-range raises exception.
+ try:
+ gsapi_set_param(instance, 'foo', 2**70, None)
+ except Exception as e:
+ print(e)
+ assert 'out of range' in str(e)
+ else:
+ assert 0
+
+ print('Checking that we can set and get NumCompies and get same result back')
+ gsapi_set_param(instance, "NumCopies", 10, gs_spt_i64)
+ v = gsapi_get_param(instance, "NumCopies", gs_spt_i64)
+ assert v == 10
+
+ for value in False, True:
+ gsapi_set_param(instance, "DisablePageHandler", value)
+ v = gsapi_get_param(instance, "DisablePageHandler")
+ assert v is value
+
+ for value in 32, True, 3.14:
+ gsapi_set_param(instance, "foo", value);
+ print('gsapi_set_param() %s ok.' % value)
+ try:
+ gsapi_get_param(instance, 'foo')
+ except GSError as e:
+ pass
+ else:
+ assert 0, 'expected gsapi_get_param() to fail'
+
+ value = "hello world"
+ gsapi_set_param(instance, "foo", value, gs_spt_string)
+ print('gsapi_set_param() %s ok.' % value)
+
+ gsapi_set_param(instance, "foo", 123, gs_spt_bool)
+
+ try:
+ gsapi_set_param(instance, "foo", None, gs_spt_bool)
+ except Exception:
+ pass
+ else:
+ assert 0
+ if 0: assert gsapi_get_param(instance, "foo") is None
+
+ # Enumerate all params and print name/value.
+ print('gsapi_enumerate_params():')
+ for param, type_ in gsapi_enumerate_params(instance):
+ value = gsapi_get_param(instance, param, type_)
+ value2 = gsapi_get_param(instance, param)
+ assert value2 == value, 'value=%s value2=%s' % (value, value2)
+ value3 = gsapi_get_param(instance, param, encoding='utf-8')
+ print(' %-24s type_=%-5s %r %r' % (param, type_, value, value3))
+ assert not isinstance(value, str)
+ if isinstance(value, bytes):
+ assert isinstance(value3, str)
+
+ print('Finished')
diff --git a/demos/python/gsapiwrap.py b/demos/python/gsapiwrap.py
new file mode 100755
index 00000000..0ef0bb08
--- /dev/null
+++ b/demos/python/gsapiwrap.py
@@ -0,0 +1,699 @@
+#! /usr/bin/env python3
+
+'''
+Use Swig to build wrappers for gsapi.
+
+Example usage:
+
+ Note that we use mupdf's scripts/jlib.py, and assume that there is a mupdf
+ checkout in the parent directory of the ghostpdl checkout - see 'import
+ jlib' below.
+
+ ./toolbin/gsapiwrap.py --python -l -0 -1 -t
+ Build python wrapper for gsapi and run simple test.
+
+ ./toolbin/gsapiwrap.py --csharp -l -0 -1 -t
+ Build C# wrapper for gsapi and run simple test.
+
+Args:
+
+ -c:
+ Clean language-specific out-dir.
+
+ -l:
+ Build libgs.so (by running make).
+
+ -0:
+ Run swig to generate language-specific files.
+
+ -1:
+ Generate language wrappers by compiling/linking the files generated by
+ -0.
+
+ --csharp:
+ Generate C# wrappers (requires Mono on Linux). Should usually be first
+ param.
+
+ --python
+ Generate Python wrappers. Should usually be first param.
+
+ --swig <swig>
+ Set location of swig binary.
+
+ -t
+ Run simple test of language wrappers generated by -1.
+
+Status:
+ As of 2020-05-22:
+ Some python wrappers seem to work ok.
+
+ C# wrappers are not implemented for gsapi_set_poll() and
+ gsapi_set_stdio().
+'''
+
+import os
+import re
+import sys
+import textwrap
+
+import jlib
+
+
+def devpython_info():
+ '''
+ Use python3-config to find libpython.so and python-dev include path etc.
+ '''
+ python_configdir = jlib.system( 'python3-config --configdir', out='return')
+ libpython_so = os.path.join(
+ python_configdir.strip(),
+ f'libpython{sys.version_info[0]}.{sys.version_info[1]}.so',
+ )
+ assert os.path.isfile( libpython_so), f'cannot find libpython_so={libpython_so}'
+
+ python_includes = jlib.system( 'python3-config --includes', out='return')
+ python_includes = python_includes.strip()
+ return python_includes, libpython_so
+
+def swig_version( swig='swig'):
+ t = jlib.system( f'{swig} -version', out='return')
+ m = re.search( 'SWIG Version ([0-9]+)[.]([0-9]+)[.]([0-9]+)', t)
+ assert m
+ swig_major = int( m.group(1))
+ return swig_major
+
+
+dir_ghostpdl = os.path.abspath( f'{__file__}/../../') + '/'
+
+
+def out_dir( language):
+ if language == 'python':
+ return 'gsapiwrap/python/'
+ if language == 'csharp':
+ return 'gsapiwrap/csharp/'
+ assert 0
+
+def out_so( language):
+ '''
+ Returns name of .so that implements language-specific wrapper. I think
+ these names have to match what the language runtime requires.
+
+ For python, Swig generates a module foo.py which does 'import _foo'.
+
+ Similarly C# assumes a file called 'libfoo.so'.
+ '''
+ if language == 'python':
+ return f'{out_dir(language)}_gsapi.so'
+ if language == 'csharp':
+ return f'{out_dir(language)}libgsapi.so'
+ assert 0
+
+def lib_gs_info():
+ return f'{dir_ghostpdl}sodebugbin/libgs.so', 'make sodebug'
+ return f'{dir_ghostpdl}sobin/libgs.so', 'make so'
+
+def lib_gs():
+ '''
+ Returns name of the gs shared-library.
+ '''
+ return lib_gs_info()[0]
+
+
+def swig_i( swig, language):
+ '''
+ Returns text for a swig .i file for psi/iapi.h.
+ '''
+ swig_major = swig_version( swig)
+
+
+ # We need to redeclare or wrap some functions, e.g. to add OUTPUT
+ # annotations. We use #define, %ignore and #undef to hide the original
+ # declarations in the .h file.
+ #
+ fns_redeclare = (
+ 'gsapi_run_file',
+ 'gsapi_run_string',
+ 'gsapi_run_string_begin',
+ 'gsapi_run_string_continue',
+ 'gsapi_run_string_end',
+ 'gsapi_run_string_with_length',
+ 'gsapi_set_poll',
+ 'gsapi_set_poll_with_handle',
+ 'gsapi_set_stdio',
+ 'gsapi_set_stdio_with_handle',
+ 'gsapi_new_instance',
+ )
+
+
+ swig_i_text = textwrap.dedent(f'''
+ %module(directors="1") gsapi
+
+ %include cpointer.i
+ %pointer_functions(int, pint);
+
+ // This seems to be necessary to make csharp handle OUTPUT args.
+ //
+ %include typemaps.i
+
+ // For gsapi_init_with_args().
+ %include argcargv.i
+
+ %include cstring.i
+
+ // Include type information in python doc strings. If we have
+ // swig-4, we can propogate comments from the C api instead, which
+ // is preferred.
+ //
+ {'%feature("autodoc", "3");' if swig_major < 4 else ''}
+
+ %{{
+ #include "psi/iapi.h"
+ //#include "base/gserrors.h"
+
+ // Define wrapper functions that present a modified API that
+ // swig can cope with.
+ //
+
+ // Swig cannot handle void** out-param.
+ //
+ static void* new_instance( void* caller_handle, int* out)
+ {{
+ void* ret = NULL;
+ *out = gsapi_new_instance( &ret, caller_handle);
+ printf( "gsapi_new_instance() returned *out=%i ret=%p\\n", *out, ret);
+ fflush( stdout);
+ return ret;
+ }}
+
+ // Swig cannot handle (const char* str, int strlen) args.
+ //
+ static int run_string_continue(void *instance, const char *str, int user_errors, int *pexit_code) {{
+
+ return gsapi_run_string_continue( instance, str, strlen(str), user_errors, pexit_code);
+ }}
+ %}}
+
+ // Strip gsapi_ prefix from all generated names.
+ //
+ %rename("%(strip:[gsapi_])s") "";
+
+ // Tell Swig about gsapi_get_default_device_list()'s out-params, so
+ // it adds them to the returned object.
+ //
+ // I think the '(void) *$1' will ensure that swig code doesn't
+ // attempt to free() the returned string.
+ //
+ {'%cstring_output_allocate_size(char **list, int *listlen, (void) *$1);' if language == 'python' else ''}
+
+ // Tell swig about the (argc,argv) args in gsapi_init_with_args().
+ //
+ %apply (int ARGC, char **ARGV) {{ (int argc, char **argv) }}
+
+ // Support for wrapping various functions that take function
+ // pointer args. For each, we define a wrapper function that,
+ // instead of having function pointer args, takes a class with
+ // virtual methods. This allows swig to wrap things - python/c# etc
+ // can create a derived class that implements these virtual methods
+ // in the python/c# world.
+ //
+
+ // Wrap gsapi_set_stdio_with_handle().
+ //
+ %feature("director") set_stdio_class;
+
+ %inline {{
+ struct set_stdio_class {{
+
+ virtual int stdin_fn( char* buf, int len) = 0;
+ virtual int stdout_fn( const char* buf, int len) = 0;
+ virtual int stderr_fn( const char* buf, int len) = 0;
+
+ static int stdin_fn_wrap( void *caller_handle, char *buf, int len) {{
+ return ((set_stdio_class*) caller_handle)->stdin_fn(buf, len);
+ }}
+ static int stdout_fn_wrap( void *caller_handle, const char *buf, int len) {{
+ return ((set_stdio_class*) caller_handle)->stdout_fn(buf, len);
+ }}
+ static int stderr_fn_wrap( void *caller_handle, const char *buf, int len) {{
+ return ((set_stdio_class*) caller_handle)->stderr_fn(buf, len);
+ }}
+
+ virtual ~set_stdio_class() {{}}
+ }};
+
+ int set_stdio_with_class( void *instance, set_stdio_class* class_) {{
+ return gsapi_set_stdio_with_handle(
+ instance,
+ set_stdio_class::stdin_fn_wrap,
+ set_stdio_class::stdout_fn_wrap,
+ set_stdio_class::stderr_fn_wrap,
+ (void*) class_
+ );
+ }}
+
+
+ }}
+
+ // Wrap gsapi_set_poll().
+ //
+ %feature("director") set_poll_class;
+
+ %inline {{
+ struct set_poll_class {{
+ virtual int poll_fn() = 0;
+
+ static int poll_fn_wrap( void* caller_handle) {{
+ return ((set_poll_class*) caller_handle)->poll_fn();
+ }}
+
+ virtual ~set_poll_class() {{}}
+ }};
+
+ int set_poll_with_class( void* instance, set_poll_class* class_) {{
+ return gsapi_set_poll_with_handle(
+ instance,
+ set_poll_class::poll_fn_wrap,
+ (void*) class_
+ );
+ }}
+
+ }}
+
+ // For functions that we re-declare (typically to specify OUTPUT on
+ // one or more args), use a macro to rename the declaration in the
+ // header file and tell swig to ignore these renamed declarations.
+ //
+ ''')
+
+ for fn in fns_redeclare:
+ swig_i_text += f'#define {fn} {fn}0\n'
+
+ for fn in fns_redeclare:
+ swig_i_text += f'%ignore {fn}0;\n'
+
+ swig_i_text += textwrap.dedent(f'''
+ #include "psi/iapi.h"
+ //#include "base/gserrors.h"
+ ''')
+
+ for fn in fns_redeclare:
+ swig_i_text += f'#undef {fn}\n'
+
+
+ swig_i_text += textwrap.dedent(f'''
+ // Tell swig about our wrappers and altered declarations.
+ //
+
+ // Use swig's OUTPUT annotation for out-parameters.
+ //
+ int gsapi_run_file(void *instance, const char *file_name, int user_errors, int *OUTPUT);
+ int gsapi_run_string_begin(void *instance, int user_errors, int *OUTPUT);
+ int gsapi_run_string_end(void *instance, int user_errors, int *OUTPUT);
+ //int gsapi_run_string_with_length(void *instance, const char *str, unsigned int length, int user_errors, int *OUTPUT);
+ int gsapi_run_string(void *instance, const char *str, int user_errors, int *OUTPUT);
+
+ // Declare functions defined above that we want swig to wrap. These
+ // don't have the gsapi_ prefix, so that they can internally call
+ // the wrapped gsapi_*() function. [We've told swig to strip the
+ // gsapi_ prefix on generated functions anyway, so this doesn't
+ // afffect the generated names.]
+ //
+ static int run_string_continue(void *instance, const char *str, int user_errors, int *OUTPUT);
+ static void* new_instance(void* caller_handle, int* OUTPUT);
+ ''')
+
+ if language == 'python':
+ swig_i_text += textwrap.dedent(f'''
+
+ // Define python code that is needed to handle functions with
+ // function-pointer args.
+ //
+ %pythoncode %{{
+
+ set_stdio_g = None
+ def set_stdio( instance, stdin, stdout, stderr):
+ class derived( set_stdio_class):
+ def stdin_fn( self):
+ return stdin()
+ def stdout_fn( self, text, len):
+ return stdout( text, len)
+ def stderr_fn( self, text, len):
+ return stderr( text)
+
+ global set_stdio_g
+ set_stdio_g = derived()
+ return set_stdio_with_class( instance, set_stdio_g)
+
+ set_poll_g = None
+ def set_poll( instance, fn):
+ class derived( set_poll_class):
+ def poll_fn( self):
+ return fn()
+ global set_poll_g
+ set_poll_g = derived()
+ return set_poll_with_class( instance, set_poll_g)
+ %}}
+ ''')
+
+ return swig_i_text
+
+
+
+def run_swig( swig, language):
+ '''
+ Runs swig using a generated .i file.
+
+ The .i file modifies the gsapi API in places to allow specification of
+ out-parameters that swig understands - e.g. void** OUTPUT doesn't work.
+ '''
+ os.makedirs( out_dir(language), exist_ok=True)
+ swig_major = swig_version( swig)
+
+ swig_i_text = swig_i( swig, language)
+ swig_i_filename = f'{out_dir(language)}iapi.i'
+ jlib.update_file( swig_i_text, swig_i_filename)
+
+ out_cpp = f'{out_dir(language)}gsapi.cpp'
+
+ if language == 'python':
+ out_lang = f'{out_dir(language)}gsapi.py'
+ elif language == 'csharp':
+ out_lang = f'{out_dir(language)}gsapi.cs'
+ else:
+ assert 0
+
+ out_files = (out_cpp, out_lang)
+
+ doxygen_arg = ''
+ if swig_major >= 4 and language == 'python':
+ doxygen_arg = '-doxygen'
+
+ extra = ''
+ if language == 'csharp':
+ # Tell swig to put all generated csharp code into a single file.
+ extra = f'-outfile gsapi.cs'
+
+ command = (textwrap.dedent(f'''
+ {swig}
+ -Wall
+ -c++
+ -{language}
+ {doxygen_arg}
+ -module gsapi
+ -outdir {out_dir(language)}
+ -o {out_cpp}
+ {extra}
+ -includeall
+ -I{dir_ghostpdl}
+ -ignoremissing
+ {swig_i_filename}
+ ''').strip().replace( '\n', ' \\\n')
+ )
+
+ jlib.build(
+ (swig_i_filename,),
+ out_files,
+ command,
+ prefix=' ',
+ )
+
+
+def main( argv):
+
+ swig = 'swig'
+ language = 'python'
+
+ args = jlib.Args( sys.argv[1:])
+ while 1:
+ try:
+ arg = args.next()
+ except StopIteration:
+ break
+
+ if 0:
+ pass
+
+ elif arg == '-c':
+ jlib.system( f'rm {out_dir(language)}* || true', verbose=1, prefix=' ')
+
+ elif arg == '-l':
+ command = lib_gs_info()[1]
+ jlib.system( command, verbose=1, prefix=' ')
+
+ elif arg == '-0':
+ run_swig( swig, language)
+
+ elif arg == '-1':
+
+ libs = [lib_gs()]
+ includes = [dir_ghostpdl]
+ file_cpp = f'{out_dir(language)}gsapi.cpp'
+
+ if language == 'python':
+ python_includes, libpython_so = devpython_info()
+ libs.append( libpython_so)
+ includes.append( python_includes)
+
+ includes_text = ''
+ for i in includes:
+ includes_text += f' -I{i}'
+ command = textwrap.dedent(f'''
+ g++
+ -g
+ -Wall -W
+ -o {out_so(language)}
+ -fPIC
+ -shared
+ {includes_text}
+ {jlib.link_l_flags(libs)}
+ {file_cpp}
+ ''').strip().replace( '\n', ' \\\n')
+ jlib.build(
+ (file_cpp, lib_gs(), 'psi/iapi.h'),
+ (out_so(language),),
+ command,
+ prefix=' ',
+ )
+
+ elif arg == '--csharp':
+ language = 'csharp'
+
+ elif arg == '--python':
+ language = 'python'
+
+ elif arg == '--swig':
+ swig = args.next()
+
+ elif arg == '-t':
+
+ if language == 'python':
+ text = textwrap.dedent('''
+ #!/usr/bin/env python3
+
+ import os
+ import sys
+
+ import gsapi
+
+ gsapi.gs_error_Quit = -101
+
+ def main():
+ minst, code = gsapi.new_instance(None)
+ print( f'minst={minst} code={code}')
+
+ if 1:
+ def stdin_local(len):
+ # Not sure whether this is right.
+ return sys.stdin.read(len)
+ def stdout_local(text, l):
+ sys.stdout.write(text[:l])
+ return l
+ def stderr_local(text, l):
+ sys.stderr.write(text[:l])
+ return l
+ gsapi.set_stdio( minst, None, stdout_local, stderr_local);
+
+ if 1:
+ def poll_fn():
+ return 0
+ gsapi.set_poll(minst, poll_fn)
+ if 1:
+ s = 'display x11alpha x11 bbox'
+ gsapi.set_default_device_list( minst, s, len(s))
+
+ e, text = gsapi.get_default_device_list( minst)
+ print( f'gsapi.get_default_device_list() returned e={e} text={text!r}')
+
+ out = 'out.pdf'
+ if os.path.exists( out):
+ os.remove( out)
+ assert not os.path.exists( out)
+
+ gsargv = ['']
+ gsargv += f'-dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -sOutputFile={out} contrib/pcl3/ps/levels-test.ps'.split()
+ print( f'gsargv={gsargv}')
+ code = gsapi.set_arg_encoding(minst, gsapi.GS_ARG_ENCODING_UTF8)
+ if code == 0:
+ code = gsapi.init_with_args(minst, gsargv)
+
+ code, exit_code = gsapi.run_string_begin( minst, 0)
+ print( f'gsapi.run_string_begin() returned code={code} exit_code={exit_code}')
+ assert code == 0
+ assert exit_code == 0
+
+ gsapi.run_string
+
+ code1 = gsapi.exit(minst)
+ if (code == 0 or code == gsapi.gs_error_Quit):
+ code = code1
+ gsapi.delete_instance(minst)
+ assert os.path.isfile( out)
+ if code == 0 or code == gsapi.gs_error_Quit:
+ return 0
+ return 1
+
+ if __name__ == '__main__':
+ code = main()
+ assert code == 0
+ sys.exit( code)
+ ''')
+ text = text[1:] # skip leading \n.
+ test_py = f'{out_dir(language)}test.py'
+ jlib.update_file( text, test_py)
+ os.chmod( test_py, 0o744)
+
+ jlib.system(
+ f'LD_LIBRARY_PATH={os.path.abspath( f"{lib_gs()}/..")}'
+ f' PYTHONPATH={out_dir(language)}'
+ f' {test_py}'
+ ,
+ verbose = 1,
+ prefix=' ',
+ )
+
+ elif language == 'csharp':
+ # See: https://github.com/swig/swig/blob/master/Lib/csharp/typemaps.i
+ #
+ text = textwrap.dedent('''
+ using System;
+ public class runme {
+ static void Main() {
+ int code;
+ SWIGTYPE_p_void instance;
+ Console.WriteLine("hello world");
+ instance = gsapi.new_instance(null, out code);
+ Console.WriteLine("code is: " + code);
+ gsapi.add_control_path(instance, 0, "hello");
+ }
+ }
+ ''')
+ test_cs = f'{out_dir(language)}test.cs'
+ jlib.update_file( text, test_cs)
+ files_in = f'{out_dir(language)}gsapi.cs', test_cs
+ file_out = f'{out_dir(language)}test.exe'
+ command = f'mono-csc -debug+ -out:{file_out} {" ".join(files_in)}'
+ jlib.build( files_in, (file_out,), command, prefix=' ')
+
+ ld_library_path = f'{dir_ghostpdl}sobin'
+ jlib.system( f'LD_LIBRARY_PATH={ld_library_path} {file_out}', verbose=jlib.log, prefix=' ')
+
+ elif arg == '--tt':
+ # small swig test case.
+ os.makedirs( 'swig-tt', exist_ok=True)
+ i = textwrap.dedent(f'''
+ %include cpointer.i
+ %include cstring.i
+ %feature("autodoc", "3");
+ %cstring_output_allocate_size(char **list, int *listlen, (void) *$1);
+ %inline {{
+ static inline int gsapi_get_default_device_list(void *instance, char **list, int *listlen)
+ {{
+ *list = (char*) "hello world";
+ *listlen = 6;
+ return 0;
+ }}
+ }}
+ ''')
+ jlib.update_file(i, 'swig-tt/tt.i')
+ jlib.system('swig -c++ -python -module tt -outdir swig-tt -o swig-tt/tt.cpp swig-tt/tt.i', verbose=1)
+ p = textwrap.dedent(f'''
+ #!/usr/bin/env python3
+ import tt
+ print( tt.gsapi_get_default_device_list(None))
+ ''')[1:]
+ jlib.update_file( p, 'swig-tt/test.py')
+ python_includes, python_so = devpython_info()
+ includes = f'-I {python_includes}'
+ link_flags = jlib.link_l_flags( [python_so])
+ jlib.system( f'g++ -shared -fPIC {includes} {link_flags} -o swig-tt/_tt.so swig-tt/tt.cpp', verbose=1)
+ jlib.system( f'cd swig-tt; python3 test.py', verbose=1)
+
+ elif arg == '-T':
+ # Very simple test that we can create c# wrapper for trivial code.
+ os.makedirs( 'swig-cs-test', exist_ok=True)
+ example_cpp = textwrap.dedent('''
+ #include <time.h>
+ double My_variable = 3.0;
+
+ int fact(int n) {
+ if (n <= 1) return 1;
+ else return n*fact(n-1);
+ }
+
+ int my_mod(int x, int y) {
+ return (x%y);
+ }
+
+ char *get_time()
+ {
+ time_t ltime;
+ time(&ltime);
+ return ctime(&ltime);
+ }
+ ''')
+ jlib.update_file( example_cpp, 'swig-cs-test/example.cpp')
+
+ example_i = textwrap.dedent('''
+ %module example
+ %{
+ /* Put header files here or function declarations like below */
+ extern double My_variable;
+ extern int fact(int n);
+ extern int my_mod(int x, int y);
+ extern char *get_time();
+ %}
+
+ extern double My_variable;
+ extern int fact(int n);
+ extern int my_mod(int x, int y);
+ extern char *get_time();
+ ''')
+ jlib.update_file( example_i, 'swig-cs-test/example.i')
+
+ runme_cs = textwrap.dedent('''
+ using System;
+ public class runme {
+ static void Main() {
+ Console.WriteLine(example.My_variable);
+ Console.WriteLine(example.fact(5));
+ Console.WriteLine(example.get_time());
+ }
+ }
+ ''')
+ jlib.update_file( runme_cs, 'swig-cs-test/runme.cs')
+ jlib.system( 'g++ -g -fPIC -shared -o swig-cs-test/libfoo.so swig-cs-test/example.cpp', verbose=1)
+ jlib.system( 'swig -c++ -csharp -module example -outdir swig-cs-test -o swig-cs-test/example_wrap.cpp -outfile example.cs swig-cs-test/example.i', verbose=1)
+ jlib.system( 'g++ -g -fPIC -shared -L swig-cs-test -l foo swig-cs-test/example_wrap.cpp -o swig-cs-test/libexample.so', verbose=1)
+ jlib.system( 'cd swig-cs-test; mono-csc -out:runme.exe example.cs runme.cs', verbose=1)
+ jlib.system( 'cd swig-cs-test; LD_LIBRARY_PATH=`pwd` ./runme.exe', verbose=1)
+ jlib.system( 'ls -l swig-cs-test', verbose=1)
+
+
+ else:
+ raise Exception( f'unrecognised arg: {arg}')
+
+if __name__ == '__main__':
+ try:
+ main( sys.argv)
+ except Exception as e:
+ jlib.exception_info( out=sys.stdout)
+ sys.exit(1)
diff --git a/demos/python/jlib.py b/demos/python/jlib.py
new file mode 100644
index 00000000..20506c38
--- /dev/null
+++ b/demos/python/jlib.py
@@ -0,0 +1,1355 @@
+from __future__ import print_function
+
+import codecs
+import inspect
+import io
+import os
+import shutil
+import subprocess
+import sys
+import time
+import traceback
+import threading
+
+
+def place( frame_record):
+ '''
+ Useful debugging function - returns representation of source position of
+ caller.
+ '''
+ filename = frame_record.filename
+ line = frame_record.lineno
+ function = frame_record.function
+ ret = os.path.split( filename)[1] + ':' + str( line) + ':' + function + ':'
+ if 0:
+ tid = str(threading.currentThread())
+ ret = '[' + tid + '] ' + ret
+ return ret
+
+
+def expand_nv( text, caller):
+ '''
+ Returns <text> with special handling of {<expression>} items.
+
+ text:
+ String containing {<expression>} items.
+ caller:
+ If an int, the number of frames to step up when looking for file:line
+ information or evaluating expressions.
+
+ Otherwise should be a frame record as returned by inspect.stack()[].
+
+ <expression> is evaluated in <caller>'s context using eval(), and expanded
+ to <expression> or <expression>=<value>.
+
+ If <expression> ends with '=', this character is removed and we prefix the
+ result with <expression>=.
+
+ E.g.:
+ x = 45
+ y = 'hello'
+ expand_nv( 'foo {x} {y=}')
+ returns:
+ foo 45 y=hello
+
+ <expression> can also use ':' and '!' to control formatting, like
+ str.format().
+ '''
+ if isinstance( caller, int):
+ frame_record = inspect.stack()[ caller]
+ else:
+ frame_record = caller
+ frame = frame_record.frame
+ try:
+ def get_items():
+ '''
+ Yields (pre, item), where <item> is contents of next {...} or None,
+ and <pre> is preceding text.
+ '''
+ pos = 0
+ pre = ''
+ while 1:
+ if pos == len( text):
+ yield pre, None
+ break
+ rest = text[ pos:]
+ if rest.startswith( '{{') or rest.startswith( '}}'):
+ pre += rest[0]
+ pos += 2
+ elif text[ pos] == '{':
+ close = text.find( '}', pos)
+ if close < 0:
+ raise Exception( 'After "{" at offset %s, cannot find closing "}". text is: %r' % (
+ pos, text))
+ yield pre, text[ pos+1 : close]
+ pre = ''
+ pos = close + 1
+ else:
+ pre += text[ pos]
+ pos += 1
+
+ ret = ''
+ for pre, item in get_items():
+ ret += pre
+ nv = False
+ if item:
+ if item.endswith( '='):
+ nv = True
+ item = item[:-1]
+ expression, tail = split_first_of( item, '!:')
+ try:
+ value = eval( expression, frame.f_globals, frame.f_locals)
+ value_text = ('{0%s}' % tail).format( value)
+ except Exception as e:
+ value_text = '{??Failed to evaluate %r in context %s:%s because: %s??}' % (
+ expression,
+ frame_record.filename,
+ frame_record.lineno,
+ e,
+ )
+ if nv:
+ ret += '%s=' % expression
+ ret += value_text
+
+ return ret
+
+ finally:
+ del frame
+
+
+class LogPrefixTime:
+ def __init__( self, date=False, time_=True, elapsed=False):
+ self.date = date
+ self.time = time_
+ self.elapsed = elapsed
+ self.t0 = time.time()
+ def __call__( self):
+ ret = ''
+ if self.date:
+ ret += time.strftime( ' %F')
+ if self.time:
+ ret += time.strftime( ' %T')
+ if self.elapsed:
+ ret += ' (+%s)' % time_duration( time.time() - self.t0, s_format='%.1f')
+ if ret:
+ ret = ret.strip() + ': '
+ return ret
+
+class LogPrefixFileLine:
+ def __call__( self, caller):
+ if isinstance( caller, int):
+ caller = inspect.stack()[ caller]
+ return place( caller) + ' '
+
+class LogPrefixScopes:
+ '''
+ Internal use only.
+ '''
+ def __init__( self):
+ self.items = []
+ def __call__( self):
+ ret = ''
+ for item in self.items:
+ if callable( item):
+ item = item()
+ ret += item
+ return ret
+
+
+class LogPrefixScope:
+ '''
+ Can be used to insert scoped prefix to log output.
+ '''
+ def __init__( self, prefix):
+ g_log_prefixe_scopes.items.append( prefix)
+ def __enter__( self):
+ pass
+ def __exit__( self, exc_type, exc_value, traceback):
+ global g_log_prefix
+ g_log_prefixe_scopes.items.pop()
+
+
+g_log_delta = 0
+
+class LogDeltaScope:
+ '''
+ Can be used to temporarily change verbose level of logging.
+
+ E.g to temporarily increase logging:
+
+ with jlib.LogDeltaScope(-1):
+ ...
+ '''
+ def __init__( self, delta):
+ self.delta = delta
+ global g_log_delta
+ g_log_delta += self.delta
+ def __enter__( self):
+ pass
+ def __exit__( self, exc_type, exc_value, traceback):
+ global g_log_delta
+ g_log_delta -= self.delta
+
+# Special item that can be inserted into <g_log_prefixes> to enable
+# temporary addition of text into log prefixes.
+#
+g_log_prefixe_scopes = LogPrefixScopes()
+
+# List of items that form prefix for all output from log().
+#
+g_log_prefixes = []
+
+
+def log_text( text=None, caller=1, nv=True):
+ '''
+ Returns log text, prepending all lines with text from g_log_prefixes.
+
+ text:
+ The text to output. Each line is prepended with prefix text.
+ caller:
+ If an int, the number of frames to step up when looking for file:line
+ information or evaluating expressions.
+
+ Otherwise should be a frame record as returned by inspect.stack()[].
+ nv:
+ If true, we expand {...} in <text> using expand_nv().
+ '''
+ if isinstance( caller, int):
+ caller += 1
+ prefix = ''
+ for p in g_log_prefixes:
+ if callable( p):
+ if isinstance( p, LogPrefixFileLine):
+ p = p(caller)
+ else:
+ p = p()
+ prefix += p
+
+ if text is None:
+ return prefix
+
+ if nv:
+ text = expand_nv( text, caller)
+
+ if text.endswith( '\n'):
+ text = text[:-1]
+ lines = text.split( '\n')
+
+ text = ''
+ for line in lines:
+ text += prefix + line + '\n'
+ return text
+
+
+
+s_log_levels_cache = dict()
+s_log_levels_items = []
+
+def log_levels_find( caller):
+ if not s_log_levels_items:
+ return 0
+
+ tb = traceback.extract_stack( None, 1+caller)
+ if len(tb) == 0:
+ return 0
+ filename, line, function, text = tb[0]
+
+ key = function, filename, line,
+ delta = s_log_levels_cache.get( key)
+
+ if delta is None:
+ # Calculate and populate cache.
+ delta = 0
+ for item_function, item_filename, item_delta in s_log_levels_items:
+ if item_function and not function.startswith( item_function):
+ continue
+ if item_filename and not filename.startswith( item_filename):
+ continue
+ delta = item_delta
+ break
+
+ s_log_levels_cache[ key] = delta
+
+ return delta
+
+
+def log_levels_add( delta, filename_prefix, function_prefix):
+ '''
+ log() calls from locations with filenames starting with <filename_prefix>
+ and/or function names starting with <function_prefix> will have <delta>
+ added to their level.
+
+ Use -ve delta to increase verbosity from particular filename or function
+ prefixes.
+ '''
+ log( 'adding level: {filename_prefix=!r} {function_prefix=!r}')
+
+ # Sort in reverse order so that long functions and filename specs come
+ # first.
+ #
+ s_log_levels_items.append( (function_prefix, filename_prefix, delta))
+ s_log_levels_items.sort( reverse=True)
+
+
+def log( text, level=0, caller=1, nv=True, out=None):
+ '''
+ Writes log text, with special handling of {<expression>} items in <text>
+ similar to python3's f-strings.
+
+ text:
+ The text to output.
+ caller:
+ How many frames to step up to get caller's context when evaluating
+ file:line information and/or expressions. Or frame record as returned
+ by inspect.stack()[].
+ nv:
+ If true, we expand {...} in <text> using expand_nv().
+ out:
+ Where to send output. If None we use sys.stdout.
+
+ <expression> is evaluated in our caller's context (<n> stack frames up)
+ using eval(), and expanded to <expression> or <expression>=<value>.
+
+ If <expression> ends with '=', this character is removed and we prefix the
+ result with <expression>=.
+
+ E.g.:
+ x = 45
+ y = 'hello'
+ expand_nv( 'foo {x} {y=}')
+ returns:
+ foo 45 y=hello
+
+ <expression> can also use ':' and '!' to control formatting, like
+ str.format().
+ '''
+ if out is None:
+ out = sys.stdout
+ level += g_log_delta
+ if isinstance( caller, int):
+ caller += 1
+ level += log_levels_find( caller)
+ if level <= 0:
+ text = log_text( text, caller, nv=nv)
+ out.write( text)
+ out.flush()
+
+
+def log0( text, caller=1, nv=True, out=None):
+ '''
+ Most verbose log. Same as log().
+ '''
+ log( text, level=0, caller=caller+1, nv=nv, out=out)
+
+def log1( text, caller=1, nv=True, out=None):
+ log( text, level=1, caller=caller+1, nv=nv, out=out)
+
+def log2( text, caller=1, nv=True, out=None):
+ log( text, level=2, caller=caller+1, nv=nv, out=out)
+
+def log3( text, caller=1, nv=True, out=None):
+ log( text, level=3, caller=caller+1, nv=nv, out=out)
+
+def log4( text, caller=1, nv=True, out=None):
+ log( text, level=4, caller=caller+1, nv=nv, out=out)
+
+def log5( text, caller=1, nv=True, out=None):
+ '''
+ Least verbose log.
+ '''
+ log( text, level=5, caller=caller+1, nv=nv, out=out)
+
+def logx( text, caller=1, nv=True, out=None):
+ '''
+ Does nothing, useful when commenting out a log().
+ '''
+ pass
+
+
+def log_levels_add_env( name='JLIB_log_levels'):
+ '''
+ Added log levels encoded in an environmental variable.
+ '''
+ t = os.environ.get( name)
+ if t:
+ for ffll in t.split( ','):
+ ffl, delta = ffll.split( '=', 1)
+ delta = int( delta)
+ ffl = ffl.split( ':')
+ if 0:
+ pass
+ elif len( ffl) == 1:
+ filename = ffl
+ function = None
+ elif len( ffl) == 2:
+ filename, function = ffl
+ else:
+ assert 0
+ log_levels_add( delta, filename, function)
+
+
+def strpbrk( text, substrings):
+ '''
+ Finds first occurrence of any item in <substrings> in <text>.
+
+ Returns (pos, substring) or (len(text), None) if not found.
+ '''
+ ret_pos = len( text)
+ ret_substring = None
+ for substring in substrings:
+ pos = text.find( substring)
+ if pos >= 0 and pos < ret_pos:
+ ret_pos = pos
+ ret_substring = substring
+ return ret_pos, ret_substring
+
+
+def split_first_of( text, substrings):
+ '''
+ Returns (pre, post), where <pre> doesn't contain any item in <substrings>
+ and <post> is empty or starts with an item in <substrings>.
+ '''
+ pos, _ = strpbrk( text, substrings)
+ return text[ :pos], text[ pos:]
+
+
+
+log_levels_add_env()
+
+
+def force_line_buffering():
+ '''
+ Ensure sys.stdout and sys.stderr are line-buffered. E.g. makes things work
+ better if output is piped to a file via 'tee'.
+
+ Returns original out,err streams.
+ '''
+ stdout0 = sys.stdout
+ stderr0 = sys.stderr
+ sys.stdout = os.fdopen( os.dup( sys.stdout.fileno()), 'w', 1)
+ sys.stderr = os.fdopen( os.dup( sys.stderr.fileno()), 'w', 1)
+ return stdout0, stderr0
+
+
+def exception_info( exception=None, limit=None, out=None, prefix='', oneline=False):
+ '''
+ General replacement for traceback.* functions that print/return information
+ about exceptions. This function provides a simple way of getting the
+ functionality provided by these traceback functions:
+
+ traceback.format_exc()
+ traceback.format_exception()
+ traceback.print_exc()
+ traceback.print_exception()
+
+ Returns:
+ A string containing description of specified exception and backtrace.
+
+ Inclusion of outer frames:
+ We improve upon traceback.* in that we also include stack frames above
+ the point at which an exception was caught - frames from the top-level
+ <module> or thread creation fn to the try..catch block, which makes
+ backtraces much more useful.
+
+ Google 'sys.exc_info backtrace incomplete' for more details.
+
+ We deliberately leave a slightly curious pair of items in the backtrace
+ - the point in the try: block that ended up raising an exception, and
+ the point in the associated except: block from which we were called.
+
+ For clarity, we insert an empty frame in-between these two items, so
+ that one can easily distinguish the two parts of the backtrace.
+
+ So the backtrace looks like this:
+
+ root (e.g. <module> or /usr/lib/python2.7/threading.py:778:__bootstrap():
+ ...
+ file:line in the except: block where the exception was caught.
+ ::(): marker
+ file:line in the try: block.
+ ...
+ file:line where the exception was raised.
+
+ The items after the ::(): marker are the usual items that traceback.*
+ shows for an exception.
+
+ Also the backtraces that are generated are more concise than those provided
+ by traceback.* - just one line per frame instead of two - and filenames are
+ output relative to the current directory if applicatble. And one can easily
+ prefix all lines with a specified string, e.g. to indent the text.
+
+ Returns a string containing backtrace and exception information, and sends
+ returned string to <out> if specified.
+
+ exception:
+ None, or a (type, value, traceback) tuple, e.g. from sys.exc_info(). If
+ None, we call sys.exc_info() and use its return value.
+ limit:
+ None or maximum number of stackframes to output.
+ out:
+ None or callable taking single <text> parameter or object with a
+ 'write' member that takes a single <text> parameter.
+ prefix:
+ Used to prefix all lines of text.
+ '''
+ if exception is None:
+ exception = sys.exc_info()
+ etype, value, tb = exception
+
+ if sys.version_info[0] == 2:
+ out2 = io.BytesIO()
+ else:
+ out2 = io.StringIO()
+ try:
+
+ frames = []
+
+ # Get frames above point at which exception was caught - frames
+ # starting at top-level <module> or thread creation fn, and ending
+ # at the point in the catch: block from which we were called.
+ #
+ # These frames are not included explicitly in sys.exc_info()[2] and are
+ # also omitted by traceback.* functions, which makes for incomplete
+ # backtraces that miss much useful information.
+ #
+ for f in reversed(inspect.getouterframes(tb.tb_frame)):
+ ff = f[1], f[2], f[3], f[4][0].strip()
+ frames.append(ff)
+
+ if 1:
+ # It's useful to see boundary between upper and lower frames.
+ frames.append( None)
+
+ # Append frames from point in the try: block that caused the exception
+ # to be raised, to the point at which the exception was thrown.
+ #
+ # [One can get similar information using traceback.extract_tb(tb):
+ # for f in traceback.extract_tb(tb):
+ # frames.append(f)
+ # ]
+ for f in inspect.getinnerframes(tb):
+ ff = f[1], f[2], f[3], f[4][0].strip()
+ frames.append(ff)
+
+ cwd = os.getcwd() + os.sep
+ if oneline:
+ if etype and value:
+ # The 'exception_text' variable below will usually be assigned
+ # something like '<ExceptionType>: <ExceptionValue>', unless
+ # there was no explanatory text provided (e.g. "raise Exception()").
+ # In this case, str(value) will evaluate to ''.
+ exception_text = traceback.format_exception_only(etype, value)[0].strip()
+ filename, line, fnname, text = frames[-1]
+ if filename.startswith(cwd):
+ filename = filename[len(cwd):]
+ if not str(value):
+ # The exception doesn't have any useful explanatory text
+ # (for example, maybe it was raised by an expression like
+ # "assert <expression>" without a subsequent comma). In
+ # the absence of anything more helpful, print the code that
+ # raised the exception.
+ exception_text += ' (%s)' % text
+ line = '%s%s at %s:%s:%s()' % (prefix, exception_text, filename, line, fnname)
+ out2.write(line)
+ else:
+ out2.write( '%sBacktrace:\n' % prefix)
+ for frame in frames:
+ if frame is None:
+ out2.write( '%s ^except raise:\n' % prefix)
+ continue
+ filename, line, fnname, text = frame
+ if filename.startswith( cwd):
+ filename = filename[ len(cwd):]
+ if filename.startswith( './'):
+ filename = filename[ 2:]
+ out2.write( '%s %s:%s:%s(): %s\n' % (
+ prefix, filename, line, fnname, text))
+
+ if etype and value:
+ out2.write( '%sException:\n' % prefix)
+ lines = traceback.format_exception_only( etype, value)
+ for line in lines:
+ out2.write( '%s %s' % ( prefix, line))
+
+ text = out2.getvalue()
+
+ # Write text to <out> if specified.
+ out = getattr( out, 'write', out)
+ if callable( out):
+ out( text)
+ return text
+
+ finally:
+ # clear things to avoid cycles.
+ exception = None
+ etype = None
+ value = None
+ tb = None
+ frames = None
+
+
+def number_sep( s):
+ '''
+ Simple number formatter, adds commas in-between thousands. <s> can
+ be a number or a string. Returns a string.
+ '''
+ if not isinstance( s, str):
+ s = str( s)
+ c = s.find( '.')
+ if c==-1: c = len(s)
+ end = s.find('e')
+ if end == -1: end = s.find('E')
+ if end == -1: end = len(s)
+ ret = ''
+ for i in range( end):
+ ret += s[i]
+ if i<c-1 and (c-i-1)%3==0:
+ ret += ','
+ elif i>c and i<end-1 and (i-c)%3==0:
+ ret += ','
+ ret += s[end:]
+ return ret
+
+assert number_sep(1)=='1'
+assert number_sep(12)=='12'
+assert number_sep(123)=='123'
+assert number_sep(1234)=='1,234'
+assert number_sep(12345)=='12,345'
+assert number_sep(123456)=='123,456'
+assert number_sep(1234567)=='1,234,567'
+
+
+class Stream:
+ '''
+ Base layering abstraction for streams - abstraction for things like
+ sys.stdout to allow prefixing of all output, e.g. with a timestamp.
+ '''
+ def __init__( self, stream):
+ self.stream = stream
+ def write( self, text):
+ self.stream.write( text)
+
+class StreamPrefix:
+ '''
+ Prefixes output with a prefix, which can be a string or a callable that
+ takes no parameters and return a string.
+ '''
+ def __init__( self, stream, prefix):
+ self.stream = stream
+ self.at_start = True
+ if callable(prefix):
+ self.prefix = prefix
+ else:
+ self.prefix = lambda : prefix
+
+ def write( self, text):
+ if self.at_start:
+ text = self.prefix() + text
+ self.at_start = False
+ append_newline = False
+ if text.endswith( '\n'):
+ text = text[:-1]
+ self.at_start = True
+ append_newline = True
+ text = text.replace( '\n', '\n%s' % self.prefix())
+ if append_newline:
+ text += '\n'
+ self.stream.write( text)
+
+ def flush( self):
+ self.stream.flush()
+
+
+def debug( text):
+ if callable(text):
+ text = text()
+ print( text)
+
+debug_periodic_t0 = [0]
+def debug_periodic( text, override=0):
+ interval = 10
+ t = time.time()
+ if t - debug_periodic_t0[0] > interval or override:
+ debug_periodic_t0[0] = t
+ debug(text)
+
+
+def time_duration( seconds, verbose=False, s_format='%i'):
+ '''
+ Returns string expressing an interval.
+
+ seconds:
+ The duration in seconds
+ verbose:
+ If true, return like '4 days 1 hour 2 mins 23 secs', otherwise as
+ '4d3h2m23s'.
+ s_format:
+ If specified, use as printf-style format string for seconds.
+ '''
+ x = abs(seconds)
+ ret = ''
+ i = 0
+ for div, text in [
+ ( 60, 'sec'),
+ ( 60, 'min'),
+ ( 24, 'hour'),
+ ( None, 'day'),
+ ]:
+ force = ( x == 0 and i == 0)
+ if div:
+ remainder = x % div
+ x = int( x/div)
+ else:
+ remainder = x
+ if not verbose:
+ text = text[0]
+ if remainder or force:
+ if verbose and remainder > 1:
+ # plural.
+ text += 's'
+ if verbose:
+ text = ' %s ' % text
+ if i == 0:
+ remainder = s_format % remainder
+ ret = '%s%s%s' % ( remainder, text, ret)
+ i += 1
+ ret = ret.strip()
+ if ret == '':
+ ret = '0s'
+ if seconds < 0:
+ ret = '-%s' % ret
+ return ret
+
+assert time_duration( 303333) == '3d12h15m33s'
+assert time_duration( 303333.33, s_format='%.1f') == '3d12h15m33.3s'
+assert time_duration( 303333, verbose=True) == '3 days 12 hours 15 mins 33 secs'
+assert time_duration( 303333.33, verbose=True, s_format='%.1f') == '3 days 12 hours 15 mins 33.3 secs'
+
+assert time_duration( 0) == '0s'
+assert time_duration( 0, verbose=True) == '0 sec'
+
+
+def date_time( t=None):
+ if t is None:
+ t = time.time()
+ return time.strftime( "%F-%T", time.gmtime( t))
+
+def stream_prefix_time( stream):
+ '''
+ Returns StreamPrefix that prefixes lines with time and elapsed time.
+ '''
+ t_start = time.time()
+ def prefix_time():
+ return '%s (+%s): ' % (
+ time.strftime( '%T'),
+ time_duration( time.time() - t_start, s_format='0.1f'),
+ )
+ return StreamPrefix( stream, prefix_time)
+
+def stdout_prefix_time():
+ '''
+ Changes sys.stdout to prefix time and elapsed time; returns original
+ sys.stdout.
+ '''
+ ret = sys.stdout
+ sys.stdout = stream_prefix_time( sys.stdout)
+ return ret
+
+
+def make_stream( out):
+ '''
+ If <out> already has a .write() member, returns <out>.
+
+ Otherwise a stream-like object with a .write() method that writes to <out>.
+
+ out:
+ Where output is sent.
+ If None, output is lost.
+ Otherwise if an integer, we do: os.write( out, text)
+ Otherwise if callable, we do: out( text)
+ Otherwise we assume <out> is python stream or similar already.
+ '''
+ if getattr( out, 'write', None):
+ return out
+ class Ret:
+ def flush():
+ pass
+ ret = Ret()
+ if out is None:
+ ret.write = lambda text: None
+ elif isinstance( out, int):
+ ret.write = lambda text: os.write( out, text)
+ elif callable( out):
+ ret.write = out
+ else:
+ ret.write = lambda text: out.write( text)
+ return ret
+
+
+def system_raw(
+ command,
+ out=None,
+ shell=True,
+ encoding='latin_1',
+ errors='strict',
+ buffer_len=-1,
+ ):
+ '''
+ Runs command, writing output to <out> which can be an int fd, a python
+ stream or a Stream object.
+
+ Args:
+ command:
+ The command to run.
+ out:
+ Where output is sent.
+ If None, output is lost.
+ If -1, output is sent to stdout and stderr.
+ Otherwise if an integer, we do: os.write( out, text)
+ Otherwise if callable, we do: out( text)
+ Otherwise we assume <out> is python stream or similar, and do: out.write(text)
+ shell:
+ Whether to run command inside a shell (see subprocess.Popen).
+ encoding:
+ Sepecify the encoding used to translate the command's output
+ to characters.
+
+ Note that if <encoding> is None and we are being run by python3,
+ <out> will be passed bytes, not a string.
+
+ Note that latin_1 will never raise a UnicodeDecodeError.
+ errors:
+ How to handle encoding errors; see docs for codecs module for
+ details.
+ buffer_len:
+ The number of bytes we attempt to read at a time. If -1 we read
+ output one line at a time.
+
+ Returns:
+ subprocess's <returncode>, i.e. -N means killed by signal N, otherwise
+ the exit value (e.g. 12 if command terminated with exit(12)).
+ '''
+ if out == -1:
+ stdin = 0
+ stdout = 1
+ stderr = 2
+ else:
+ stdin = None
+ stdout = subprocess.PIPE
+ stderr = subprocess.STDOUT
+ child = subprocess.Popen(
+ command,
+ shell=shell,
+ stdin=stdin,
+ stdout=stdout,
+ stderr=stderr,
+ close_fds=True,
+ #encoding=encoding - only python-3.6+.
+ )
+
+ child_out = child.stdout
+ if encoding:
+ child_out = codecs.getreader( encoding)( child_out, errors)
+
+ out = make_stream( out)
+
+ if stdout == subprocess.PIPE:
+ if buffer_len == -1:
+ for line in child_out:
+ out.write( line)
+ else:
+ while 1:
+ text = child_out.read( buffer_len)
+ if not text:
+ break
+ out.write( text)
+ #decode( lambda : os.read( child_out.fileno(), 100), outfn, encoding)
+
+ return child.wait()
+
+if __name__ == '__main__':
+
+ if os.getenv( 'jtest_py_system_raw_test') == '1':
+ out = io.StringIO()
+ system_raw(
+ 'jtest_py_system_raw_test=2 python jlib.py',
+ sys.stdout,
+ encoding='utf-8',
+ #'latin_1',
+ errors='replace',
+ )
+ print( repr( out.getvalue()))
+
+ elif os.getenv( 'jtest_py_system_raw_test') == '2':
+ for i in range(256):
+ sys.stdout.write( chr(i))
+
+
+def system(
+ command,
+ verbose=None,
+ raise_errors=True,
+ out=None,
+ prefix=None,
+ rusage=False,
+ shell=True,
+ encoding=None,
+ errors='replace',
+ buffer_len=-1,
+ ):
+ '''
+ Runs a command like os.system() or subprocess.*, but with more flexibility.
+
+ We give control over where the command's output is sent, whether to return
+ the output and/or exit code, and whether to raise an exception if the
+ command fails.
+
+ We also support the use of /usr/bin/time to gather rusage information.
+
+ command:
+ The command to run.
+ verbose:
+ If true, we output information about the command that we run, and
+ its result.
+
+ If callable or something with a .write() method, information is
+ sent to <verbose> itself. Otherwise it is sent to <out> (without
+ applying <prefix>).
+ raise_errors:
+ If true, we raise an exception if the command fails, otherwise we
+ return the failing error code or zero.
+ out:
+ Python stream, fd, callable or Stream instance to which output is
+ sent.
+
+ If <out> is 'return', we buffer the output and return (e,
+ <output>). Note that if raise_errors is true, we only return if <e>
+ is zero.
+
+ If -1, output is sent to stdout and stderr.
+ prefix:
+ If not None, should be prefix string or callable used to prefix
+ all output. [This is for convenience to avoid the need to do
+ out=StreamPrefix(...).]
+ rusage:
+ If true, we run via /usr/bin/time and return rusage string
+ containing information on execution. <raise_errors> and
+ out='return' are ignored.
+ shell:
+ Passed to underlying subprocess.Popen() call.
+ encoding:
+ Sepecify the encoding used to translate the command's output
+ to characters. Defaults to utf-8.
+ errors:
+ How to handle encoding errors; see docs for codecs module
+ for details. Defaults to 'replace' so we never raise a
+ UnicodeDecodeError.
+ buffer_len:
+ The number of bytes we attempt to read at a time. If -1 we read
+ output one line at a time.
+
+ Returns:
+ If <rusage> is true, we return the rusage text.
+
+ Else if raise_errors is true:
+ If the command failed, we raise an exception.
+ Else if <out> is 'return' we return the text output from the command.
+ Else we return None
+
+ Else if <out> is 'return', we return (e, text) where <e> is the
+ command's exit code and <text> is the output from the command.
+
+ Else we return <e>, the command's exit code.
+ '''
+ if encoding is None:
+ if sys.version_info[0] == 2:
+ # python-2 doesn't seem to implement 'replace' properly.
+ encoding = None
+ errors = None
+ else:
+ encoding = 'utf-8'
+ errors = 'replace'
+
+ out_original = out
+ if out is None:
+ out = sys.stdout
+ elif out == 'return':
+ # Store the output ourselves so we can return it.
+ out = io.StringIO()
+ else:
+ out = make_stream( out)
+
+ if verbose:
+ if getattr( verbose, 'write', None):
+ pass
+ elif callable( verbose):
+ verbose = make_stream( verbose)
+ else:
+ verbose = out
+
+ if prefix:
+ out = StreamPrefix( out, prefix)
+
+ if verbose:
+ verbose.write( 'running: %s\n' % command)
+
+ if rusage:
+ command2 = ''
+ command2 += '/usr/bin/time -o ubt-out -f "D=%D E=%D F=%F I=%I K=%K M=%M O=%O P=%P R=%r S=%S U=%U W=%W X=%X Z=%Z c=%c e=%e k=%k p=%p r=%r s=%s t=%t w=%w x=%x C=%C"'
+ command2 += ' '
+ command2 += command
+ system_raw( command2, out, shell, encoding, errors, buffer_len=buffer_len)
+ with open('ubt-out') as f:
+ rusage_text = f.read()
+ #print 'have read rusage output: %r' % rusage_text
+ if rusage_text.startswith( 'Command '):
+ # Annoyingly, /usr/bin/time appears to write 'Command
+ # exited with ...' or 'Command terminated by ...' to the
+ # output file before the rusage info if command doesn't
+ # exit 0.
+ nl = rusage_text.find('\n')
+ rusage_text = rusage_text[ nl+1:]
+ return rusage_text
+ else:
+ e = system_raw( command, out, shell, encoding, errors, buffer_len=buffer_len)
+
+ if verbose:
+ verbose.write( '[returned e=%s]\n' % e)
+
+ if raise_errors:
+ if e:
+ raise Exception( 'command failed: %s' % command)
+ if out_original == 'return':
+ return out.getvalue()
+ return
+
+ if out_original == 'return':
+ return e, out.getvalue()
+ return e
+
+def get_gitfiles( directory, submodules=False):
+ '''
+ Returns list of all files known to git in <directory>; <directory> must be
+ somewhere within a git checkout.
+
+ Returned names are all relative to <directory>.
+
+ If .git directory, we also create <directory>/jtest-git-files. Otherwise we
+ assume a this file already exists.
+ '''
+ if os.path.isdir( '%s/.git' % directory):
+ command = 'cd ' + directory + ' && git ls-files'
+ if submodules:
+ command += ' --recurse-submodules'
+ command += ' > jtest-git-files'
+ system( command, verbose=sys.stdout)
+
+ with open( '%s/jtest-git-files' % directory, 'r') as f:
+ text = f.read()
+ ret = text.split( '\n')
+ return ret
+
+def get_git_id_raw( directory):
+ if not os.path.isdir( '%s/.git' % directory):
+ return
+ text = system(
+ f'cd {directory} && (PAGER= git show --pretty=oneline|head -n 1 && git diff)',
+ out='return',
+ )
+ return text
+
+def get_git_id( directory, allow_none=False):
+ '''
+ Returns text where first line is '<git-sha> <commit summary>' and remaining
+ lines contain output from 'git diff' in <directory>.
+
+ directory:
+ Root of git checkout.
+ allow_none:
+ If true, we return None if <directory> is not a git checkout and
+ jtest-git-id file does not exist.
+ '''
+ filename = f'{directory}/jtest-git-id'
+ text = get_git_id_raw( directory)
+ if text:
+ with open( filename, 'w') as f:
+ f.write( text)
+ elif os.path.isfile( filename):
+ with open( filename) as f:
+ text = f.read()
+ else:
+ if not allow_none:
+ raise Exception( f'Not in git checkout, and no file {filename}.')
+ text = None
+ return text
+
+class Args:
+ '''
+ Iterates over argv items. Does getopt-style splitting of args starting with
+ single '-' character.
+ '''
+ def __init__( self, argv):
+ self.argv = argv
+ self.pos = 0
+ self.pos_sub = None
+ def next( self):
+ while 1:
+ if self.pos >= len(self.argv):
+ raise StopIteration()
+ arg = self.argv[self.pos]
+ if (not self.pos_sub
+ and arg.startswith('-')
+ and not arg.startswith('--')
+ ):
+ # Start splitting current arg.
+ self.pos_sub = 1
+ if self.pos_sub and self.pos_sub >= len(arg):
+ # End of '-' sub-arg.
+ self.pos += 1
+ self.pos_sub = None
+ continue
+ if self.pos_sub:
+ # Return '-' sub-arg.
+ ret = arg[self.pos_sub]
+ self.pos_sub += 1
+ return f'-{ret}'
+ # Return normal arg.
+ self.pos += 1
+ return arg
+
+def update_file( text, filename):
+ '''
+ Writes <text> to <filename>. Does nothing if contents of <filename> are
+ already <text>.
+ '''
+ try:
+ with open( filename) as f:
+ text0 = f.read()
+ except OSError:
+ text0 = None
+ if text == text0:
+ log( 'Unchanged: ' + filename)
+ else:
+ log( 'Updating: ' + filename)
+ # Write to temp file and rename, to ensure we are atomic.
+ filename_temp = f'{filename}-jlib-temp'
+ with open( filename_temp, 'w') as f:
+ f.write( text)
+ os.rename( filename_temp, filename)
+
+
+def mtime( filename, default=0):
+ '''
+ Returns mtime of file, or <default> if error - e.g. doesn't exist.
+ '''
+ try:
+ return os.path.getmtime( filename)
+ except OSError:
+ return default
+
+def get_filenames( paths):
+ '''
+ Yields each file in <paths>, walking any directories.
+ '''
+ if isinstance( paths, str):
+ paths = (paths,)
+ for name in paths:
+ if os.path.isdir( name):
+ for dirpath, dirnames, filenames in os.walk( name):
+ for filename in filenames:
+ path = os.path.join( dirpath, filename)
+ yield path
+ else:
+ yield name
+
+def remove( path):
+ '''
+ Removes file or directory, without raising exception if it doesn't exist.
+
+ We assert-fail if the path still exists when we return, in case of
+ permission problems etc.
+ '''
+ try:
+ os.remove( path)
+ except Exception:
+ pass
+ shutil.rmtree( path, ignore_errors=1)
+ assert not os.path.exists( path)
+
+
+# Things for figuring out whether files need updating, using mtimes.
+#
+def newest( names):
+ '''
+ Returns mtime of newest file in <filenames>. Returns 0 if no file exists.
+ '''
+ assert isinstance( names, (list, tuple))
+ assert names
+ ret_t = 0
+ ret_name = None
+ for filename in get_filenames( names):
+ t = mtime( filename)
+ if t > ret_t:
+ ret_t = t
+ ret_name = filename
+ return ret_t, ret_name
+
+def oldest( names):
+ '''
+ Returns mtime of oldest file in <filenames> or 0 if no file exists.
+ '''
+ assert isinstance( names, (list, tuple))
+ assert names
+ ret_t = None
+ ret_name = None
+ for filename in get_filenames( names):
+ t = mtime( filename)
+ if ret_t is None or t < ret_t:
+ ret_t = t
+ ret_name = filename
+ if ret_t is None:
+ ret_t = 0
+ return ret_t, ret_name
+
+def update_needed( infiles, outfiles):
+ '''
+ If any file in <infiles> is newer than any file in <outfiles>, returns
+ string description. Otherwise returns None.
+ '''
+ in_tmax, in_tmax_name = newest( infiles)
+ out_tmin, out_tmin_name = oldest( outfiles)
+ if in_tmax > out_tmin:
+ text = f'{in_tmax_name} is newer than {out_tmin_name}'
+ return text
+
+def build(
+ infiles,
+ outfiles,
+ command,
+ force_rebuild=False,
+ out=None,
+ all_reasons=False,
+ verbose=True,
+ prefix=None,
+ ):
+ '''
+ Ensures that <outfiles> are up to date using enhanced makefile-like
+ determinism of dependencies.
+
+ Rebuilds <outfiles> by running <command> if we determine that any of them
+ are out of date.
+
+ infiles:
+ Names of files that are read by <command>. Can be a single filename. If
+ an item is a directory, we expand to all filenames in the directory's
+ tree.
+ outfiles:
+ Names of files that are written by <command>. Can also be a single
+ filename.
+ command:
+ Command to run.
+ force_rebuild:
+ If true, we always re-run the command.
+ out:
+ A callable, passed to jlib.system(). If None, we use jlib.log() with
+ our caller's stack record.
+ all_reasons:
+ If true we check all ways for a build being needed, even if we already
+ know a build is needed; this only affects the diagnostic that we
+ output.
+ verbose:
+ Passed to jlib.system().
+ prefix:
+ Passed to jlib.system().
+
+ We compare mtimes of <infiles> and <outfiles>, and we also detect changes
+ to the command itself.
+
+ If any of infiles are newer than any of outfiles, or <command> is
+ different to contents of commandfile '<outfile[0]>.cmd, then truncates
+ commandfile and runs <command>. If <command> succeeds we writes <command>
+ to commandfile.
+ '''
+ if isinstance( infiles, str):
+ infiles = (infiles,)
+ if isinstance( outfiles, str):
+ infiles = (outfiles,)
+
+ if not out:
+ out_frame_record = inspect.stack()[1]
+ out = lambda text: log( text, nv=0, caller=out_frame_record)
+
+ command_filename = f'{outfiles[0]}.cmd'
+
+ reasons = []
+
+ if not reasons or all_reasons:
+ if force_rebuild:
+ reasons.append( 'force_rebuild was specified')
+
+ if not reasons or all_reasons:
+ try:
+ with open( command_filename) as f:
+ command0 = f.read()
+ except Exception:
+ command0 = None
+ if command != command0:
+ if command0:
+ reasons.append( 'command has changed')
+ else:
+ reasons.append( 'no previous command')
+
+ if not reasons or all_reasons:
+ reason = update_needed( infiles, outfiles)
+ if reason:
+ reasons.append( reason)
+
+ if not reasons:
+ out( 'Already up to date: ' + ' '.join(outfiles))
+ return
+
+ if out:
+ out( 'Rebuilding because %s: %s' % (
+ ', and '.join( reasons),
+ ' '.join(outfiles),
+ ))
+
+ # Empty <command_filename) while we run the command so that if command
+ # fails but still creates target(s), then next time we will know target(s)
+ # are not up to date.
+ #
+ with open( command_filename, 'w') as f:
+ pass
+
+ system( command, out=out, verbose=verbose, prefix=prefix)
+
+ with open( command_filename, 'w') as f:
+ f.write( command)
+
+
+def link_l_flags( sos):
+ '''
+ Returns flags needed to link with items in <sos>. For each unique item we
+ use -L with parent directory, and -l with embedded name (without leading
+ 'lib' or trailing '.co').
+ '''
+ dirs = set()
+ names = []
+ if isinstance( sos, str):
+ sos = (sos,)
+ for so in sos:
+ dir_ = os.path.dirname( so)
+ name = os.path.basename( so)
+ assert name.startswith( 'lib')
+ assert name.endswith ( '.so')
+ name = name[3:-3]
+ dirs.add( dir_)
+ names.append( name)
+ ret = ''
+ # Important to use sorted() here, otherwise ordering from set() is
+ # arbitrary causing occasional spurious rebuilds by jlib.build().
+ for dir_ in sorted(dirs):
+ ret += f' -L {dir_}'
+ for name in names:
+ ret += f' -l {name}'
+ return ret