Graphviz  2.35.20130930.0449
gvusershape.c
Go to the documentation of this file.
1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3 
4 /*************************************************************************
5  * Copyright (c) 2011 AT&T Intellectual Property
6  * All rights reserved. This program and the accompanying materials
7  * are made available under the terms of the Eclipse Public License v1.0
8  * which accompanies this distribution, and is available at
9  * http://www.eclipse.org/legal/epl-v10.html
10  *
11  * Contributors: See CVS logs. Details at http://www.graphviz.org/
12  *************************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
17 
18 #include <stddef.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <errno.h>
23 
24 #ifdef WIN32
25 #include <windows.h>
26 #define GLOB_NOSPACE 1 /* Ran out of memory. */
27 #define GLOB_ABORTED 2 /* Read error. */
28 #define GLOB_NOMATCH 3 /* No matches found. */
29 #define GLOB_NOSORT 4
30 #define DMKEY "Software\\Microsoft" //key to look for library dir
31 #include "regex_win32.h"
32 #else
33 #include <regex.h>
34 #endif
35 
36 #include "types.h"
37 #include "logic.h"
38 #include "memory.h"
39 #include "agxbuf.h"
40 
41 #define _BLD_gvc 1
42 #include "utils.h"
43 #include "gvplugin_loadimage.h"
44 
45 extern char *Gvimagepath;
46 extern char *HTTPServerEnVar;
47 extern shape_desc *find_user_shape(const char *);
48 
49 static Dict_t *ImageDict;
50 
51 typedef struct {
52  char *template;
53  int size;
54  int type;
55  char *stringtype;
56 } knowntype_t;
57 
58 #define HDRLEN 20
59 
60 #define PNG_MAGIC "\x89PNG\x0D\x0A\x1A\x0A"
61 #define PS_MAGIC "%!PS-Adobe-"
62 #define BMP_MAGIC "BM"
63 #define GIF_MAGIC "GIF8"
64 #define JPEG_MAGIC "\xFF\xD8\xFF\xE0"
65 #define PDF_MAGIC "%PDF-"
66 #define EPS_MAGIC "\xC5\xD0\xD3\xC6"
67 #define XML_MAGIC "<?xml"
68 #define SVG_MAGIC "<svg"
69 #define RIFF_MAGIC "RIFF"
70 #define WEBP_MAGIC "WEBP"
71 
72 static knowntype_t knowntypes[] = {
73  { PNG_MAGIC, sizeof(PNG_MAGIC)-1, FT_PNG, "png", },
74  { PS_MAGIC, sizeof(PS_MAGIC)-1, FT_PS, "ps", },
75  { BMP_MAGIC, sizeof(BMP_MAGIC)-1, FT_BMP, "bmp", },
76  { GIF_MAGIC, sizeof(GIF_MAGIC)-1, FT_GIF, "gif", },
77  { JPEG_MAGIC, sizeof(JPEG_MAGIC)-1, FT_JPEG, "jpeg", },
78  { PDF_MAGIC, sizeof(PDF_MAGIC)-1, FT_PDF, "pdf", },
79  { EPS_MAGIC, sizeof(EPS_MAGIC)-1, FT_EPS, "eps", },
80 /* { SVG_MAGIC, sizeof(SVG_MAGIC)-1, FT_SVG, "svg", }, - viewers expect xml preamble */
81  { XML_MAGIC, sizeof(XML_MAGIC)-1, FT_XML, "xml", },
82  { RIFF_MAGIC, sizeof(RIFF_MAGIC)-1, FT_RIFF, "riff", },
83 };
84 
85 static int imagetype (usershape_t *us)
86 {
87  char header[HDRLEN];
88  char line[200];
89  int i;
90 
91  if (us->f && fread(header, 1, HDRLEN, us->f) == HDRLEN) {
92  for (i = 0; i < sizeof(knowntypes) / sizeof(knowntype_t); i++) {
93  if (!memcmp (header, knowntypes[i].template, knowntypes[i].size)) {
94  us->stringtype = knowntypes[i].stringtype;
95  us->type = knowntypes[i].type;
96  if (us->type == FT_XML) {
97  /* check for SVG in case of XML */
98  while (fgets(line, sizeof(line), us->f) != NULL) {
99  if (!memcmp(line, SVG_MAGIC, sizeof(SVG_MAGIC)-1)) {
100  us->stringtype = "svg";
101  return (us->type = FT_SVG);
102  }
103  }
104  }
105  else if (us->type == FT_RIFF) {
106  /* check for WEBP in case of RIFF */
107  if (!memcmp(header+8, WEBP_MAGIC, sizeof(WEBP_MAGIC)-1)) {
108  us->stringtype = "webp";
109  return (us->type = FT_WEBP);
110  }
111  }
112  return us->type;
113  }
114  }
115  }
116 
117  us->stringtype = "(lib)";
118  us->type = FT_NULL;
119 
120  return FT_NULL;
121 }
122 
123 static boolean get_int_lsb_first (FILE *f, unsigned int sz, unsigned int *val)
124 {
125  int ch, i;
126 
127  *val = 0;
128  for (i = 0; i < sz; i++) {
129  ch = fgetc(f);
130  if (feof(f))
131  return FALSE;
132  *val |= (ch << 8*i);
133  }
134  return TRUE;
135 }
136 
137 static boolean get_int_msb_first (FILE *f, unsigned int sz, unsigned int *val)
138 {
139  int ch, i;
140 
141  *val = 0;
142  for (i = 0; i < sz; i++) {
143  ch = fgetc(f);
144  if (feof(f))
145  return FALSE;
146  *val <<= 8;
147  *val |= ch;
148  }
149  return TRUE;
150 }
151 
152 static unsigned int svg_units_convert(double n, char *u)
153 {
154  if (strcmp(u, "in") == 0)
155  return ROUND(n * POINTS_PER_INCH);
156  if (strcmp(u, "px") == 0)
157  return ROUND(n * POINTS_PER_INCH / 96);
158  if (strcmp(u, "pc") == 0)
159  return ROUND(n * POINTS_PER_INCH / 6);
160  if (strcmp(u, "pt") == 0 || strcmp(u, "\"") == 0) /* ugly!! - if there are no inits then the %2s get the trailing '"' */
161  return ROUND(n);
162  if (strcmp(u, "cm") == 0)
163  return ROUND(n * POINTS_PER_CM);
164  if (strcmp(u, "mm") == 0)
165  return ROUND(n * POINTS_PER_MM);
166  return 0;
167 }
168 
169 static char* svg_attr_value_re = "([a-z][a-zA-Z]*)=\"([^\"]*)\"";
170 static regex_t re, *pre = NULL;
171 
172 static void svg_size (usershape_t *us)
173 {
174  unsigned int w = 0, h = 0;
175  double n, x0, y0, x1, y1;
176  char u[10];
177  char *attribute, *value, *re_string;
178  char line[200];
179  boolean wFlag = FALSE, hFlag = FALSE;
180 #define RE_NMATCH 4
181  regmatch_t re_pmatch[RE_NMATCH];
182 
183  /* compile on first use */
184  if (! pre) {
185  if (regcomp(&re, svg_attr_value_re, REG_EXTENDED) != 0) {
186  agerr(AGERR,"cannot compile regular expression %s", svg_attr_value_re);
187  }
188  pre = &re;
189  }
190 
191  fseek(us->f, 0, SEEK_SET);
192  while (fgets(line, sizeof(line), us->f) != NULL && (!wFlag || !hFlag)) {
193  re_string = line;
194  while (regexec(&re, re_string, RE_NMATCH, re_pmatch, 0) == 0) {
195  re_string[re_pmatch[1].rm_eo] = '\0';
196  re_string[re_pmatch[2].rm_eo] = '\0';
197  attribute = re_string + re_pmatch[1].rm_so;
198  value = re_string + re_pmatch[2].rm_so;
199  re_string += re_pmatch[0].rm_eo + 1;
200 
201  if (strcmp(attribute,"width") == 0) {
202  if (sscanf(value, "%lf%2s", &n, u) == 2) {
203  w = svg_units_convert(n, u);
204  wFlag = TRUE;
205  }
206  else if (sscanf(value, "%lf", &n) == 1) {
207  w = svg_units_convert(n, "pt");
208  wFlag = TRUE;
209  }
210  if (hFlag)
211  break;
212  }
213  else if (strcmp(attribute,"height") == 0) {
214  if (sscanf(value, "%lf%2s", &n, u) == 2) {
215  h = svg_units_convert(n, u);
216  hFlag = TRUE;
217  }
218  else if (sscanf(value, "%lf", &n) == 1) {
219  h = svg_units_convert(n, "pt");
220  hFlag = TRUE;
221  }
222  if (wFlag)
223  break;
224  }
225  else if (strcmp(attribute,"viewBox") == 0
226  && sscanf(value, "%lf %lf %lf %lf", &x0,&y0,&x1,&y1) == 4) {
227  w = x1 - x0 + 1;
228  h = y1 - y0 + 1;
229  wFlag = TRUE;
230  hFlag = TRUE;
231  break;
232  }
233  }
234  }
235  us->dpi = 72;
236  us->w = w;
237  us->h = h;
238 }
239 
240 static void png_size (usershape_t *us)
241 {
242  unsigned int w, h;
243 
244  us->dpi = 0;
245  fseek(us->f, 16, SEEK_SET);
246  if (get_int_msb_first(us->f, 4, &w) && get_int_msb_first(us->f, 4, &h)) {
247  us->w = w;
248  us->h = h;
249  }
250 }
251 
252 static void webp_size (usershape_t *us)
253 {
254  unsigned int w, h;
255 
256  us->dpi = 0;
257  fseek(us->f, 15, SEEK_SET);
258  if (fgetc(us->f) == 'X') { //VP8X
259  fseek(us->f, 24, SEEK_SET);
260  if (get_int_lsb_first(us->f, 4, &w) && get_int_lsb_first(us->f, 4, &h)) {
261  us->w = w;
262  us->h = h;
263  }
264  }
265  else { //VP8
266  fseek(us->f, 26, SEEK_SET);
267  if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
268  us->w = w;
269  us->h = h;
270  }
271  }
272 }
273 
274 static void gif_size (usershape_t *us)
275 {
276  unsigned int w, h;
277 
278  us->dpi = 0;
279  fseek(us->f, 6, SEEK_SET);
280  if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
281  us->w = w;
282  us->h = h;
283  }
284 }
285 
286 static void bmp_size (usershape_t *us) {
287  unsigned int size_x_msw, size_x_lsw, size_y_msw, size_y_lsw;
288 
289  us->dpi = 0;
290  fseek (us->f, 16, SEEK_SET);
291  if ( get_int_lsb_first (us->f, 2, &size_x_msw) &&
292  get_int_lsb_first (us->f, 2, &size_x_lsw) &&
293  get_int_lsb_first (us->f, 2, &size_y_msw) &&
294  get_int_lsb_first (us->f, 2, &size_y_lsw) ) {
295  us->w = size_x_msw << 16 | size_x_lsw;
296  us->h = size_y_msw << 16 | size_y_lsw;
297  }
298 }
299 
300 static void jpeg_size (usershape_t *us) {
301  unsigned int marker, length, size_x, size_y, junk;
302 
303  /* These are the markers that follow 0xff in the file.
304  * Other markers implicitly have a 2-byte length field that follows.
305  */
306  static unsigned char standalone_markers [] = {
307  0x01, /* Temporary */
308  0xd0, 0xd1, 0xd2, 0xd3, /* Reset */
309  0xd4, 0xd5, 0xd6,
310  0xd7,
311  0xd8, /* Start of image */
312  0xd9, /* End of image */
313  0
314  };
315 
316  us->dpi = 0;
317  while (TRUE) {
318  /* Now we must be at a 0xff or at a series of 0xff's.
319  * If that is not the case, or if we're at EOF, then there's
320  * a parsing error.
321  */
322  if (! get_int_msb_first (us->f, 1, &marker))
323  return;
324 
325  if (marker == 0xff)
326  continue;
327 
328  /* Ok.. marker now read. If it is not a stand-alone marker,
329  * then continue. If it's a Start Of Frame (0xc?), then we're there.
330  * If it's another marker with a length field, then skip ahead
331  * over that length field.
332  */
333 
334  /* A stand-alone... */
335  if (strchr ((char*)standalone_markers, marker))
336  continue;
337 
338  /* Incase of a 0xc0 marker: */
339  if (marker == 0xc0) {
340  /* Skip length and 2 lengths. */
341  if ( get_int_msb_first (us->f, 3, &junk) &&
342  get_int_msb_first (us->f, 2, &size_x) &&
343  get_int_msb_first (us->f, 2, &size_y) ) {
344 
345  /* Store length. */
346  us->h = size_x;
347  us->w = size_y;
348  }
349  return;
350  }
351 
352  /* Incase of a 0xc2 marker: */
353  if (marker == 0xc2) {
354  /* Skip length and one more byte */
355  if (! get_int_msb_first (us->f, 3, &junk))
356  return;
357 
358  /* Get length and store. */
359  if ( get_int_msb_first (us->f, 2, &size_x) &&
360  get_int_msb_first (us->f, 2, &size_y) ) {
361  us->h = size_x;
362  us->w = size_y;
363  }
364  return;
365  }
366 
367  /* Any other marker is assumed to be followed by 2 bytes length. */
368  if (! get_int_msb_first (us->f, 2, &length))
369  return;
370 
371  fseek (us->f, length - 2, SEEK_CUR);
372  }
373 }
374 
375 static void ps_size (usershape_t *us)
376 {
377  char line[BUFSIZ];
378  boolean saw_bb;
379  int lx, ly, ux, uy;
380  char* linep;
381 
382  us->dpi = POINTS_PER_INCH;
383  fseek(us->f, 0, SEEK_SET);
384  saw_bb = FALSE;
385  while (fgets(line, sizeof(line), us->f)) {
386  /* PostScript accepts \r as EOL, so using fgets () and looking for a
387  * bounding box comment at the beginning doesn't work in this case.
388  * As a heuristic, we first search for a bounding box comment in line.
389  * This obviously fails if not all of the numbers make it into the
390  * current buffer. This shouldn't be a problem, as the comment is
391  * typically near the beginning, and so should be read within the first
392  * BUFSIZ bytes (even on Windows where this is 512).
393  */
394  if (!(linep = strstr (line, "%%BoundingBox:")))
395  continue;
396  if (sscanf (linep, "%%%%BoundingBox: %d %d %d %d", &lx, &ly, &ux, &uy) == 4) {
397  saw_bb = TRUE;
398  break;
399  }
400  }
401  if (saw_bb) {
402  us->x = lx;
403  us->y = ly;
404  us->w = ux - lx;
405  us->h = uy - ly;
406  }
407 }
408 
409 #define KEY "/MediaBox"
410 
411 typedef struct {
412  char* s;
413  char* buf;
414  FILE* fp;
415 } stream_t;
416 
417 static unsigned char
418 nxtc (stream_t* str)
419 {
420  if (fgets(str->buf, BUFSIZ, str->fp)) {
421  str->s = str->buf;
422  return *(str->s);
423  }
424  return '\0';
425 
426 }
427 
428 #define strc(x) (*(x->s)?*(x->s):nxtc(x))
429 #define stradv(x) (x->s++)
430 
431 static void
432 skipWS (stream_t* str)
433 {
434  unsigned char c;
435  while ((c = strc(str))) {
436  if (isspace(c)) stradv(str);
437  else return;
438  }
439 }
440 
441 static int
442 scanNum (char* tok, double* dp)
443 {
444  char* endp;
445  double d = strtod(tok, &endp);
446 
447  if (tok == endp) return 1;
448  *dp = d;
449  return 0;
450 }
451 
452 static void
453 getNum (stream_t* str, char* buf)
454 {
455  int len = 0;
456  char c;
457  skipWS(str);
458  while ((c = strc(str)) && (isdigit(c) || (c == '.'))) {
459  buf[len++] = c;
460  stradv(str);
461  if (len == BUFSIZ-1) break;
462  }
463  buf[len] = '\0';
464 
465  return;
466 }
467 
468 static int
469 boxof (stream_t* str, boxf* bp)
470 {
471  char tok[BUFSIZ];
472 
473  skipWS(str);
474  if (strc(str) != '[') return 1;
475  stradv(str);
476  getNum(str, tok);
477  if (scanNum(tok,&bp->LL.x)) return 1;
478  getNum(str, tok);
479  if (scanNum(tok,&bp->LL.y)) return 1;
480  getNum(str, tok);
481  if (scanNum(tok,&bp->UR.x)) return 1;
482  getNum(str, tok);
483  if (scanNum(tok,&bp->UR.y)) return 1;
484  return 0;
485 }
486 
487 static int
488 bboxPDF (FILE* fp, boxf* bp)
489 {
490  stream_t str;
491  char* s;
492  char buf[BUFSIZ];
493  while (fgets(buf, BUFSIZ, fp)) {
494  if ((s = strstr(buf,KEY))) {
495  str.buf = buf;
496  str.s = s+(sizeof(KEY)-1);
497  str.fp = fp;
498  return boxof(&str,bp);
499  }
500  }
501  return 1;
502 }
503 
504 static void pdf_size (usershape_t *us)
505 {
506  boxf bb;
507 
508  us->dpi = POINTS_PER_INCH;
509  fseek(us->f, 0, SEEK_SET);
510  if ( ! bboxPDF (us->f, &bb)) {
511  us->x = bb.LL.x;
512  us->y = bb.LL.y;
513  us->w = bb.UR.x - bb.LL.x;
514  us->h = bb.UR.y - bb.LL.y;
515  }
516 }
517 
518 static void usershape_close (Dict_t * dict, Void_t * p, Dtdisc_t * disc)
519 {
520  usershape_t *us = (usershape_t *)p;
521 
522  if (us->f)
523  fclose(us->f);
524  if (us->data && us->datafree)
525  us->datafree(us);
526  free (us);
527 }
528 
529 static Dtdisc_t ImageDictDisc = {
530  offsetof(usershape_t, name), /* key */
531  -1, /* size */
532  0, /* link offset */
533  NIL(Dtmake_f),
534  usershape_close,
535  NIL(Dtcompar_f),
536  NIL(Dthash_f),
537  NIL(Dtmemory_f),
538  NIL(Dtevent_f)
539 };
540 
542 {
543  usershape_t probe;
544 
545  if (!ImageDict)
546  return NULL;
547 
548  probe.name = name;
549  return (dtsearch(ImageDict, &probe));
550 }
551 
552 #define MAX_USERSHAPE_FILES_OPEN 50
554 {
555  static int usershape_files_open_cnt;
556  const char *fn;
557 
558  assert(us);
559  assert(us->name);
560 
561  if (us->f)
562  fseek(us->f, 0, SEEK_SET);
563  else {
564  if ((fn = safefile(us->name))) {
565 #ifndef WIN32
566  us->f = fopen(fn, "r");
567 #else
568  us->f = fopen(fn, "rb");
569 #endif
570  if (us->f == NULL) {
571  agerr(AGWARN, "%s while opening %s\n", strerror(errno), fn);
572  return FALSE;
573  }
574  if (usershape_files_open_cnt >= MAX_USERSHAPE_FILES_OPEN)
575  us->nocache = TRUE;
576  else
577  usershape_files_open_cnt++;
578  }
579  }
580  return TRUE;
581 }
582 
584 {
585  if (us->nocache) {
586  if (us->f) {
587  fclose(us->f);
588  us->f = NULL;
589  }
590  }
591 }
592 
593 static usershape_t *gvusershape_open (char *name)
594 {
595  usershape_t *us;
596 
597  if (!ImageDict)
598  ImageDict = dtopen(&ImageDictDisc, Dttree);
599 
600  if (! (us = gvusershape_find(name))) {
601  if (! (us = zmalloc(sizeof(usershape_t))))
602  return NULL;
603 
604  us->name = name;
605  if (!gvusershape_file_access(us))
606  return NULL;
607 
608  switch(imagetype(us)) {
609  case FT_NULL:
610  if (!(us->data = (void*)find_user_shape(us->name)))
611  agerr(AGWARN, "\"%s\" was not found as a file or as a shape library member\n", us->name);
612  free(us);
613  return NULL;
614  break;
615  case FT_GIF:
616  gif_size(us);
617  break;
618  case FT_PNG:
619  png_size(us);
620  break;
621  case FT_BMP:
622  bmp_size(us);
623  break;
624  case FT_JPEG:
625  jpeg_size(us);
626  break;
627  case FT_PS:
628  ps_size(us);
629  break;
630  case FT_WEBP:
631  webp_size(us);
632  break;
633  case FT_SVG:
634  svg_size(us);
635  break;
636  case FT_PDF:
637  pdf_size(us);
638  break;
639  case FT_EPS: /* no eps_size code available */
640  default:
641  break;
642  }
643  dtinsert(ImageDict, us);
644  }
645 
647 
648  return us;
649 }
650 
651 /* gvusershape_size_dpi:
652  * Return image size in points.
653  */
654 point
656 {
657  point rv;
658 
659  if (!us) {
660  rv.x = rv.y = -1;
661  }
662  else {
663  if (us->dpi != 0) {
664  dpi.x = dpi.y = us->dpi;
665  }
666  rv.x = us->w * POINTS_PER_INCH / dpi.x;
667  rv.y = us->h * POINTS_PER_INCH / dpi.y;
668  }
669  return rv;
670 }
671 
672 /* gvusershape_size:
673  * Loads user image from file name if not already loaded.
674  * Return image size in points.
675  */
677 {
678  point rv;
679  pointf dpi;
680  static char* oldpath;
681  usershape_t* us;
682 
683  /* no shape file, no shape size */
684  if (!name || (*name == '\0')) {
685  rv.x = rv.y = -1;
686  return rv;
687  }
688 
689  if (!HTTPServerEnVar && (oldpath != Gvimagepath)) {
690  oldpath = Gvimagepath;
691  if (ImageDict) {
692  dtclose(ImageDict);
693  ImageDict = NULL;
694  }
695  }
696 
697  if ((dpi.y = GD_drawing(g)->dpi) >= 1.0)
698  dpi.x = dpi.y;
699  else
700  dpi.x = dpi.y = (double)DEFAULT_DPI;
701 
702  us = gvusershape_open (name);
703  rv = gvusershape_size_dpi (us, dpi);
704  return rv;
705 }