diff options
author | 2020-09-10 18:10:49 +0200 | |
---|---|---|
committer | 2020-09-11 20:06:36 +0200 | |
commit | acfc02c1747065fe450c7cfeb6f1844b62335f08 (patch) | |
tree | 5887806a2e6b99bbb0255e013a9028810e230a7f /demos | |
parent | Import Ghostscript 9.52 (diff) | |
download | ghostscript-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')
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(<ime); + return ctime(<ime); + } + ''') + 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 |