Graphviz  2.29.20120524.0446
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               && sscanf(value, "%lf%2s", &n, u) == 2) {
00201                 w = svg_units_convert(n, u);
00202                 wFlag = TRUE;
00203                 if (hFlag)
00204                     break;
00205             }
00206             else if (strcmp(attribute,"height") == 0
00207               && sscanf(value, "%lf%2s", &n, u) == 2) {
00208                 h = svg_units_convert(n, u);
00209                 hFlag = TRUE;
00210                 if (wFlag)
00211                     break;
00212             }
00213             else if (strcmp(attribute,"viewBox") == 0
00214               && sscanf(value, "%lf %lf %lf %lf", &x0,&y0,&x1,&y1) == 4) {
00215                 w = x1 - x0 + 1;
00216                 h = y1 - y0 + 1;
00217                 wFlag = TRUE;
00218                 hFlag = TRUE;
00219                 break;
00220             }
00221         }
00222     }
00223     us->dpi = 72;
00224     us->w = w;
00225     us->h = h;
00226 }
00227 
00228 static void png_size (usershape_t *us)
00229 {
00230     unsigned int w, h;
00231 
00232     us->dpi = 0;
00233     fseek(us->f, 16, SEEK_SET);
00234     if (get_int_msb_first(us->f, 4, &w) && get_int_msb_first(us->f, 4, &h)) {
00235         us->w = w;
00236         us->h = h;
00237     }
00238 }
00239 
00240 static void webp_size (usershape_t *us)
00241 {
00242     unsigned int w, h;
00243 
00244     us->dpi = 0;
00245     fseek(us->f, 15, SEEK_SET);
00246     if (fgetc(us->f) == 'X') { //VP8X
00247         fseek(us->f, 24, SEEK_SET);
00248         if (get_int_lsb_first(us->f, 4, &w) && get_int_lsb_first(us->f, 4, &h)) {
00249             us->w = w;
00250             us->h = h;
00251         }
00252     }
00253     else { //VP8
00254         fseek(us->f, 26, SEEK_SET);
00255         if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
00256             us->w = w;
00257             us->h = h;
00258         }
00259     }
00260 }
00261 
00262 static void gif_size (usershape_t *us)
00263 {
00264     unsigned int w, h;
00265 
00266     us->dpi = 0;
00267     fseek(us->f, 6, SEEK_SET);
00268     if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
00269         us->w = w;
00270         us->h = h;
00271     }
00272 }
00273 
00274 static void bmp_size (usershape_t *us) {
00275     unsigned int size_x_msw, size_x_lsw, size_y_msw, size_y_lsw;
00276 
00277     us->dpi = 0;
00278     fseek (us->f, 16, SEEK_SET);
00279     if ( get_int_lsb_first (us->f, 2, &size_x_msw) &&
00280          get_int_lsb_first (us->f, 2, &size_x_lsw) &&
00281          get_int_lsb_first (us->f, 2, &size_y_msw) &&
00282          get_int_lsb_first (us->f, 2, &size_y_lsw) ) {
00283         us->w = size_x_msw << 16 | size_x_lsw;
00284         us->h = size_y_msw << 16 | size_y_lsw;
00285     }
00286 }
00287 
00288 static void jpeg_size (usershape_t *us) {
00289     unsigned int marker, length, size_x, size_y, junk;
00290 
00291     /* These are the markers that follow 0xff in the file.
00292      * Other markers implicitly have a 2-byte length field that follows.
00293      */
00294     static unsigned char standalone_markers [] = {
00295         0x01,                       /* Temporary */
00296         0xd0, 0xd1, 0xd2, 0xd3,     /* Reset */
00297             0xd4, 0xd5, 0xd6,
00298             0xd7,
00299         0xd8,                       /* Start of image */
00300         0xd9,                       /* End of image */
00301         0
00302     };
00303 
00304     us->dpi = 0;
00305     while (TRUE) {
00306         /* Now we must be at a 0xff or at a series of 0xff's.
00307          * If that is not the case, or if we're at EOF, then there's
00308          * a parsing error.
00309          */
00310         if (! get_int_msb_first (us->f, 1, &marker))
00311             return;
00312 
00313         if (marker == 0xff)
00314             continue;
00315 
00316         /* Ok.. marker now read. If it is not a stand-alone marker,
00317          * then continue. If it's a Start Of Frame (0xc?), then we're there.
00318          * If it's another marker with a length field, then skip ahead
00319          * over that length field.
00320          */
00321 
00322         /* A stand-alone... */
00323         if (strchr ((char*)standalone_markers, marker))
00324             continue;
00325 
00326         /* Incase of a 0xc0 marker: */
00327         if (marker == 0xc0) {
00328             /* Skip length and 2 lengths. */
00329             if ( get_int_msb_first (us->f, 3, &junk)   &&
00330                  get_int_msb_first (us->f, 2, &size_x) &&
00331                  get_int_msb_first (us->f, 2, &size_y) ) {
00332 
00333             /* Store length. */
00334                 us->h = size_x;
00335                 us->w = size_y;
00336             }
00337             return;
00338         }
00339 
00340         /* Incase of a 0xc2 marker: */
00341         if (marker == 0xc2) {
00342             /* Skip length and one more byte */
00343             if (! get_int_msb_first (us->f, 3, &junk))
00344                 return;
00345 
00346             /* Get length and store. */
00347             if ( get_int_msb_first (us->f, 2, &size_x) &&
00348                  get_int_msb_first (us->f, 2, &size_y) ) {
00349                 us->h = size_x;
00350                 us->w = size_y;
00351             }
00352             return;
00353         }
00354 
00355         /* Any other marker is assumed to be followed by 2 bytes length. */
00356         if (! get_int_msb_first (us->f, 2, &length))
00357             return;
00358 
00359         fseek (us->f, length - 2, SEEK_CUR);
00360     }
00361 }
00362 
00363 static void ps_size (usershape_t *us)
00364 {
00365     char line[BUFSIZ];
00366     boolean saw_bb;
00367     int lx, ly, ux, uy;
00368     char* linep;
00369 
00370     us->dpi = POINTS_PER_INCH;
00371     fseek(us->f, 0, SEEK_SET);
00372     saw_bb = FALSE;
00373     while (fgets(line, sizeof(line), us->f)) {
00374         /* PostScript accepts \r as EOL, so using fgets () and looking for a
00375          * bounding box comment at the beginning doesn't work in this case. 
00376          * As a heuristic, we first search for a bounding box comment in line.
00377          * This obviously fails if not all of the numbers make it into the
00378          * current buffer. This shouldn't be a problem, as the comment is
00379          * typically near the beginning, and so should be read within the first
00380          * BUFSIZ bytes (even on Windows where this is 512).
00381          */
00382         if (!(linep = strstr (line, "%%BoundingBox:")))
00383             continue;
00384         if (sscanf (linep, "%%%%BoundingBox: %d %d %d %d", &lx, &ly, &ux, &uy) == 4) {
00385             saw_bb = TRUE;
00386             break;
00387         }
00388     }
00389     if (saw_bb) {
00390         us->x = lx;
00391         us->y = ly;
00392         us->w = ux - lx;
00393         us->h = uy - ly;
00394     }
00395 }
00396 
00397 static void usershape_close (Dict_t * dict, Void_t * p, Dtdisc_t * disc)
00398 {
00399     usershape_t *us = (usershape_t *)p;
00400 
00401     if (us->f)
00402         fclose(us->f);
00403     if (us->data && us->datafree)
00404         us->datafree(us);
00405     free (us);
00406 }
00407 
00408 static Dtdisc_t ImageDictDisc = {
00409     offsetof(usershape_t, name), /* key */
00410     -1,                         /* size */
00411     0,                          /* link offset */
00412     NIL(Dtmake_f),
00413     usershape_close,
00414     NIL(Dtcompar_f),
00415     NIL(Dthash_f),
00416     NIL(Dtmemory_f),
00417     NIL(Dtevent_f)
00418 };
00419 
00420 usershape_t *gvusershape_find(char *name)
00421 {
00422     usershape_t probe;
00423 
00424     if (!ImageDict)
00425         return NULL;
00426 
00427     probe.name = name;
00428     return (dtsearch(ImageDict, &probe));
00429 }
00430 
00431 #define MAX_USERSHAPE_FILES_OPEN 50
00432 boolean gvusershape_file_access(usershape_t *us)
00433 {
00434     static int usershape_files_open_cnt;
00435     const char *fn;
00436 
00437     assert(us);
00438     assert(us->name);
00439 
00440     if (us->f)
00441         fseek(us->f, 0, SEEK_SET);
00442     else {
00443         if ((fn = safefile(us->name))) {
00444 #ifndef WIN32
00445             us->f = fopen(fn, "r");
00446 #else
00447             us->f = fopen(fn, "rb");
00448 #endif
00449             if (us->f == NULL) {
00450                 agerr(AGWARN, "%s while opening %s\n", strerror(errno), fn);
00451                 return FALSE;
00452             }
00453             if (usershape_files_open_cnt >= MAX_USERSHAPE_FILES_OPEN)
00454                 us->nocache = TRUE;
00455             else
00456                 usershape_files_open_cnt++;
00457         }
00458     }
00459     return TRUE;
00460 }
00461 
00462 void gvusershape_file_release(usershape_t *us)
00463 {
00464     if (us->nocache) {
00465         if (us->f) {
00466             fclose(us->f);
00467             us->f = NULL;
00468         }
00469     }
00470 }
00471 
00472 static usershape_t *gvusershape_open (char *name)
00473 {
00474     usershape_t *us;
00475 
00476     if (!ImageDict)
00477         ImageDict = dtopen(&ImageDictDisc, Dttree);
00478 
00479     if (! (us = gvusershape_find(name))) {
00480         if (! (us = zmalloc(sizeof(usershape_t))))
00481             return NULL;
00482 
00483         us->name = name;
00484         if (!gvusershape_file_access(us)) 
00485             return NULL;
00486 
00487         switch(imagetype(us)) {
00488             case FT_NULL:
00489                 if (!(us->data = (void*)find_user_shape(us->name)))
00490                     agerr(AGWARN, "\"%s\" was not found as a file or as a shape library member\n", us->name);
00491                     free(us);
00492                     return NULL;
00493                 break;
00494             case FT_GIF:
00495                 gif_size(us);
00496                 break;
00497             case FT_PNG:
00498                 png_size(us);
00499                 break;
00500             case FT_BMP:
00501                 bmp_size(us);
00502                 break;
00503             case FT_JPEG:
00504                 jpeg_size(us);
00505                 break;
00506             case FT_PS:
00507                 ps_size(us);
00508                 break;
00509             case FT_WEBP:
00510                 webp_size(us);
00511                 break;
00512             case FT_SVG:
00513                 svg_size(us);
00514                 break;
00515             case FT_PDF:   /* no pdf_size code available */
00516             case FT_EPS:   /* no eps_size code available */
00517             default:
00518                 break;
00519         }
00520         dtinsert(ImageDict, us);
00521     }
00522 
00523     gvusershape_file_release(us);
00524 
00525     return us;
00526 }
00527 
00528 /* gvusershape_size_dpi:
00529  * Return image size in points.
00530  */
00531 point 
00532 gvusershape_size_dpi (usershape_t* us, pointf dpi)
00533 {
00534     point rv;
00535 
00536     if (!us) {
00537         rv.x = rv.y = -1;
00538     }
00539     else {
00540         if (us->dpi != 0) {
00541             dpi.x = dpi.y = us->dpi;
00542         }
00543         rv.x = us->w * POINTS_PER_INCH / dpi.x;
00544         rv.y = us->h * POINTS_PER_INCH / dpi.y;
00545     }
00546     return rv;
00547 }
00548 
00549 /* gvusershape_size:
00550  * Loads user image from file name if not already loaded.
00551  * Return image size in points.
00552  */
00553 point gvusershape_size(graph_t * g, char *name)
00554 {
00555     point rv;
00556     pointf dpi;
00557     static char* oldpath;
00558 
00559     /* no shape file, no shape size */
00560     if (!name || (*name == '\0')) {
00561         rv.x = rv.y = -1;
00562         return rv;
00563     }
00564 
00565     if (!HTTPServerEnVar && (oldpath != Gvimagepath)) {
00566         oldpath = Gvimagepath;
00567         if (ImageDict) {
00568             dtclose(ImageDict);
00569             ImageDict = NULL;
00570         }
00571     }
00572 
00573     if ((dpi.y = GD_drawing(g)->dpi) >= 1.0)
00574         dpi.x = dpi.y;
00575     else
00576         dpi.x = dpi.y = (double)DEFAULT_DPI;
00577 
00578     return gvusershape_size_dpi (gvusershape_open (name), dpi);
00579 }