Graphviz  2.31.20130525.0447
lib/gvc/gvusershape.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 #ifdef HAVE_CONFIG_H
00015 #include "config.h"
00016 #endif
00017 
00018 #include <stddef.h>
00019 #include <string.h>
00020 #include <errno.h>
00021 
00022 #ifdef WIN32
00023 #include <windows.h>
00024 #define GLOB_NOSPACE    1   /* Ran out of memory.  */
00025 #define GLOB_ABORTED    2   /* Read error.  */
00026 #define GLOB_NOMATCH    3   /* No matches found.  */
00027 #define GLOB_NOSORT     4
00028 #define DMKEY "Software\\Microsoft" //key to look for library dir
00029 #include "regex_win32.h"
00030 #else
00031 #include <regex.h>
00032 #endif
00033 
00034 #include "types.h"
00035 #include "logic.h"
00036 #include "memory.h"
00037 #include "agxbuf.h"
00038 
00039 #define _BLD_gvc 1
00040 #include "utils.h"
00041 #include "gvplugin_loadimage.h"
00042 
00043 extern char *Gvimagepath;
00044 extern char *HTTPServerEnVar;
00045 extern shape_desc *find_user_shape(const char *);
00046 
00047 static Dict_t *ImageDict;
00048 
00049 typedef struct {
00050     char *template;
00051     int size;
00052     int type;
00053     char *stringtype;
00054 } knowntype_t;
00055 
00056 #define HDRLEN 20
00057 
00058 #define PNG_MAGIC  "\x89PNG\x0D\x0A\x1A\x0A"
00059 #define PS_MAGIC   "%!PS-Adobe-"
00060 #define BMP_MAGIC  "BM"
00061 #define GIF_MAGIC  "GIF8"
00062 #define JPEG_MAGIC "\xFF\xD8\xFF\xE0"
00063 #define PDF_MAGIC  "%PDF-"
00064 #define EPS_MAGIC  "\xC5\xD0\xD3\xC6"
00065 #define XML_MAGIC  "<?xml"
00066 #define SVG_MAGIC  "<svg"
00067 #define RIFF_MAGIC  "RIFF"
00068 #define WEBP_MAGIC  "WEBP"
00069 
00070 static knowntype_t knowntypes[] = {
00071     { PNG_MAGIC,  sizeof(PNG_MAGIC)-1,   FT_PNG,  "png",  },
00072     { PS_MAGIC,   sizeof(PS_MAGIC)-1,    FT_PS,   "ps",   },
00073     { BMP_MAGIC,  sizeof(BMP_MAGIC)-1,   FT_BMP,  "bmp",  },
00074     { GIF_MAGIC,  sizeof(GIF_MAGIC)-1,   FT_GIF,  "gif",  },
00075     { JPEG_MAGIC, sizeof(JPEG_MAGIC)-1,  FT_JPEG, "jpeg", },
00076     { PDF_MAGIC,  sizeof(PDF_MAGIC)-1,   FT_PDF,  "pdf",  },
00077     { EPS_MAGIC,  sizeof(EPS_MAGIC)-1,   FT_EPS,  "eps",  },
00078 /*    { SVG_MAGIC,  sizeof(SVG_MAGIC)-1,  FT_SVG,  "svg",  },  - viewers expect xml preamble */
00079     { XML_MAGIC,  sizeof(XML_MAGIC)-1,   FT_XML,  "xml",  },
00080     { RIFF_MAGIC,  sizeof(RIFF_MAGIC)-1, FT_RIFF, "riff", },
00081 };
00082 
00083 static int imagetype (usershape_t *us)
00084 {
00085     char header[HDRLEN];
00086     char line[200];
00087     int i;
00088 
00089     if (us->f && fread(header, 1, HDRLEN, us->f) == HDRLEN) {
00090         for (i = 0; i < sizeof(knowntypes) / sizeof(knowntype_t); i++) {
00091             if (!memcmp (header, knowntypes[i].template, knowntypes[i].size)) {
00092                 us->stringtype = knowntypes[i].stringtype;
00093                 us->type = knowntypes[i].type;
00094                 if (us->type == FT_XML) {
00095                     /* check for SVG in case of XML */
00096                     while (fgets(line, sizeof(line), us->f) != NULL) {
00097                         if (!memcmp(line, SVG_MAGIC, sizeof(SVG_MAGIC)-1)) {
00098                             us->stringtype = "svg";
00099                             return (us->type = FT_SVG);
00100                         }
00101                     }
00102                 }
00103                 else if (us->type == FT_RIFF) {
00104                     /* check for WEBP in case of RIFF */
00105                     if (!memcmp(header+8, WEBP_MAGIC, sizeof(WEBP_MAGIC)-1)) {
00106                         us->stringtype = "webp";
00107                         return (us->type = FT_WEBP);
00108                     }
00109                 }
00110                 return us->type;
00111             }
00112         }
00113     }
00114 
00115     us->stringtype = "(lib)";
00116     us->type = FT_NULL;
00117 
00118     return FT_NULL;
00119 }
00120     
00121 static boolean get_int_lsb_first (FILE *f, unsigned int sz, unsigned int *val)
00122 {
00123     int ch, i;
00124 
00125     *val = 0;
00126     for (i = 0; i < sz; i++) {
00127         ch = fgetc(f);
00128         if (feof(f))
00129             return FALSE;
00130         *val |= (ch << 8*i);
00131     }
00132     return TRUE;
00133 }
00134         
00135 static boolean get_int_msb_first (FILE *f, unsigned int sz, unsigned int *val)
00136 {
00137     int ch, i;
00138 
00139     *val = 0;
00140     for (i = 0; i < sz; i++) {
00141         ch = fgetc(f);
00142         if (feof(f))
00143             return FALSE;
00144         *val <<= 8;
00145         *val |= ch;
00146     }
00147     return TRUE;
00148 }
00149 
00150 static unsigned int svg_units_convert(double n, char *u)
00151 {
00152     if (strcmp(u, "in") == 0)
00153         return ROUND(n * POINTS_PER_INCH);
00154     if (strcmp(u, "px") == 0)
00155         return ROUND(n * POINTS_PER_INCH / 96);
00156     if (strcmp(u, "pc") == 0)
00157         return ROUND(n * POINTS_PER_INCH / 6); 
00158     if (strcmp(u, "pt") == 0 || strcmp(u, "\"") == 0)   /* ugly!!  - if there are no inits then the %2s get the trailing '"' */
00159         return ROUND(n);
00160     if (strcmp(u, "cm") == 0)
00161         return ROUND(n * POINTS_PER_CM);
00162     if (strcmp(u, "mm") == 0)
00163         return ROUND(n * POINTS_PER_MM);
00164     return 0;
00165 }
00166 
00167 static char* svg_attr_value_re = "([a-z][a-zA-Z]*)=\"([^\"]*)\"";
00168 static regex_t re, *pre = NULL;
00169 
00170 static void svg_size (usershape_t *us)
00171 {
00172     unsigned int w = 0, h = 0;
00173     double n, x0, y0, x1, y1;
00174     char u[10];
00175     char *attribute, *value, *re_string;
00176     char line[200];
00177     boolean wFlag = FALSE, hFlag = FALSE;
00178 #define RE_NMATCH 4
00179     regmatch_t re_pmatch[RE_NMATCH];
00180 
00181     /* compile on first use */
00182     if (! pre) {
00183         if (regcomp(&re, svg_attr_value_re, REG_EXTENDED) != 0) {
00184             agerr(AGERR,"cannot compile regular expression %s", svg_attr_value_re);
00185         }
00186         pre = &re;
00187     }
00188 
00189     fseek(us->f, 0, SEEK_SET);
00190     while (fgets(line, sizeof(line), us->f) != NULL && (!wFlag || !hFlag)) {
00191         re_string = line;
00192         while (regexec(&re, re_string, RE_NMATCH, re_pmatch, 0) == 0) {
00193             re_string[re_pmatch[1].rm_eo] = '\0';
00194             re_string[re_pmatch[2].rm_eo] = '\0';
00195             attribute = re_string + re_pmatch[1].rm_so;
00196             value = re_string + re_pmatch[2].rm_so;
00197             re_string += re_pmatch[0].rm_eo + 1;
00198 
00199             if (strcmp(attribute,"width") == 0) {
00200                 if (sscanf(value, "%lf%2s", &n, u) == 2) {
00201                     w = svg_units_convert(n, u);
00202                     wFlag = TRUE;
00203                 }
00204                 else if (sscanf(value, "%lf", &n) == 1) {
00205                     w = svg_units_convert(n, "pt");
00206                     wFlag = TRUE;
00207                 }
00208                 if (hFlag)
00209                     break;
00210             }
00211             else if (strcmp(attribute,"height") == 0) {
00212                 if (sscanf(value, "%lf%2s", &n, u) == 2) {
00213                     h = svg_units_convert(n, u);
00214                     hFlag = TRUE;
00215                 }
00216                 else if (sscanf(value, "%lf", &n) == 1) {
00217                     h = svg_units_convert(n, "pt");
00218                     hFlag = TRUE;
00219                 }
00220                 if (wFlag)
00221                     break;
00222             }
00223             else if (strcmp(attribute,"viewBox") == 0
00224               && sscanf(value, "%lf %lf %lf %lf", &x0,&y0,&x1,&y1) == 4) {
00225                 w = x1 - x0 + 1;
00226                 h = y1 - y0 + 1;
00227                 wFlag = TRUE;
00228                 hFlag = TRUE;
00229                 break;
00230             }
00231         }
00232     }
00233     us->dpi = 72;
00234     us->w = w;
00235     us->h = h;
00236 }
00237 
00238 static void png_size (usershape_t *us)
00239 {
00240     unsigned int w, h;
00241 
00242     us->dpi = 0;
00243     fseek(us->f, 16, SEEK_SET);
00244     if (get_int_msb_first(us->f, 4, &w) && get_int_msb_first(us->f, 4, &h)) {
00245         us->w = w;
00246         us->h = h;
00247     }
00248 }
00249 
00250 static void webp_size (usershape_t *us)
00251 {
00252     unsigned int w, h;
00253 
00254     us->dpi = 0;
00255     fseek(us->f, 15, SEEK_SET);
00256     if (fgetc(us->f) == 'X') { //VP8X
00257         fseek(us->f, 24, SEEK_SET);
00258         if (get_int_lsb_first(us->f, 4, &w) && get_int_lsb_first(us->f, 4, &h)) {
00259             us->w = w;
00260             us->h = h;
00261         }
00262     }
00263     else { //VP8
00264         fseek(us->f, 26, SEEK_SET);
00265         if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
00266             us->w = w;
00267             us->h = h;
00268         }
00269     }
00270 }
00271 
00272 static void gif_size (usershape_t *us)
00273 {
00274     unsigned int w, h;
00275 
00276     us->dpi = 0;
00277     fseek(us->f, 6, SEEK_SET);
00278     if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
00279         us->w = w;
00280         us->h = h;
00281     }
00282 }
00283 
00284 static void bmp_size (usershape_t *us) {
00285     unsigned int size_x_msw, size_x_lsw, size_y_msw, size_y_lsw;
00286 
00287     us->dpi = 0;
00288     fseek (us->f, 16, SEEK_SET);
00289     if ( get_int_lsb_first (us->f, 2, &size_x_msw) &&
00290          get_int_lsb_first (us->f, 2, &size_x_lsw) &&
00291          get_int_lsb_first (us->f, 2, &size_y_msw) &&
00292          get_int_lsb_first (us->f, 2, &size_y_lsw) ) {
00293         us->w = size_x_msw << 16 | size_x_lsw;
00294         us->h = size_y_msw << 16 | size_y_lsw;
00295     }
00296 }
00297 
00298 static void jpeg_size (usershape_t *us) {
00299     unsigned int marker, length, size_x, size_y, junk;
00300 
00301     /* These are the markers that follow 0xff in the file.
00302      * Other markers implicitly have a 2-byte length field that follows.
00303      */
00304     static unsigned char standalone_markers [] = {
00305         0x01,                       /* Temporary */
00306         0xd0, 0xd1, 0xd2, 0xd3,     /* Reset */
00307             0xd4, 0xd5, 0xd6,
00308             0xd7,
00309         0xd8,                       /* Start of image */
00310         0xd9,                       /* End of image */
00311         0
00312     };
00313 
00314     us->dpi = 0;
00315     while (TRUE) {
00316         /* Now we must be at a 0xff or at a series of 0xff's.
00317          * If that is not the case, or if we're at EOF, then there's
00318          * a parsing error.
00319          */
00320         if (! get_int_msb_first (us->f, 1, &marker))
00321             return;
00322 
00323         if (marker == 0xff)
00324             continue;
00325 
00326         /* Ok.. marker now read. If it is not a stand-alone marker,
00327          * then continue. If it's a Start Of Frame (0xc?), then we're there.
00328          * If it's another marker with a length field, then skip ahead
00329          * over that length field.
00330          */
00331 
00332         /* A stand-alone... */
00333         if (strchr ((char*)standalone_markers, marker))
00334             continue;
00335 
00336         /* Incase of a 0xc0 marker: */
00337         if (marker == 0xc0) {
00338             /* Skip length and 2 lengths. */
00339             if ( get_int_msb_first (us->f, 3, &junk)   &&
00340                  get_int_msb_first (us->f, 2, &size_x) &&
00341                  get_int_msb_first (us->f, 2, &size_y) ) {
00342 
00343             /* Store length. */
00344                 us->h = size_x;
00345                 us->w = size_y;
00346             }
00347             return;
00348         }
00349 
00350         /* Incase of a 0xc2 marker: */
00351         if (marker == 0xc2) {
00352             /* Skip length and one more byte */
00353             if (! get_int_msb_first (us->f, 3, &junk))
00354                 return;
00355 
00356             /* Get length and store. */
00357             if ( get_int_msb_first (us->f, 2, &size_x) &&
00358                  get_int_msb_first (us->f, 2, &size_y) ) {
00359                 us->h = size_x;
00360                 us->w = size_y;
00361             }
00362             return;
00363         }
00364 
00365         /* Any other marker is assumed to be followed by 2 bytes length. */
00366         if (! get_int_msb_first (us->f, 2, &length))
00367             return;
00368 
00369         fseek (us->f, length - 2, SEEK_CUR);
00370     }
00371 }
00372 
00373 static void ps_size (usershape_t *us)
00374 {
00375     char line[BUFSIZ];
00376     boolean saw_bb;
00377     int lx, ly, ux, uy;
00378     char* linep;
00379 
00380     us->dpi = POINTS_PER_INCH;
00381     fseek(us->f, 0, SEEK_SET);
00382     saw_bb = FALSE;
00383     while (fgets(line, sizeof(line), us->f)) {
00384         /* PostScript accepts \r as EOL, so using fgets () and looking for a
00385          * bounding box comment at the beginning doesn't work in this case. 
00386          * As a heuristic, we first search for a bounding box comment in line.
00387          * This obviously fails if not all of the numbers make it into the
00388          * current buffer. This shouldn't be a problem, as the comment is
00389          * typically near the beginning, and so should be read within the first
00390          * BUFSIZ bytes (even on Windows where this is 512).
00391          */
00392         if (!(linep = strstr (line, "%%BoundingBox:")))
00393             continue;
00394         if (sscanf (linep, "%%%%BoundingBox: %d %d %d %d", &lx, &ly, &ux, &uy) == 4) {
00395             saw_bb = TRUE;
00396             break;
00397         }
00398     }
00399     if (saw_bb) {
00400         us->x = lx;
00401         us->y = ly;
00402         us->w = ux - lx;
00403         us->h = uy - ly;
00404     }
00405 }
00406 
00407 static void usershape_close (Dict_t * dict, Void_t * p, Dtdisc_t * disc)
00408 {
00409     usershape_t *us = (usershape_t *)p;
00410 
00411     if (us->f)
00412         fclose(us->f);
00413     if (us->data && us->datafree)
00414         us->datafree(us);
00415     free (us);
00416 }
00417 
00418 static Dtdisc_t ImageDictDisc = {
00419     offsetof(usershape_t, name), /* key */
00420     -1,                         /* size */
00421     0,                          /* link offset */
00422     NIL(Dtmake_f),
00423     usershape_close,
00424     NIL(Dtcompar_f),
00425     NIL(Dthash_f),
00426     NIL(Dtmemory_f),
00427     NIL(Dtevent_f)
00428 };
00429 
00430 usershape_t *gvusershape_find(char *name)
00431 {
00432     usershape_t probe;
00433 
00434     if (!ImageDict)
00435         return NULL;
00436 
00437     probe.name = name;
00438     return (dtsearch(ImageDict, &probe));
00439 }
00440 
00441 #define MAX_USERSHAPE_FILES_OPEN 50
00442 boolean gvusershape_file_access(usershape_t *us)
00443 {
00444     static int usershape_files_open_cnt;
00445     const char *fn;
00446 
00447     assert(us);
00448     assert(us->name);
00449 
00450     if (us->f)
00451         fseek(us->f, 0, SEEK_SET);
00452     else {
00453         if ((fn = safefile(us->name))) {
00454 #ifndef WIN32
00455             us->f = fopen(fn, "r");
00456 #else
00457             us->f = fopen(fn, "rb");
00458 #endif
00459             if (us->f == NULL) {
00460                 agerr(AGWARN, "%s while opening %s\n", strerror(errno), fn);
00461                 return FALSE;
00462             }
00463             if (usershape_files_open_cnt >= MAX_USERSHAPE_FILES_OPEN)
00464                 us->nocache = TRUE;
00465             else
00466                 usershape_files_open_cnt++;
00467         }
00468     }
00469     return TRUE;
00470 }
00471 
00472 void gvusershape_file_release(usershape_t *us)
00473 {
00474     if (us->nocache) {
00475         if (us->f) {
00476             fclose(us->f);
00477             us->f = NULL;
00478         }
00479     }
00480 }
00481 
00482 static usershape_t *gvusershape_open (char *name)
00483 {
00484     usershape_t *us;
00485 
00486     if (!ImageDict)
00487         ImageDict = dtopen(&ImageDictDisc, Dttree);
00488 
00489     if (! (us = gvusershape_find(name))) {
00490         if (! (us = zmalloc(sizeof(usershape_t))))
00491             return NULL;
00492 
00493         us->name = name;
00494         if (!gvusershape_file_access(us)) 
00495             return NULL;
00496 
00497         switch(imagetype(us)) {
00498             case FT_NULL:
00499                 if (!(us->data = (void*)find_user_shape(us->name)))
00500                     agerr(AGWARN, "\"%s\" was not found as a file or as a shape library member\n", us->name);
00501                     free(us);
00502                     return NULL;
00503                 break;
00504             case FT_GIF:
00505                 gif_size(us);
00506                 break;
00507             case FT_PNG:
00508                 png_size(us);
00509                 break;
00510             case FT_BMP:
00511                 bmp_size(us);
00512                 break;
00513             case FT_JPEG:
00514                 jpeg_size(us);
00515                 break;
00516             case FT_PS:
00517                 ps_size(us);
00518                 break;
00519             case FT_WEBP:
00520                 webp_size(us);
00521                 break;
00522             case FT_SVG:
00523                 svg_size(us);
00524                 break;
00525             case FT_PDF:   /* no pdf_size code available */
00526             case FT_EPS:   /* no eps_size code available */
00527             default:
00528                 break;
00529         }
00530         dtinsert(ImageDict, us);
00531     }
00532 
00533     gvusershape_file_release(us);
00534 
00535     return us;
00536 }
00537 
00538 /* gvusershape_size_dpi:
00539  * Return image size in points.
00540  */
00541 point 
00542 gvusershape_size_dpi (usershape_t* us, pointf dpi)
00543 {
00544     point rv;
00545 
00546     if (!us) {
00547         rv.x = rv.y = -1;
00548     }
00549     else {
00550         if (us->dpi != 0) {
00551             dpi.x = dpi.y = us->dpi;
00552         }
00553         rv.x = us->w * POINTS_PER_INCH / dpi.x;
00554         rv.y = us->h * POINTS_PER_INCH / dpi.y;
00555     }
00556     return rv;
00557 }
00558 
00559 /* gvusershape_size:
00560  * Loads user image from file name if not already loaded.
00561  * Return image size in points.
00562  */
00563 point gvusershape_size(graph_t * g, char *name)
00564 {
00565     point rv;
00566     pointf dpi;
00567     static char* oldpath;
00568     usershape_t* us;
00569 
00570     /* no shape file, no shape size */
00571     if (!name || (*name == '\0')) {
00572         rv.x = rv.y = -1;
00573         return rv;
00574     }
00575 
00576     if (!HTTPServerEnVar && (oldpath != Gvimagepath)) {
00577         oldpath = Gvimagepath;
00578         if (ImageDict) {
00579             dtclose(ImageDict);
00580             ImageDict = NULL;
00581         }
00582     }
00583 
00584     if ((dpi.y = GD_drawing(g)->dpi) >= 1.0)
00585         dpi.x = dpi.y;
00586     else
00587         dpi.x = dpi.y = (double)DEFAULT_DPI;
00588 
00589     us = gvusershape_open (name);
00590     rv = gvusershape_size_dpi (us, dpi);
00591     return rv;
00592 }