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#include "config.h"
15
16#include <stddef.h>
17#include <string.h>
18#include <stdlib.h>
19#include <ctype.h>
20#include <errno.h>
21
22#ifdef _WIN32
23#include <windows.h>
24#define GLOB_NOSPACE 1 /* Ran out of memory. */
25#define GLOB_ABORTED 2 /* Read error. */
26#define GLOB_NOMATCH 3 /* No matches found. */
27#define GLOB_NOSORT 4
28#define DMKEY "Software\\Microsoft" //key to look for library dir
29#endif
30
31#include <regex.h>
32#include "types.h"
33#include "logic.h"
34#include "memory.h"
35#include "agxbuf.h"
36
37#define _BLD_gvc 1
38#include "utils.h"
39#include "gvplugin_loadimage.h"
40
41extern char *Gvimagepath;
42extern char *HTTPServerEnVar;
43extern shape_desc *find_user_shape(const char *);
44
45static Dict_t *ImageDict;
46
47typedef struct {
48 char *template;
49 int size;
50 int type;
51 char *stringtype;
52} knowntype_t;
53
54#define HDRLEN 20
55
56#define PNG_MAGIC "\x89PNG\x0D\x0A\x1A\x0A"
57#define PS_MAGIC "%!PS-Adobe-"
58#define BMP_MAGIC "BM"
59#define GIF_MAGIC "GIF8"
60#define JPEG_MAGIC "\xFF\xD8\xFF\xE0"
61#define PDF_MAGIC "%PDF-"
62#define EPS_MAGIC "\xC5\xD0\xD3\xC6"
63#define XML_MAGIC "<?xml"
64#define SVG_MAGIC "<svg"
65#define RIFF_MAGIC "RIFF"
66#define WEBP_MAGIC "WEBP"
67//#define TIFF_MAGIC "II"
68#define ICO_MAGIC "\x00\x00\x01\x00"
69
70static knowntype_t knowntypes[] = {
71 { PNG_MAGIC, sizeof(PNG_MAGIC)-1, FT_PNG, "png", },
72 { PS_MAGIC, sizeof(PS_MAGIC)-1, FT_PS, "ps", },
73 { BMP_MAGIC, sizeof(BMP_MAGIC)-1, FT_BMP, "bmp", },
74 { GIF_MAGIC, sizeof(GIF_MAGIC)-1, FT_GIF, "gif", },
75 { JPEG_MAGIC, sizeof(JPEG_MAGIC)-1, FT_JPEG, "jpeg", },
76 { PDF_MAGIC, sizeof(PDF_MAGIC)-1, FT_PDF, "pdf", },
77 { EPS_MAGIC, sizeof(EPS_MAGIC)-1, FT_EPS, "eps", },
78/* { SVG_MAGIC, sizeof(SVG_MAGIC)-1, FT_SVG, "svg", }, - viewers expect xml preamble */
79 { XML_MAGIC, sizeof(XML_MAGIC)-1, FT_XML, "xml", },
80 { RIFF_MAGIC, sizeof(RIFF_MAGIC)-1, FT_RIFF, "riff", },
81 { ICO_MAGIC, sizeof(ICO_MAGIC)-1, FT_ICO, "ico", },
82// { TIFF_MAGIC, sizeof(TIFF_MAGIC)-1, FT_TIFF, "tiff", },
83};
84
85static 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
123static 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
137static 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
152static 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
169static char* svg_attr_value_re = "([a-z][a-zA-Z]*)=\"([^\"]*)\"";
170static regex_t re, *pre = NULL;
171
172static 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 = 0;
236 us->w = w;
237 us->h = h;
238}
239
240static 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
252static void ico_size (usershape_t *us)
253{
254 unsigned int w, h;
255
256 us->dpi = 0;
257 fseek(us->f, 6, SEEK_SET);
258 if (get_int_msb_first(us->f, 1, &w) && get_int_msb_first(us->f, 1, &h)) {
259 us->w = w;
260 us->h = h;
261 }
262}
263
264
265// FIXME - how to get the size of a tiff image?
266#if 0
267static void tiff_size (usershape_t *us)
268{
269 unsigned int w, h;
270
271 us->dpi = 0;
272 fseek(us->f, 6, SEEK_SET);
273 if (get_int_msb_first(us->f, 1, &w) && get_int_msb_first(us->f, 1, &h)) {
274 us->w = w;
275 us->h = h;
276 }
277}
278#endif
279
280static void webp_size (usershape_t *us)
281{
282 unsigned int w, h;
283
284 us->dpi = 0;
285 fseek(us->f, 15, SEEK_SET);
286 if (fgetc(us->f) == 'X') { //VP8X
287 fseek(us->f, 24, SEEK_SET);
288 if (get_int_lsb_first(us->f, 4, &w) && get_int_lsb_first(us->f, 4, &h)) {
289 us->w = w;
290 us->h = h;
291 }
292 }
293 else { //VP8
294 fseek(us->f, 26, SEEK_SET);
295 if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
296 us->w = w;
297 us->h = h;
298 }
299 }
300}
301
302static void gif_size (usershape_t *us)
303{
304 unsigned int w, h;
305
306 us->dpi = 0;
307 fseek(us->f, 6, SEEK_SET);
308 if (get_int_lsb_first(us->f, 2, &w) && get_int_lsb_first(us->f, 2, &h)) {
309 us->w = w;
310 us->h = h;
311 }
312}
313
314static void bmp_size (usershape_t *us) {
315 unsigned int size_x_msw, size_x_lsw, size_y_msw, size_y_lsw;
316
317 us->dpi = 0;
318 fseek (us->f, 16, SEEK_SET);
319 if ( get_int_lsb_first (us->f, 2, &size_x_msw) &&
320 get_int_lsb_first (us->f, 2, &size_x_lsw) &&
321 get_int_lsb_first (us->f, 2, &size_y_msw) &&
322 get_int_lsb_first (us->f, 2, &size_y_lsw) ) {
323 us->w = size_x_msw << 16 | size_x_lsw;
324 us->h = size_y_msw << 16 | size_y_lsw;
325 }
326}
327
328static void jpeg_size (usershape_t *us) {
329 unsigned int marker, length, size_x, size_y, junk;
330
331 /* These are the markers that follow 0xff in the file.
332 * Other markers implicitly have a 2-byte length field that follows.
333 */
334 static unsigned char standalone_markers [] = {
335 0x01, /* Temporary */
336 0xd0, 0xd1, 0xd2, 0xd3, /* Reset */
337 0xd4, 0xd5, 0xd6,
338 0xd7,
339 0xd8, /* Start of image */
340 0xd9, /* End of image */
341 0
342 };
343
344 us->dpi = 0;
345 while (TRUE) {
346 /* Now we must be at a 0xff or at a series of 0xff's.
347 * If that is not the case, or if we're at EOF, then there's
348 * a parsing error.
349 */
350 if (! get_int_msb_first (us->f, 1, &marker))
351 return;
352
353 if (marker == 0xff)
354 continue;
355
356 /* Ok.. marker now read. If it is not a stand-alone marker,
357 * then continue. If it's a Start Of Frame (0xc?), then we're there.
358 * If it's another marker with a length field, then skip ahead
359 * over that length field.
360 */
361
362 /* A stand-alone... */
363 if (strchr ((char*)standalone_markers, marker))
364 continue;
365
366 /* Incase of a 0xc0 marker: */
367 if (marker == 0xc0) {
368 /* Skip length and 2 lengths. */
369 if ( get_int_msb_first (us->f, 3, &junk) &&
370 get_int_msb_first (us->f, 2, &size_x) &&
371 get_int_msb_first (us->f, 2, &size_y) ) {
372
373 /* Store length. */
374 us->h = size_x;
375 us->w = size_y;
376 }
377 return;
378 }
379
380 /* Incase of a 0xc2 marker: */
381 if (marker == 0xc2) {
382 /* Skip length and one more byte */
383 if (! get_int_msb_first (us->f, 3, &junk))
384 return;
385
386 /* Get length and store. */
387 if ( get_int_msb_first (us->f, 2, &size_x) &&
388 get_int_msb_first (us->f, 2, &size_y) ) {
389 us->h = size_x;
390 us->w = size_y;
391 }
392 return;
393 }
394
395 /* Any other marker is assumed to be followed by 2 bytes length. */
396 if (! get_int_msb_first (us->f, 2, &length))
397 return;
398
399 fseek (us->f, length - 2, SEEK_CUR);
400 }
401}
402
403static void ps_size (usershape_t *us)
404{
405 char line[BUFSIZ];
406 boolean saw_bb;
407 int lx, ly, ux, uy;
408 char* linep;
409
410 us->dpi = 72;
411 fseek(us->f, 0, SEEK_SET);
412 saw_bb = FALSE;
413 while (fgets(line, sizeof(line), us->f)) {
414 /* PostScript accepts \r as EOL, so using fgets () and looking for a
415 * bounding box comment at the beginning doesn't work in this case.
416 * As a heuristic, we first search for a bounding box comment in line.
417 * This obviously fails if not all of the numbers make it into the
418 * current buffer. This shouldn't be a problem, as the comment is
419 * typically near the beginning, and so should be read within the first
420 * BUFSIZ bytes (even on Windows where this is 512).
421 */
422 if (!(linep = strstr (line, "%%BoundingBox:")))
423 continue;
424 if (sscanf (linep, "%%%%BoundingBox: %d %d %d %d", &lx, &ly, &ux, &uy) == 4) {
425 saw_bb = TRUE;
426 break;
427 }
428 }
429 if (saw_bb) {
430 us->x = lx;
431 us->y = ly;
432 us->w = ux - lx;
433 us->h = uy - ly;
434 }
435}
436
437#define KEY "/MediaBox"
438
439typedef struct {
440 char* s;
441 char* buf;
442 FILE* fp;
443} stream_t;
444
445static unsigned char
446nxtc (stream_t* str)
447{
448 if (fgets(str->buf, BUFSIZ, str->fp)) {
449 str->s = str->buf;
450 return *(str->s);
451 }
452 return '\0';
453
454}
455
456#define strc(x) (*(x->s)?*(x->s):nxtc(x))
457#define stradv(x) (x->s++)
458
459static void
460skipWS (stream_t* str)
461{
462 unsigned char c;
463 while ((c = strc(str))) {
464 if (isspace(c)) stradv(str);
465 else return;
466 }
467}
468
469static int
470scanNum (char* tok, double* dp)
471{
472 char* endp;
473 double d = strtod(tok, &endp);
474
475 if (tok == endp) return 1;
476 *dp = d;
477 return 0;
478}
479
480static void
481getNum (stream_t* str, char* buf)
482{
483 int len = 0;
484 char c;
485 skipWS(str);
486 while ((c = strc(str)) && (isdigit(c) || (c == '.'))) {
487 buf[len++] = c;
488 stradv(str);
489 if (len == BUFSIZ-1) break;
490 }
491 buf[len] = '\0';
492
493 return;
494}
495
496static int
497boxof (stream_t* str, boxf* bp)
498{
499 char tok[BUFSIZ];
500
501 skipWS(str);
502 if (strc(str) != '[') return 1;
503 stradv(str);
504 getNum(str, tok);
505 if (scanNum(tok,&bp->LL.x)) return 1;
506 getNum(str, tok);
507 if (scanNum(tok,&bp->LL.y)) return 1;
508 getNum(str, tok);
509 if (scanNum(tok,&bp->UR.x)) return 1;
510 getNum(str, tok);
511 if (scanNum(tok,&bp->UR.y)) return 1;
512 return 0;
513}
514
515static int
516bboxPDF (FILE* fp, boxf* bp)
517{
518 stream_t str;
519 char* s;
520 char buf[BUFSIZ];
521 while (fgets(buf, BUFSIZ, fp)) {
522 if ((s = strstr(buf,KEY))) {
523 str.buf = buf;
524 str.s = s+(sizeof(KEY)-1);
525 str.fp = fp;
526 return boxof(&str,bp);
527 }
528 }
529 return 1;
530}
531
532static void pdf_size (usershape_t *us)
533{
534 boxf bb;
535
536 us->dpi = 0;
537 fseek(us->f, 0, SEEK_SET);
538 if ( ! bboxPDF (us->f, &bb)) {
539 us->x = bb.LL.x;
540 us->y = bb.LL.y;
541 us->w = bb.UR.x - bb.LL.x;
542 us->h = bb.UR.y - bb.LL.y;
543 }
544}
545
546static void usershape_close (Dict_t * dict, void * p, Dtdisc_t * disc)
547{
548 usershape_t *us = (usershape_t *)p;
549
550 if (us->f)
551 fclose(us->f);
552 if (us->data && us->datafree)
553 us->datafree(us);
554 free (us);
555}
556
557static Dtdisc_t ImageDictDisc = {
558 offsetof(usershape_t, name), /* key */
559 -1, /* size */
560 0, /* link offset */
561 NIL(Dtmake_f),
562 usershape_close,
563 NIL(Dtcompar_f),
564 NIL(Dthash_f),
565 NIL(Dtmemory_f),
566 NIL(Dtevent_f)
567};
568
569usershape_t *gvusershape_find(const char *name)
570{
571 usershape_t *us;
572
573 assert(name);
574 assert(name[0]);
575
576 if (!ImageDict)
577 return NULL;
578
579 us = dtmatch(ImageDict, name);
580 return us;
581}
582
583#define MAX_USERSHAPE_FILES_OPEN 50
584boolean gvusershape_file_access(usershape_t *us)
585{
586 static int usershape_files_open_cnt;
587 const char *fn;
588
589 assert(us);
590 assert(us->name);
591 assert(us->name[0]);
592
593 if (us->f)
594 fseek(us->f, 0, SEEK_SET);
595 else {
596 if (! (fn = safefile(us->name))) {
597 agerr(AGWARN, "Filename \"%s\" is unsafe\n", us->name);
598 return FALSE;
599 }
600#ifndef _WIN32
601 us->f = fopen(fn, "r");
602#else
603 us->f = fopen(fn, "rb");
604#endif
605 if (us->f == NULL) {
606 agerr(AGWARN, "%s while opening %s\n", strerror(errno), fn);
607 return FALSE;
608 }
609 if (usershape_files_open_cnt >= MAX_USERSHAPE_FILES_OPEN)
610 us->nocache = TRUE;
611 else
612 usershape_files_open_cnt++;
613 }
614 assert(us->f);
615 return TRUE;
616}
617
618void gvusershape_file_release(usershape_t *us)
619{
620 if (us->nocache) {
621 if (us->f) {
622 fclose(us->f);
623 us->f = NULL;
624 }
625 }
626}
627
628static void freeUsershape (usershape_t* us)
629{
630 if (us->name) agstrfree(0, (char*)us->name);
631 free (us);
632}
633
634static usershape_t *gvusershape_open (const char *name)
635{
636 usershape_t *us;
637
638 assert(name);
639
640 if (!ImageDict)
641 ImageDict = dtopen(&ImageDictDisc, Dttree);
642
643 if (! (us = gvusershape_find(name))) {
644 if (! (us = zmalloc(sizeof(usershape_t))))
645 return NULL;
646
647 us->name = agstrdup (0, (char*)name);
648 if (!gvusershape_file_access(us)) {
649 freeUsershape (us);
650 return NULL;
651 }
652
653 assert(us->f);
654
655 switch(imagetype(us)) {
656 case FT_NULL:
657 if (!(us->data = (void*)find_user_shape(us->name))) {
658 agerr(AGWARN, "\"%s\" was not found as a file or as a shape library member\n", us->name);
659 freeUsershape (us);
660 return NULL;
661 }
662 break;
663 case FT_GIF:
664 gif_size(us);
665 break;
666 case FT_PNG:
667 png_size(us);
668 break;
669 case FT_BMP:
670 bmp_size(us);
671 break;
672 case FT_JPEG:
673 jpeg_size(us);
674 break;
675 case FT_PS:
676 ps_size(us);
677 break;
678 case FT_WEBP:
679 webp_size(us);
680 break;
681 case FT_SVG:
682 svg_size(us);
683 break;
684 case FT_PDF:
685 pdf_size(us);
686 break;
687 case FT_ICO:
688 ico_size(us);
689 break;
690// case FT_TIFF:
691// tiff_size(us);
692// break;
693 case FT_EPS: /* no eps_size code available */
694 default:
695 break;
696 }
697 gvusershape_file_release(us);
698 dtinsert(ImageDict, us);
699 return us;
700 }
701 gvusershape_file_release(us);
702 return us;
703}
704
705/* gvusershape_size_dpi:
706 * Return image size in points.
707 */
708point
709gvusershape_size_dpi (usershape_t* us, pointf dpi)
710{
711 point rv;
712
713 if (!us) {
714 rv.x = rv.y = -1;
715 }
716 else {
717 if (us->dpi != 0) {
718 dpi.x = dpi.y = us->dpi;
719 }
720 rv.x = us->w * POINTS_PER_INCH / dpi.x;
721 rv.y = us->h * POINTS_PER_INCH / dpi.y;
722 }
723 return rv;
724}
725
726/* gvusershape_size:
727 * Loads user image from file name if not already loaded.
728 * Return image size in points.
729 */
730point gvusershape_size(graph_t * g, char *name)
731{
732 point rv;
733 pointf dpi;
734 static char* oldpath;
735 usershape_t* us;
736
737 /* no shape file, no shape size */
738 if (!name || (*name == '\0')) {
739 rv.x = rv.y = -1;
740 return rv;
741 }
742
743 if (!HTTPServerEnVar && (oldpath != Gvimagepath)) {
744 oldpath = Gvimagepath;
745 if (ImageDict) {
746 dtclose(ImageDict);
747 ImageDict = NULL;
748 }
749 }
750
751 if ((dpi.y = GD_drawing(g)->dpi) >= 1.0)
752 dpi.x = dpi.y;
753 else
754 dpi.x = dpi.y = (double)DEFAULT_DPI;
755
756 us = gvusershape_open (name);
757 rv = gvusershape_size_dpi (us, dpi);
758 return rv;
759}
760