diff options
author | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
commit | 487ca4806cc046076293cf6cc5fbba0db282bac7 (patch) | |
tree | 847b412ab5158dd7bdd7ed7e5a4cc3fbca94be32 /Build/source/texk/xdvik/image-magick.c | |
parent | a3d3111bfe26b8e5f5bc6049dfb2a4ca2edc7881 (diff) |
texk 1
git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/xdvik/image-magick.c')
-rw-r--r-- | Build/source/texk/xdvik/image-magick.c | 1422 |
1 files changed, 1422 insertions, 0 deletions
diff --git a/Build/source/texk/xdvik/image-magick.c b/Build/source/texk/xdvik/image-magick.c new file mode 100644 index 00000000000..50bf201956a --- /dev/null +++ b/Build/source/texk/xdvik/image-magick.c @@ -0,0 +1,1422 @@ +/* + Imagemagick support for xdvik, written by Zhang Linbo <zlb@lsec.cc.ac.cn> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + TODO: + + 1. Specials other than 'psfile=' and 'em:graph' which include + image files. + + [ NOTE SU: I consider this more or less done now with the backtick + parsing ;-) ] + + 2. Use an XtWorkProc or similar to do some background pre-processing + of image files before they are displayed. + + 3. Replace popen() by fork and explicit pipes, and make the + use of xdvi_temp_fd safer. + + 4. PS figs are always clipped to the bbox defined in the special + string, which is not what we want when clip=false. + The problem can be partly solved by increasing the value of + the PSBW macro, or we may get the correct bbox by reading the + '%%BoundingBox' or '%%HiresBoundingBox' lines in the PS file + and set the image geometry accordingly. + + 5. The code only(?) works with true color displays. + + 6. There's also work to do to respond to window events in + load_image() and display_image() functions. +*/ +#include <ctype.h> + +#include "xdvi-config.h" +#include "xdvi.h" +#include "dvi-init.h" +#include "events.h" +#include "dvi-draw.h" +#include "util.h" +#include "special.h" +#include "image-magick.h" + +#ifdef MAGICK /* entire file */ + +#include "magick/api.h" +#if MAGICK_VER_MAJOR > 5 || MAGICK_VER_MINOR >= 4 +# include "magick/xwindow.h" /* IM >= 5.4 */ +#else +# include "magick/xwindows.h" /* IM <= 5.3 */ +#endif + +#if MAGICK_VER_MAJOR > 5 || MAGICK_VER_MINOR >= 4 /* >= 5.4 */ +# define CATCH_EXCEPTION(e) do { \ + if (e.severity != UndefinedException) CatchException(&e); } while (0) +#elif MAGICK_VER_MINOR >= 3 /* 5.3 */ +# define CATCH_EXCEPTION(e) if (image == NULL) \ + MagickWarning(e.severity, e.reason, e.description); +#else /* <= 5.2 (no check) */ +# define CATCH_EXCEPTION(e) if (image == NULL) \ + MagickWarning(e.severity, e.message, e.qualifier); +#endif + +#if 0 +#define CATCH_EXCEPTION(e) /* do nothing */ +#endif + +#ifndef False +# define False 0 +#endif + +#ifndef True +# define True 1 +#endif + +#define TWOPI (2*3.141592653589793) + +static double ROUND_tmp; +#define ROUND(x) ( (ROUND_tmp=(x)) >= 0 ? (long)(ROUND_tmp + 0.5) : \ + - (long)(- ROUND_tmp + 0.5) ) + +static void +showtime(const char *msg) +{ + if (globals.debug & DBG_PS) { + static double time0 = -1; + double time; + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) 0); + time = tv.tv_sec + (double) tv.tv_usec * 1e-6; + if (time0 < 0) + time0 = time; + fprintf(stderr, " %-40s %0.4f\n", msg, time - time0); + } +} + +#define DPI ( resource.pixels_per_inch*0.001*magnification/mane.shrinkfactor ) + +typedef struct XDviImageInfo { + Boolean clip; + int rwi, rhi, angle; + double llx, lly, urx, ury; + char filename[MaxTextExtent]; +} XDviImageInfo; + +typedef struct { + XDviImageInfo info; + XImage *ximage; /* ximage */ + unsigned long bgpixel; /* background pixel */ + int xoff, yoff; /* offsets in pixels */ +} XDviImage; + +static size_t cache_size = 0; +/* default value for cache_limit (16MB), it can be specified with the + * "-magick_cache size" option */ +static size_t cache_limit = 16 * 1024 * 1024; +static int ncached = 0; +static XDviImage *cache = NULL; + +static char *magick_tmp; +static double current_dpi = -1; + +/* Input image types */ +enum { + TYPE_UNKNOWN, /* unknown */ + TYPE_CONVERT, /* `convert ... */ +}; +static int image_type; /* type of current input image */ + +static void +cleanup(void) +{ + if (magick_tmp != NULL) + unlink(magick_tmp); +} + +static void +free_ximage(XImage * ximage) +{ + if (ximage == NULL) + return; + if (ximage->data != NULL) { + free(ximage->data); + ximage->data = NULL; + } + XDestroyImage(ximage); +} + +static void +clear_cache() +{ + XDviImage *c; + if (!ncached) + return; + for (c = cache; c < cache + ncached; c++) + free_ximage(c->ximage); + free(cache); + ncached = 0; + cache = NULL; + cache_size = 0; +} + +static void +parse_backtick(char *fn) +{ + char *p, *q, *r, c; + + if (fn[0] != '`') + return; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: parsing backtick command |%s|\n", fn); + +#ifdef __GNUC__ +#warning FIXME: add code to execute the cmd then load the output file, \ + if '-allowshell' is effective and the user requests it \ + somehow (slow but reliable). +#endif + + fn[0] = '\0'; + p = fn + 1; + /* Skip 'env' and 'VAR=value' */ + do { + while (isspace((int)*p)) + ++p; + q = p; + while (*q != '\0' && !isspace((int)*q)) + ++q; + if (*q == '\0' || q <= p) + return; + if (((r = strchr(p, '=')) == NULL || r >= q) && + (!isspace((int)*q) || strncmp(p, "env", 3))) + break; + p = q; + } while (1); + + /* Now [p ... q-1] is the command name */ + if (q - p == 7 && !memcmp(p, "convert", 7)) + image_type = TYPE_CONVERT; + else + image_type = TYPE_UNKNOWN; + p = q; + do { + while (isspace((int)*p)) + ++p; + q = p; + if (*q == '`' || *q == '\'' || *q == '"') { + c = *p; + ++q; + while (*q != '\0' && *q != c) + q++; + if (*q != '\0') + q++; + } + else { + while (*q != '\0' && !isspace((int)*q)) + q++; + } +#ifdef __GNUC__ +#warning FIXME: check for some cmdline options + break; +#endif + } while (1); + *q = '\0'; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: filename=|%s|\n", p); + + /* check for the case `kpsewhich -n ... #1` */ + if (*p == '`') { +#if 0 + /* WARNING: unsafe! */ + FILE *pipe; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: executing %s\n", p); + if (*(--q) == '`') + *q = '\0'; + if ((pipe = popen(p + 1, "r")) == NULL) + return; + fscanf(pipe, "%s", fn); + pclose(pipe); + return; +#else + if (memcmp(++p, "kpsewhich", sizeof("kpsewhich") - 1)) + return; +#ifdef __GNUC__ +#warning FIXME: better make KPSE library call here. +#endif + if (*(--q) == '`') + --q; + while (q >= p && isspace((int)*q)) + --q; + if (q < p) + return; + *(q + 1) = '\0'; + while (q >= p && !isspace((int)*q)) + --q; + p = q + 1; +#endif + } + + strcpy(fn, p); +} + +static Boolean +imagick_eps_to_jpeg(const char *fullpath, + unsigned long *columns, unsigned long *rows, + XDviImage *img, PixelPacket *bgcolor, + double xdpi, double ydpi) +{ + size_t size; +#ifdef __GNUC__ +#warning FIXME: security (check 'allowshell'?), backticks, pathname canonicalization, + /* note SU: better use fork()/exec() here, popen() is too unsafe. */ +#endif + static char *cmdline = NULL; + static size_t cmdline_size = 0; + static char *fmt = "%s -q -dBATCH -dMaxBitmap=300000000 " + "-dNOPAUSE -dNOSAFER -sDEVICE=jpeg " + "-dTextAlphaBits=4 -dGraphicsAlphaBits=4 " + "-g%ldx%ld -r%0.4fx%0.4f -sOutputFile=%s - -c quit"; + static const char strsafe[] = + "{ << /PermitFileReading [ (*) ] /PermitFileWriting [ ] /PermitFileControl [ ] " + ">> setuserparams .locksafe " + "} stopped pop\n"; + FILE *pipe; + + showtime("PS/EPS -> JPEG:"); + /* Add a border to the drawing box (PSBW=border width in bp) */ +#define PSBW 1 + *columns += ROUND(2 * PSBW / 72.0 * xdpi); + *rows += ROUND(2 * PSBW / 72.0 * ydpi); + img->xoff = ROUND(PSBW / 72.0 * xdpi); + img->yoff = -ROUND(PSBW / 72.0 * ydpi); + + size = strlen(fmt) + 25 * 2 + 10 * 2 + strlen(magick_tmp) + strlen(resource.gs_path) + 1; + if (size > cmdline_size) { + cmdline_size = size; + cmdline = xrealloc(cmdline, cmdline_size); + } + unlink(magick_tmp); + sprintf(cmdline, fmt, resource.gs_path, *columns, *rows, xdpi, ydpi, + magick_tmp); + if ((pipe = popen(cmdline, "w")) == NULL) { + fprintf(stderr, "%s: Cannot execute %s, ignoring image %s.\n", + globals.program_name, resource.gs_path, fullpath); + return False; + } + +#define ColorToPixelPacket(c) ((c*((1L<<(QuantumDepth))-1L)+32768L)/65535L) +#define UnifiedColorValue(c) ((c)/(double)((1L<<(QuantumDepth))-1L)) +#ifdef __GNUC__ +#warning FIXME: how to control roundoff error which may produce a \ + different backgroung pixel in the output image? +#endif +#if COLOR + bgcolor->red = ColorToPixelPacket(bg_current->color.r); + bgcolor->green = ColorToPixelPacket(bg_current->color.g); + bgcolor->blue = ColorToPixelPacket(bg_current->color.b); +#else + bgcolor->red = bgcolor->green = bgcolor->blue = ColorToPixelPacket(65535); +#endif + bgcolor->opacity = (1L << QuantumDepth) - 1L; + +#ifdef __GNUC__ +#warning FIXME: how to set bg color within gs (instead of filling the bbox) ? +#endif + if (resource.gs_safer) { + fprintf(pipe, strsafe); + } + /* fill PS paper with background color */ + fprintf(pipe, + "newpath -5 -5 moveto %f 0 rlineto " + "0 %f rlineto %f 0 rlineto 0 %f rlineto " + "closepath %f %f %f setrgbcolor fill\n", + img->info.urx - img->info.llx + 2 * (PSBW + 5), + img->info.ury - img->info.lly + 2 * (PSBW + 5), + -(img->info.urx - img->info.llx + 2 * (PSBW + 5)), + -(img->info.ury - img->info.lly + 2 * (PSBW + 5)), + UnifiedColorValue(bgcolor->red), + UnifiedColorValue(bgcolor->green), + UnifiedColorValue(bgcolor->blue)); + + /* shift PS coordinates */ + fprintf(pipe, "%f %f translate\n", -img->info.llx + PSBW, -img->info.lly + PSBW); + if (globals.debug & DBG_PS) + fprintf(stderr, " clip=%s\n", img->info.clip ? "yes" : "no"); + if (img->info.clip) { + fprintf(pipe, "newpath %f %f moveto %f 0 rlineto 0 %f rlineto " + "%f 0 rlineto 0 %f rlineto closepath clip\n", + img->info.llx, img->info.lly, img->info.urx - img->info.llx, + img->info.ury - img->info.lly, -(img->info.urx - img->info.llx), + -(img->info.ury - img->info.lly)); + } + fprintf(pipe, "%f %f moveto\n", img->info.llx, img->info.lly); + fprintf(pipe, "(%s) run showpage\n", fullpath); + pclose(pipe); + return True; +} + + +static Image * +rotate_image(XDviImage *img, Image *image, ExceptionInfo exception) +{ + Image *tmp_image; + unsigned long w = image->columns, h = image->rows; + double sin_a = sin(img->info.angle * (TWOPI / 360)); + double cos_a = cos(img->info.angle * (TWOPI / 360)); + + if (globals.debug & DBG_PS) + fprintf(stderr, "\t rotating image by %d degrees\n", + img->info.angle); + + /* adjust xoff, yoff, assuming (0,0) the origin of rotation */ + if (img->xoff || img->yoff) { + double a, b; + a = img->xoff * cos_a + img->yoff * sin_a; + b = -img->xoff * sin_a + img->yoff * cos_a; + img->xoff = ROUND(a); + img->yoff = ROUND(b); + } + + showtime("RotateImage:"); + tmp_image = RotateImage(image, -(double)img->info.angle, &exception); + DestroyImage(image); + image = tmp_image; + CATCH_EXCEPTION(exception); + if (image == NULL) + return NULL; + /* + * _______________________________________ + * | * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * |* *| + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | * * | + * | O (origin) | + * O-------------------------------------- + * (lower-left corner of image box after rotation) + */ + if (img->info.angle <= 90) { + img->xoff += ROUND(h * sin_a); + } + else if (img->info.angle <= 180) { + img->xoff += ROUND(h * sin_a - w * cos_a); + img->yoff += ROUND(h * cos_a); + } + else if (img->info.angle <= 270) { + img->xoff -= ROUND(w * cos_a); + img->yoff += ROUND(w * sin_a + h * cos_a); + } + else { + img->yoff += ROUND(w * sin_a); + } + return image; +} + +static Image * +crop_shift_image(XDviImage *img, Image *image, + unsigned long *columns, unsigned long *rows, + double xscale, double yscale, + ExceptionInfo exception) +{ + /* ZLB: the code below works for the case where the BoundingBox + * of the PS figure generated by the shell command is (0 0 w h), + * where w and h are the pixel width and height of the input image. + * (this is true for the program 'convert' without options). + * The image should then be cropped or/and shifted depending + * on the value of 'clip' */ + int w = image->columns, h = image->rows; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: type of input image = CONVERT\n"); + + if (!img->info.clip) { + /* We only need to shift the image */ + img->xoff = +ROUND(xscale * DPI * img->info.llx / 72.0); + img->yoff = -ROUND(yscale * DPI * img->info.lly / 72.0); + *columns = ROUND(xscale * DPI * w / 72.0); + *rows = ROUND(yscale * DPI * h / 72.0); + } + else { + int llx = ROUND(img->info.llx); + int lly = ROUND(img->info.lly); + int urx = ROUND(img->info.urx); + int ury = ROUND(img->info.ury); + + if (llx < 0) { + img->xoff = +ROUND(xscale * DPI * llx / 72.0); + llx = 0; + } + if (lly < 0) { + img->yoff = -ROUND(yscale * DPI * lly / 72.0); + lly = 0; + } + if (urx > w) + urx = w; + if (ury > h) + ury = h; + + if ((urx - llx) < 1 || (urx - llx) < 1) { + DestroyImage(image); + return NULL; + } + + if (llx != 0 || lly != 0 || urx != w || ury != h) { + /* Crop the image */ + Image *tmp_image; + RectangleInfo geo; + geo.width = urx - llx; + geo.height = ury - lly; + geo.x = llx; + geo.y = h - ury; + if (globals.debug & DBG_PS) + fprintf(stderr, "\t cropping image (%d %d %d %d)\n", + llx, lly, urx, ury); + showtime("CropImage:"); + tmp_image = CropImage(image, &geo, &exception); + DestroyImage(image); + image = tmp_image; + CATCH_EXCEPTION(exception); + if (image == NULL) + return NULL; /* error cropping image */ + } + *columns = ROUND(xscale * DPI * (urx - llx) / 72.0); + *rows = ROUND(yscale * DPI * (ury - lly) / 72.0); + } + return image; +} + + +/* parse and return the `magick_cache' resource size_str */ +static long +parse_cache_setting(const char *size_str) +{ + char *p; + long size = strtol(size_str, &p, 0); + switch (*p) { + case 'g': + case 'G': + size *= 1024; + /* fall through */ + case 'm': + case 'M': + size *= 1024; + /* fall through */ + case 'k': + case 'K': + size *= 1024; + break; + default: + if (*p != '\0') { + fprintf(stderr, "Magick: invalid suffix (%c) in " + "magick_cache option\n", *p); + } + } + return size; +} + +static XDviImage * +load_image(XDviImageInfo *info) +{ + static ExceptionInfo exception; + static ImageInfo *image_info; + static XResourceInfo resource_info; + static XVisualInfo visual_info; + static XStandardColormap map_info; + static XPixelInfo pixel_info; + static XWindowInfo window_info; + static Boolean initialized = False; + static Boolean disabled = False; + + static XDviImage img = { {False, 0, 0, 0, 0, 0, 0, 0, ""}, NULL, 0, 0, 0 }; + + Image *image, *tmp_image; + double xscale, yscale, xdpi, ydpi; + int n; + unsigned long columns, rows; + char density[40]; + size_t size; + char *fn = info->filename; + XDviImage *c; + PixelPacket bgcolor; + + struct stat statbuf; + char *path, *fullpath; + char canonical_path[MAXPATHLEN + 1]; + + if (disabled) + return NULL; + + parse_backtick(info->filename); + if (info->filename[0] == '\0') { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: skipping malformed backtick command.\n"); + return NULL; + } + else { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: filename=|%s|\n", info->filename); + } + + img.info = *info; + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: load_image: fn=%s\n", fn); + + /* check for changes in mag/shrink/resolution */ + if (fabs(current_dpi - DPI) > 1e-4) { + current_dpi = DPI; + clear_cache(); + } + + /* look it up in the cache */ + if (ncached) { + for (c = cache; c < cache + ncached; c++) { + if (memcmp(&c->info, info, sizeof *info)) + continue; + if (globals.debug & DBG_PS) + fprintf(stderr, " Image found in cache.\n"); + return c; + } + } + + if (!initialized) { + magick_tmp = NULL; + if ((n = xdvi_temp_fd(&magick_tmp)) == -1) { + fprintf(stderr, "Magick: cannot create tmp filename, disabled.\n"); + disabled = True; + return NULL; + } + /* FIXME: mustn't close filedescriptor returned by xdvi_temp_fd(), + else we have a race condition again; see e.g. + http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/avoid-race.html + */ + close(n); + unlink(magick_tmp); + atexit(cleanup); /* remove magick_tmp at exit. */ + + showtime("Initializing load_image:"); + InitializeMagick("xdvi.bin"); + GetExceptionInfo(&exception); + image_info = CloneImageInfo((ImageInfo *) NULL); + + XGetResourceInfo(XrmGetStringDatabase(""), "xdvi", &resource_info); + /* resource_info.debug = 1; */ + if (!XMatchVisualInfo(DISP, XScreenNumberOfScreen(SCRN), + G_depth, G_visual->class, &visual_info)) { + fprintf(stderr, "Magick: can't get visual info, image disabled.\n"); + disabled = True; + return NULL; + } + XGetMapInfo(&visual_info, G_colormap, &map_info); + XGetWindowInfo(DISP, &visual_info, &map_info, &pixel_info, + (XFontStruct *) NULL, &resource_info, &window_info); + + if (resource.magick_cache != NULL) { + cache_limit = parse_cache_setting(resource.magick_cache); + if (globals.debug & DBG_PS) + fprintf(stderr, " Setting magick_cache to %lu\n", + (unsigned long)cache_limit); + } + initialized = True; + } + + xscale = info->rwi / (double) ((int) (10 * (info->urx - info->llx) + .5)); + yscale = info->rhi / (double) ((int) (10 * (info->ury - info->lly) + .5)); + xdpi = xscale * DPI; + ydpi = yscale * DPI; + /* Compute scaled size of the image */ + columns = ROUND(DPI * info->rwi * 0.1 / 72); + rows = ROUND(DPI * info->rhi * 0.1 / 72); + if (globals.debug & DBG_PS) + fprintf(stderr, "\tscale=%0.2fx%0.2f, dpi=%0.2fx%0.2f, size=%ldx%ld\n", + xscale, yscale, xdpi, ydpi, columns, rows); + + /* expand and canonicalize path name */ + path = find_file(fn, &statbuf, kpse_pict_format); + if (path != NULL) { + fullpath = REALPATH(path, canonical_path); + if (fullpath == NULL) { + XDVI_WARNING((stderr, "Couldn't canonicalize %s to full path - returning unexpanded.", + path)); + fullpath = path; + } + else { + free(path); + } + } + else { + fprintf(stderr, "%s: %s:%d: Can't find image file %s!\n", + globals.program_name, __FILE__, __LINE__, fn); + return NULL; + } + + /* If the file is PS/EPS transform it to JPEG first (faster) */ + n = strlen(fullpath); + if ((n >= 3 && !memicmp(fullpath + n - 3, ".ps", 3)) || + (n >= 4 && !memicmp(fullpath + n - 4, ".eps", 4))) { + if (!imagick_eps_to_jpeg(fullpath, &columns, &rows, &img, &bgcolor, xdpi, ydpi)) { + return NULL; + } + /* Should we append the 'JPEG:' prefix to the filename to help + * ImageMagick to identify the file format? */ + fullpath = magick_tmp; + } + else { + img.xoff = img.yoff = 0; + } + + strcpy(image_info->filename, fullpath); + showtime("ReadImage:"); + if (fullpath == magick_tmp) { + /* seems 'density' only affects image size of PS/EPS files */ + sprintf(density, "%0.2fx%0.2f", xdpi, ydpi); + image_info->density = density; + image_info->units = PixelsPerInchResolution; + } + image = ReadImage(image_info, &exception); + CATCH_EXCEPTION(exception); + unlink(magick_tmp); + if (image == NULL) /* error loading image */ + return NULL; + + /* Set background color for PS/EPS fig */ + if (fullpath == magick_tmp) /* input is PS */ + image->background_color = bgcolor; + + if (globals.debug & DBG_PS) { + int i = image->units; + fprintf(stderr, "\t image size = %ldx%ld\n", + image->columns, image->rows); + fprintf(stderr, "\t image resolution = %0.2fx%0.2f (units=%s)\n", + image->x_resolution, image->y_resolution, + i == UndefinedResolution ? "???" : + i == PixelsPerInchResolution ? "PPI" : "PPCM"); + } + + if (fullpath != magick_tmp) { /* non PS image */ + /* Note: (info->llx info->lly info->urx info->ury) should be + * regarded as the viewport */ + if (image_type == TYPE_CONVERT) { + image = crop_shift_image(&img, image, &columns, &rows, xscale, yscale, exception); + } + else { + /* Do nothing. The image will be scaled to the size of the bbox. + * ZLB: the output will be identical to dvips iff the bbox of + * the PS figure generated by the shell command is equal to + * (info->llx info->lly info->urx info->ury). + * (this is true if the bbox set via 'bb', 'viewport', etc., + * is equal to the real bbox of the PS figure) */ + } + + if (columns != image->columns || rows != image->rows) { + /* Scale image */ + if (globals.debug & DBG_PS) { + fprintf(stderr, "\t scaling image %ldx%ld -> %ldx%ld\n", + image->columns, image->rows, columns, rows); + } + showtime("ScaleImage:"); + tmp_image = ScaleImage(image, columns, rows, &exception); + DestroyImage(image); + image = tmp_image; + CATCH_EXCEPTION(exception); + if (image == NULL) + return NULL; /* error resizing image */ + } + } + + /* rotate image */ + if (info->angle) { + image = rotate_image(&img, image, exception); + } + +#if 0 + DescribeImage(image, stdout, 1 /* verbosity */ ); + DisplayImages(image_info, image); +#endif + +#define XStandardPixel(map,color,dx) (unsigned long) (map.base_pixel + \ + (((color).red * map.red_max + (1L << (dx - 1L))) / ((1L << dx) - 1L)) * map.red_mult + \ + (((color).green * map.green_max + (1L << (dx - 1L))) / ((1L << dx) - 1L)) * map.green_mult + \ + (((color).blue * map.blue_max + (1L << (dx - 1L))) / ((1L << dx) - 1L)) * map.blue_mult) + + img.bgpixel = XStandardPixel(map_info, image->background_color, 16); + if (img.ximage != NULL) { + free_ximage(img.ximage); + img.ximage = NULL; + } +#undef XStandardPixel + + /* Transform to XImage */ + window_info.id = mane.win; + window_info.annotate_context = globals.gc.copy; + /* + FIXME: If this isn't set to false, running on a remote display may + lead to `Badaccess (no permission to access private resource)' X + errors (only with ImageMagick >= 5.4 - zlb has emailed the + developers about this). + Setting it to false always might slightly hurt performance - how + to check for remoteness? Parse the DISPLAY variable? + */ + window_info.shared_memory = False; + showtime("XMakeImage:"); + XGetPixelPacket(DISP, &visual_info, &map_info, + &resource_info, image, &pixel_info); + n = XMakeImage(DISP, &resource_info, &window_info, image, + image->columns, image->rows); + + /* Since XDestroyImage doesn't know how to free window_info.ximage->data, + * we simply copy window_info.ximage. The next call of XMakeImage + * will free both window_info.ximage->data and window_info.ximage. + */ + DestroyImage(image); + if (!n) + return NULL; + img.ximage = XCreateImage(DISP, G_visual, G_depth, ZPixmap, 0, NULL, + window_info.ximage->width, + window_info.ximage->height, BMBITS, 0); + if (img.ximage == NULL) + return NULL; + *img.ximage = *(window_info.ximage); + size = img.ximage->bytes_per_line * img.ximage->height; + img.ximage->data = malloc(size ? size : 1); /* NOTE: no xmalloc! */ + if (img.ximage->data == NULL) { + fprintf(stderr, "Magick: cannot allocate memory for ximage data.\n"); + XDestroyImage(img.ximage); + img.ximage = NULL; + return NULL; + } + if (size) + memcpy(img.ximage->data, window_info.ximage->data, size); + + size += sizeof(XDviImage) + sizeof(XImage); + if (cache_size + size > cache_limit) { + if (globals.debug & DBG_PS) + fprintf(stderr, " image not cached (cache limit).\n"); + return &img; + } + + c = realloc(cache, sizeof(XDviImage) * (ncached + 1)); /* NOTE: no xrealloc! */ + if (c == NULL) { + if (globals.debug & DBG_PS) + fprintf(stderr, " image not cached (cache alloc).\n"); + return &img; + } + cache_size += size; + cache = c; + c = cache + (ncached++); + *c = img; + img.ximage = NULL; /* so next call won't delete this ximage */ + + if (globals.debug & DBG_PS) + fprintf(stderr, " image cached (cache_size=%uKB, limit=%uKB).\n", + cache_size / 1024, cache_limit / 1024); +#if 0 + DestroyImageInfo(image_info); + DestroyExceptionInfo(&exception); + DestroyMagick(); +#endif + + return c; +} + +static void +display_image(XDviImage * img, int xul, int yul, + const struct window_expose_info *expose, + const GC gc) +{ + if (globals.debug & DBG_PS) + fprintf(stderr, " display_image: pos=%d, %d\n", xul, yul); + + xul -= img->xoff; + yul -= img->yoff; + +#if 0 + XPutImage(DISP, mane.win, globals.gc.copy, img->ximage, 0, 0, + xul, yul - img->ximage.height + 1, + img->ximage.width, img->ximage.height); +#else + /* Try to make background pixels transparent */ + { + XImage *scr; + int x0 = xul; + int y0 = yul - img->ximage->height + 1; + int w = img->ximage->width; + int h = img->ximage->height; + int xoff = 0; + int yoff = 0; + int x, y; + + if (x0 + w <= expose->min_x || x0 >= expose->max_x || + y0 + h <= expose->min_y || y0 >= expose->max_y) + return; + if (x0 < expose->min_x) { + xoff = expose->min_x - x0; + x0 = expose->min_x; + w -= xoff; + } + if (x0 + w > expose->max_x) + w = expose->max_x - x0 + 0; + if (y0 < expose->min_y) { + yoff = expose->min_y - y0; + y0 = expose->min_y; + h -= yoff; + } + if (y0 + h > expose->max_y) + h = expose->max_y - y0 + 0; + + { + /* Need to double check the bounds here, because redraw_page() in + * events.c may set larger bounding box (expose->min_x, expose->min_y, etc.) + * than actual viewport causing 'BadMatch' error in XGetImage(). + * + * Here we clip the rectangle w*h+x0+y0 with the root window. */ + XWindowAttributes a; + Status status; + Window child; + + status = XGetWindowAttributes(DISP, mane.win, &a); + if (!status || a.map_state != IsViewable) + return; + XTranslateCoordinates(DISP, mane.win, a.root, x0, y0, &x, &y, + &child); + XGetWindowAttributes(DISP, a.root, &a); + if (x + w <= a.x || x >= a.x + a.width || y + h <= a.y + || y >= a.y + a.height) return; + if (x < a.x) { + x0 += a.x - x; + xoff += a.x - x; + w -= a.x - x; + x = a.x; + } + if (x + w > a.x + a.width) + w = a.x + a.width - x; + if (y < a.y) { + y0 += a.y - y; + yoff += a.y - y; + h -= a.y - y; + y = a.y; + } + if (y + h > a.y + a.height) + h = a.y + a.height - y; +#if 0 + XDrawLine(DISP, mane.win, gc, x0, y0, x0 + w - 1, y0); + XDrawLine(DISP, mane.win, gc, x0 + w - 1, y0, x0 + w - 1, y0 + h - 1); + XDrawLine(DISP, mane.win, gc, x0 + w - 1, y0 + h - 1, x0, y0 + h - 1); + XDrawLine(DISP, mane.win, gc, x0, y0 + h - 1, x0, y0); + XDrawLine(DISP, mane.win, gc, x0, y0, x0 + w - 1, y0 + h - 1); + XDrawLine(DISP, mane.win, gc, x0 + w - 1, y0, x0, y0 + h - 1); + + XDrawLine(DISP, mane.win, gc, expose->min_x, + expose->min_y, expose->max_x, expose->min_y); + XDrawLine(DISP, mane.win, gc, expose->max_x, + expose->min_y, expose->max_x, expose->max_y); + XDrawLine(DISP, mane.win, gc, expose->max_x, + expose->max_y, expose->min_x, expose->max_y); + XDrawLine(DISP, mane.win, gc, expose->min_x, + expose->max_y, expose->min_x, expose->min_y); + XDrawLine(DISP, mane.win, gc, expose->max_x, + expose->max_y, expose->min_x, expose->min_y); + XDrawLine(DISP, mane.win, gc, expose->min_x, + expose->max_y, expose->max_x, expose->min_y); +#endif + } + +#ifdef __GNUC__ +#warning FIXME: GetImage may still produce 'BadMatch' when dragging \ + the window around (toward outside of the desktop). +#endif + scr = XGetImage(DISP, mane.win, x0, y0, w, h, AllPlanes, ZPixmap); + +#warning FIXME: the loops are only a quick hack and are slow. + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + unsigned long pixel = 0; + pixel = XGetPixel(img->ximage, x + xoff, y + yoff); + if (pixel == img->bgpixel) + continue; + XPutPixel(scr, x, y, pixel); + } + XPutImage(DISP, mane.win, globals.gc.copy, scr, 0, 0, x0, y0, w, h); + XDestroyImage(scr); + } +#endif + showtime(" Done."); +} + +static void +render_image_file(XDviImageInfo *info, int x, int y) +{ + XDviImage *im = NULL; + int w, h; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: render_image_file: pos=(%d,%d)\n", x, y); + +#ifdef __GNUC__ +#warning FIXME: immediate return if image not exposed. +#endif + + if (!INSIDE_MANE_WIN) + return; + + info->angle = bbox_angle >= 0 ? bbox_angle % 360 : 360 - (-bbox_angle) % 360; + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: render_image_file: angle=%d\n", info->angle); + + if (resource.postscript && (im = load_image(info)) != NULL) + display_image(im, x, y, &globals.win_expose, globals.gc.rule); + + if (im != NULL && resource.postscript == 1) + return; + + /* Draw bbox of the image */ + w = ROUND(DPI * info->rwi / 720.0); + h = ROUND(DPI * info->rhi / 720.0); + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: render_image_file: box=%dx%d+%d+%d\n", + w, h, x, y - h + 1); + + if (!info->angle) { + XDrawRectangle(DISP, currwin.win, globals.gc.high, x, y - h + 1, w, h); + } + else + { +#if 1 + bbox_valid = True; + bbox_width = w; + bbox_height = bbox_voffset = h; + save_bbox(); + bbox_valid = False; +#else + /* code borrowed from special.c:draw_bbox() */ + double sin_a = sin(info->angle * (TWOPI / 360)); + double cos_a = cos(info->angle * (TWOPI / 360)); + double a, b, c, d; + + a = cos_a * (w-1); + b = -sin_a * (w-1); + c = -sin_a * (h-1); + d = -cos_a * (h-1); + + XDrawLine(DISP, currwin.win, globals.gc.high, + x, y, x + ROUND(a), y + ROUND(b)); + XDrawLine(DISP, currwin.win, globals.gc.high, + x + ROUND(a), y + ROUND(b), + x + ROUND(a + c), y + ROUND(b + d)); + XDrawLine(DISP, currwin.win, globals.gc.high, + x + ROUND(a + c), y + ROUND(b + d), + x + ROUND(c), y + ROUND(d)); + XDrawLine(DISP, currwin.win, globals.gc.high, + x + ROUND(c), y + ROUND(d), x, y); +#endif + } +} + +/* Process EPSF specials */ + +static Boolean +epsf_special(char *cp) +{ + char *filename; + unsigned filename_len; + char *p; + int n; + unsigned long flags = 0, u; + double rwi, rhi; + enum { LLX, LLY, + URX, URY, + RWI, RHI, + HSIZE, VSIZE, + HOFFSET, VOFFSET, + HSCALE, VSCALE, + ANGLE + }; + XDviImageInfo info; + + struct { + double val; + const char *key; + long mask; + } key_info[] = { + { 0, "llx", 0x0001 }, + { 0, "lly", 0x0002 }, + { 0, "urx", 0x0004 }, + { 0, "ury", 0x0008 }, + { 0, "rwi", 0x0010 }, + { 0, "rhi", 0x0020 }, + { 0, "hsize", 0x0040 }, + { 0, "vsize", 0x0080 }, + { 0, "hoffset", 0x0100 }, + { 0, "voffset", 0x0200 }, + { 0, "hscale", 0x0400 }, + { 0, "vscale", 0x0800 }, + { 0, "angle", 0x1000 }, + }; + + memset(&info, 0, sizeof info); /* for validating memcmp(&info, ... */ + + filename = cp; + if (*cp == '\'' || *cp == '"') { + do + ++cp; + while (*cp != '\0' && *cp != *filename); + filename_len = cp - filename - 1; + if (*cp == *filename) + ++cp; + ++filename; + } + else { + while (*cp != '\0' && !isspace((int)*cp)) + ++cp; + filename_len = cp - filename; + } + + if (filename_len + 1 > MaxTextExtent) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: filename too long, passing to psgs.c\n"); + return False; + } + + memcpy(info.filename, filename, filename_len); /* no need to terminate because of memset(...) above */ + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: epsf_special: filename = '%s'\n", + info.filename); + + /* Scan for EPSF keywords */ + while (*cp != '\0') { + while (isspace((int)*cp)) + ++cp; + p = cp; + while (*p != '=' && !isspace((int)*p) && *p != '\0') + p++; + n = p - cp; + if (!n) + break; + if (*p == '=') + ++p; + while (isspace((int)*p)) + ++p; + + if (!memcmp(cp, "clip", n)) { + /* this is the only key without value */ + info.clip = True; + } + else { + size_t i; + Boolean found = False; + for (i = 0; !found && i < XtNumber(key_info); i++) { + if (memicmp(cp, key_info[i].key, n) == 0) { + if (sscanf(p, "%lf", &(key_info[i].val)) == 1) { + flags |= key_info[i].mask; + while (!isspace((int)*p) && *p != '\0') + ++p; + found = True; + } + else { + fprintf(stderr, "Magick: invalid value for %s\n", key_info[i].key); + return False; + } + } + } + + if (!found) { + i = *(cp + n); + *(cp + n) = '\0'; + fprintf(stderr,"Magick: ignoring unknown EPSF key \"%s\".\n", cp); + *(cp + n) = i; + /* skip '=...' */ + while (*p != '\0' && !isspace((int)*p)) + ++p; + } + } + cp = p; + } + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: epsf_special: flags = 0x%lx\n", flags); + + /* only accept the special when llx, lly, urx, ury are properly defined */ + u = key_info[LLX].mask | key_info[LLY].mask | + key_info[URX].mask | key_info[URY].mask; + if ((flags & u) != u) + return False; + +#ifdef __GNUC__ +#warning keywords to handle: [hv]size, [hv]offset, [hv]scale, angle \ + (ZLB: not used by the graphicx/graphics packages) +#endif + if (flags & (key_info[HSIZE].mask | key_info[VSIZE].mask | + key_info[HOFFSET].mask | key_info[VOFFSET].mask | + key_info[HSCALE].mask | key_info[VSCALE].mask | + key_info[ANGLE].mask)) + fprintf(stderr, "Magick: warning: EPSF keywords '[hv]size', " + "'[hv]offset', '[hv]scale', and 'angle' are not " + "implemented.\n"); + + info.urx = key_info[URX].val; + info.ury = key_info[URY].val; + info.llx = key_info[LLX].val; + info.lly = key_info[LLY].val; + rwi = key_info[RWI].val; + rhi = key_info[RHI].val; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: epsf_special: llx=%0.2f, lly=%0.2f, " + "urx=%0.2f, ury=%0.2f\n", + info.llx, info.lly, info.urx, info.ury); + + if (info.urx - info.llx < 1 || info.ury - info.lly < 1) + return False; + if (info.urx - info.llx > 1e+4 || info.ury - info.lly > 1e+4) + return False; + if (fabs(info.llx) > 1e+10 || fabs(info.lly) > 1e+10) + return False; + + if (!(flags & (key_info[RWI].mask | key_info[RHI].mask))) { + /* both rwi and rhi undefined */ + rwi = 10 * (info.urx - info.llx); + rhi = 10 * (info.ury - info.lly); + } + else if (!(flags & key_info[RWI].mask)) { + /* rwi undefined, rhi defined */ + rwi = rhi * (info.urx - info.llx) / (info.ury - info.lly); + } + else if (!(flags & key_info[RHI].mask)) { + /* rhi undefined, rwi defined */ + rhi = rwi * (info.ury - info.lly) / (info.urx - info.llx); + } + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: epsf_special: rwi=%f, rhi=%f\n", rwi, rhi); + + if (rwi < 1 || rhi < 1) + return False; + + /* We don't want IM to process large images (>16MB) */ + if (DPI * rwi / 720 * DPI * rhi / 720 * G_depth / 8 / 1024 / 1024 > 16) { + p = filename + (n = filename_len); + if ((n >= 3 && !memicmp(p - 3, ".ps", 3)) || + (n >= 4 && !memicmp(p - 4, ".eps", 4))) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: image too large, passing to psgs.c\n"); + return False; + } + else { + /* don't pass non-PS files to psgs.c */ + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: image too large, ignored.\n"); + return True; + } + } + + info.rwi = ROUND(rwi); + info.rhi = ROUND(rhi); + + render_image_file(&info, PXL_H - currwin.base_x, PXL_V - currwin.base_y); + + return True; +} + +static char * +emgraph_get_dimen(char *cp, double *v) +{ + char *p; + typedef struct { + int name_len; + char *name; + double factor; + } Unit; + Unit *u; + /* table for converting an arbitrary unit to bp */ +#define UNIT(u) sizeof(u)-1, u + static Unit units[] = { + { UNIT("bp"), 1.0 }, + { UNIT("in"), 72.0 }, + { UNIT("cm"), 72.0 / 2.54 }, + { UNIT("mm"), 72.0 / 2.54 * 0.1 }, + { UNIT("pt"), 72.0 / 72.27 }, + { UNIT("sp"), 72.0 / 72.27 / 65536.0 }, + { UNIT("pc"), 12.0 * 72.0 / 72.27 }, + { UNIT("dd"), 1238.0 / 1157.0 * 72.0 / 72.27 }, + { UNIT("cc"), 12.0 * 1238.0 / 1157.0 * 72.0 / 72.27 } + }; + + *v = strtod(cp, &p); + if (p == NULL) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: can't get dimension, image ignored.\n"); + return NULL; + } + + while (isspace((int)*p)) + ++p; + for (u = units; u < units + XtNumber(units); u++) + if (!memicmp(p, u->name, u->name_len)) + break; + if (u >= units + XtNumber(units)) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: unknown unit, image ignored.\n"); + return NULL; + } + *v *= u->factor; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: unit=%s, len=%d, factor=%0.4f, value=%0.4f\n", + u->name, u->name_len, u->factor, *v); + + if (*v < 0.01 || *v > 10000) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: invalid dimension, image ignored.\n"); + return NULL; + } + + return p + u->name_len; +} + +static Boolean +emgraph_special(char *cp) +{ + char *p; + double w, h; + XDviImageInfo info; + + memset(&info, 0, sizeof info); + + /* get filename */ + while (isspace((int)*cp)) + ++cp; + p = cp; + while (!isspace((int)*p) && *p != '\0' && *p != ',') + ++p; + if ((unsigned)(p - cp) >= sizeof info.filename) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: filename too long, image ignored.\n"); + return False; + } + if (p <= cp) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: empty filename, image ignored.\n"); + return False; + } + memcpy(info.filename, cp, p - cp); /* no need to terminate because of memset(...) above */ + + cp = p; + + /* get width */ + if ((cp = strchr(cp, ',')) == NULL) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: width undefined, image ignored.\n"); + return False; + } + if ((cp = emgraph_get_dimen(++cp, &w)) == NULL) + return False; + + /* get height */ + if ((cp = strchr(cp, ',')) == NULL) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: height undefined, image ignored.\n"); + return False; + } + if ((cp = emgraph_get_dimen(++cp, &h)) == NULL) + return False; + + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: filename=\"%s\", width=%0.2f, height=%0.2f\n", + info.filename, w, h); + + info.llx = info.lly = 0; + info.urx = w; + info.ury = h; + info.rwi = ROUND(10 * w); + info.rhi = ROUND(10 * h); + + if (info.rwi < 1 || info.rhi < 1) + return False; + + render_image_file(&info, PXL_H - currwin.base_x, + PXL_V - currwin.base_y + ROUND(h / 72 * DPI) - 1); + + return True; +} + +/* Filter specials. Returns True if the special has been processed + * by ImageMagick, False otherwise. */ +Boolean +Magick_parse_special(char *cp) +{ + switch (*cp) { + case 'p': + case 'P': + if (!memicmp(cp, "psfile", 6)) { + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: parsing string |%s|\n", cp); + cp += 6; + while (isspace((int)*cp)) + ++cp; + if (*cp != '=') + return False; + do + ++cp; + while (isspace((int)*cp)); + return epsf_special(cp); + } + break; + case 'e': + case 'E': + if (!memicmp(cp, "em:", 3)) { + cp += 3; + while (isspace((int)*cp)) + ++cp; + if (memicmp(cp, "graph", 5)) + return False; + cp += 5; + if (globals.debug & DBG_PS) + fprintf(stderr, "Magick: parsing string |em:graph %s|\n", cp); + return emgraph_special(cp); + } + break; + } + + return False; +} + +#else +/* silence `empty compilation unit' warnings */ +static void bar(void); static void foo() { bar(); } static void bar(void) { foo(); } +#endif /* MAGICK */ |