indent all the rest of the code, and add some typedefs to indent.pro
[rrdtool.git] / src / rrd_gfx.c
1 /****************************************************************************
2  * RRDtool 1.2.23  Copyright by Tobi Oetiker, 1997-2007
3  ****************************************************************************
4  * rrd_gfx.c  graphics wrapper for rrdtool
5   **************************************************************************/
6
7 /* #define DEBUG */
8
9 /* stupid MSVC doesnt support variadic macros = no debug for now! */
10 #ifdef _MSC_VER
11 # define RRDPRINTF()
12 #else
13 # ifdef DEBUG
14 #  define RRDPRINTF(...)  fprintf(stderr, __VA_ARGS__);
15 # else
16 #  define RRDPRINTF(...)
17 # endif                         /* DEBUG */
18 #endif                          /* _MSC_VER */
19 #include "rrd_tool.h"
20 #include <png.h>
21 #include <ft2build.h>
22 #include FT_FREETYPE_H
23 #include FT_GLYPH_H
24
25 #include "rrd_gfx.h"
26 #include "rrd_afm.h"
27 #include "unused.h"
28
29 /* lines are better drawn on the pixle than between pixles */
30 #define LINEOFFSET 0.5
31
32 #define USE_PDF_FAKE_ALPHA 1
33 #define USE_EPS_FAKE_ALPHA 1
34
35 typedef struct gfx_char_s *gfx_char;
36 struct gfx_char_s {
37     FT_UInt   index;    /* glyph index */
38     FT_Vector pos;      /* location from baseline in 26.6 */
39     FT_Glyph  image;    /* glyph bitmap */
40 };
41
42 typedef struct gfx_string_s *gfx_string;
43 struct gfx_string_s {
44     unsigned int width;
45     unsigned int height;
46     int       count;    /* number of characters */
47     gfx_char  glyphs;
48     size_t    num_glyphs;
49     FT_BBox   bbox;
50     FT_Matrix transform;
51 };
52
53 /* compute string bbox */
54 static void compute_string_bbox(
55     gfx_string string);
56
57 /* create a freetype glyph string */
58 gfx_string gfx_string_create(
59     gfx_canvas_t *canvas,
60     FT_Face face,
61     const char *text,
62     int rotation,
63     double tabwidth,
64     double size);
65
66 /* create a freetype glyph string */
67 static void gfx_string_destroy(
68     gfx_string string);
69
70 static
71 gfx_node_t *gfx_new_node(
72     gfx_canvas_t *canvas,
73     enum gfx_en type)
74 {
75     gfx_node_t *node = art_new(gfx_node_t, 1);
76
77     if (node == NULL)
78         return NULL;
79     node->type = type;
80     node->color = 0x0;  /* color of element  0xRRGGBBAA  alpha 0xff is solid */
81     node->size = 0.0;   /* font size, line width */
82     node->path = NULL;  /* path */
83     node->points = 0;
84     node->points_max = 0;
85     node->closed_path = 0;
86     node->filename = NULL;  /* font or image filename */
87     node->text = NULL;
88     node->x = 0.0;
89     node->y = 0.0;      /* position */
90     node->angle = 0;
91     node->halign = GFX_H_NULL;  /* text alignement */
92     node->valign = GFX_V_NULL;  /* text alignement */
93     node->tabwidth = 0.0;
94     node->next = NULL;
95     if (canvas->lastnode != NULL) {
96         canvas->lastnode->next = node;
97     }
98     if (canvas->firstnode == NULL) {
99         canvas->firstnode = node;
100     }
101     canvas->lastnode = node;
102     return node;
103 }
104
105 gfx_canvas_t *gfx_new_canvas(
106     void)
107 {
108     gfx_canvas_t *canvas = art_new(gfx_canvas_t, 1);
109
110     canvas->firstnode = NULL;
111     canvas->lastnode = NULL;
112     canvas->imgformat = IF_PNG; /* we default to PNG output */
113     canvas->interlaced = 0;
114     canvas->zoom = 1.0;
115     canvas->font_aa_threshold = -1.0;
116     canvas->aa_type = AA_NORMAL;
117     return canvas;
118 }
119
120 /* create a new line */
121 gfx_node_t *gfx_new_line(
122     gfx_canvas_t *canvas,
123     double X0,
124     double Y0,
125     double X1,
126     double Y1,
127     double width,
128     gfx_color_t color)
129 {
130     return gfx_new_dashed_line(canvas, X0, Y0, X1, Y1, width, color, 0, 0);
131 }
132
133 gfx_node_t *gfx_new_dashed_line(
134     gfx_canvas_t *canvas,
135     double X0,
136     double Y0,
137     double X1,
138     double Y1,
139     double width,
140     gfx_color_t color,
141     double dash_on,
142     double dash_off)
143 {
144
145     gfx_node_t *node;
146     ArtVpath *vec;
147
148     node = gfx_new_node(canvas, GFX_LINE);
149     if (node == NULL)
150         return NULL;
151     vec = art_new(ArtVpath, 3);
152     if (vec == NULL)
153         return NULL;
154     vec[0].code = ART_MOVETO_OPEN;
155     vec[0].x = X0 + LINEOFFSET;
156     vec[0].y = Y0 + LINEOFFSET;
157     vec[1].code = ART_LINETO;
158     vec[1].x = X1 + LINEOFFSET;
159     vec[1].y = Y1 + LINEOFFSET;
160     vec[2].code = ART_END;
161     vec[2].x = 0;
162     vec[2].y = 0;
163
164     node->points = 3;
165     node->points_max = 3;
166     node->color = color;
167     node->size = width;
168     node->dash_on = dash_on;
169     node->dash_off = dash_off;
170     node->path = vec;
171     return node;
172 }
173
174 /* create a new area */
175 gfx_node_t *gfx_new_area(
176     gfx_canvas_t *canvas,
177     double X0,
178     double Y0,
179     double X1,
180     double Y1,
181     double X2,
182     double Y2,
183     gfx_color_t color)
184 {
185
186     gfx_node_t *node;
187     ArtVpath *vec;
188
189     node = gfx_new_node(canvas, GFX_AREA);
190     if (node == NULL)
191         return NULL;
192     vec = art_new(ArtVpath, 5);
193     if (vec == NULL)
194         return NULL;
195     vec[0].code = ART_MOVETO;
196     vec[0].x = X0;
197     vec[0].y = Y0;
198     vec[1].code = ART_LINETO;
199     vec[1].x = X1;
200     vec[1].y = Y1;
201     vec[2].code = ART_LINETO;
202     vec[2].x = X2;
203     vec[2].y = Y2;
204     vec[3].code = ART_LINETO;
205     vec[3].x = X0;
206     vec[3].y = Y0;
207     vec[4].code = ART_END;
208     vec[4].x = 0;
209     vec[4].y = 0;
210
211     node->points = 5;
212     node->points_max = 5;
213     node->color = color;
214     node->path = vec;
215
216     return node;
217 }
218
219 /* add a point to a line or to an area */
220 int gfx_add_point(
221     gfx_node_t *node,
222     double x,
223     double y)
224 {
225     if (node == NULL)
226         return 1;
227     if (node->type == GFX_AREA) {
228         double    X0 = node->path[0].x;
229         double    Y0 = node->path[0].y;
230
231         node->points -= 2;
232         art_vpath_add_point(&(node->path),
233                             &(node->points),
234                             &(node->points_max), ART_LINETO, x, y);
235         art_vpath_add_point(&(node->path),
236                             &(node->points),
237                             &(node->points_max), ART_LINETO, X0, Y0);
238         art_vpath_add_point(&(node->path),
239                             &(node->points),
240                             &(node->points_max), ART_END, 0, 0);
241     } else if (node->type == GFX_LINE) {
242         node->points -= 1;
243         art_vpath_add_point(&(node->path),
244                             &(node->points),
245                             &(node->points_max),
246                             ART_LINETO, x + LINEOFFSET, y + LINEOFFSET);
247         art_vpath_add_point(&(node->path),
248                             &(node->points),
249                             &(node->points_max), ART_END, 0, 0);
250
251     } else {
252         /* can only add point to areas and lines */
253         return 1;
254     }
255     return 0;
256 }
257
258 void gfx_close_path(
259     gfx_node_t *node)
260 {
261     node->closed_path = 1;
262     if (node->path[0].code == ART_MOVETO_OPEN)
263         node->path[0].code = ART_MOVETO;
264 }
265
266 /* create a text node */
267 gfx_node_t *gfx_new_text(
268     gfx_canvas_t *canvas,
269     double x,
270     double y,
271     gfx_color_t color,
272     char *font,
273     double size,
274     double tabwidth,
275     double angle,
276     enum gfx_h_align_en h_align,
277     enum gfx_v_align_en v_align,
278     char *text)
279 {
280     gfx_node_t *node = gfx_new_node(canvas, GFX_TEXT);
281
282     node->text = strdup(text);
283     node->size = size;
284     node->filename = strdup(font);
285     node->x = x;
286     node->y = y;
287     node->angle = angle;
288     node->color = color;
289     node->tabwidth = tabwidth;
290     node->halign = h_align;
291     node->valign = v_align;
292 #if 0
293     /* debugging: show text anchor
294        green is along x-axis, red is downward y-axis */
295     if (1) {
296         double    a = 2 * M_PI * -node->angle / 360.0;
297         double    cos_a = cos(a);
298         double    sin_a = sin(a);
299         double    len = 3;
300
301         gfx_new_line(canvas,
302                      x, y, x + len * cos_a, y - len * sin_a, 0.2, 0x00FF0000);
303         gfx_new_line(canvas,
304                      x, y, x + len * sin_a, y + len * cos_a, 0.2, 0xFF000000);
305     }
306 #endif
307     return node;
308 }
309
310 int gfx_render(
311     gfx_canvas_t *canvas,
312     art_u32 width,
313     art_u32 height,
314     gfx_color_t background,
315     FILE * fp)
316 {
317     switch (canvas->imgformat) {
318     case IF_PNG:
319         return gfx_render_png(canvas, width, height, background, fp);
320     case IF_SVG:
321         return gfx_render_svg(canvas, width, height, background, fp);
322     case IF_EPS:
323         return gfx_render_eps(canvas, width, height, background, fp);
324     case IF_PDF:
325         return gfx_render_pdf(canvas, width, height, background, fp);
326     default:
327         return -1;
328     }
329 }
330
331 static void gfx_string_destroy(
332     gfx_string string)
333 {
334     unsigned int n;
335
336     if (string->glyphs) {
337         for (n = 0; n < string->num_glyphs; ++n)
338             FT_Done_Glyph(string->glyphs[n].image);
339         free(string->glyphs);
340     }
341     free(string);
342 }
343
344
345 double gfx_get_text_width(
346     gfx_canvas_t *canvas,
347     double start,
348     char *font,
349     double size,
350     double tabwidth,
351     char *text,
352     int rotation)
353 {
354     switch (canvas->imgformat) {
355     case IF_PNG:
356         return gfx_get_text_width_libart(canvas, start, font, size, tabwidth,
357                                          text, rotation);
358     case IF_SVG:       /* fall through */
359     case IF_EPS:
360     case IF_PDF:
361         return afm_get_text_width(start, font, size, tabwidth, text);
362     default:
363         return size * strlen(text);
364     }
365 }
366
367 double gfx_get_text_width_libart(
368     gfx_canvas_t *canvas,
369     double UNUSED(start),
370     char *font,
371     double size,
372     double tabwidth,
373     char *text,
374     int rotation)
375 {
376
377     int       error;
378     double    text_width = 0;
379     FT_Face   face;
380     FT_Library library = NULL;
381     gfx_string string;
382
383     FT_Init_FreeType(&library);
384     error = FT_New_Face(library, font, 0, &face);
385     if (error) {
386         FT_Done_FreeType(library);
387         return -1;
388     }
389     error = FT_Set_Char_Size(face, size * 64, size * 64, 100, 100);
390     if (error) {
391         FT_Done_FreeType(library);
392         return -1;
393     }
394     string = gfx_string_create(canvas, face, text, rotation, tabwidth, size);
395     text_width = string->width;
396     gfx_string_destroy(string);
397     FT_Done_FreeType(library);
398     return text_width / 64;
399 }
400
401 static void gfx_libart_close_path(
402     gfx_node_t *node,
403     ArtVpath ** vec)
404 {
405     /* libart must have end==start for closed paths,
406        even if using ART_MOVETO and not ART_MOVETO_OPEN
407        so add extra point which is the same as the starting point */
408     int       points_max = node->points;    /* scaled array has exact size */
409     int       points = node->points - 1;
410
411     art_vpath_add_point(vec, &points, &points_max, ART_LINETO,
412                         (**vec).x, (**vec).y);
413     art_vpath_add_point(vec, &points, &points_max, ART_END, 0, 0);
414 }
415
416
417 /* find bbox of a string */
418 static void compute_string_bbox(
419     gfx_string string)
420 {
421     unsigned int n;
422     FT_BBox   bbox;
423
424     bbox.xMin = bbox.yMin = 32000;
425     bbox.xMax = bbox.yMax = -32000;
426     for (n = 0; n < string->num_glyphs; n++) {
427         FT_BBox   glyph_bbox;
428
429         FT_Glyph_Get_CBox(string->glyphs[n].image, ft_glyph_bbox_gridfit,
430                           &glyph_bbox);
431         if (glyph_bbox.xMin < bbox.xMin) {
432             bbox.xMin = glyph_bbox.xMin;
433         }
434         if (glyph_bbox.yMin < bbox.yMin) {
435             bbox.yMin = glyph_bbox.yMin;
436         }
437         if (glyph_bbox.xMax > bbox.xMax) {
438             bbox.xMax = glyph_bbox.xMax;
439         }
440         if (glyph_bbox.yMax > bbox.yMax) {
441             bbox.yMax = glyph_bbox.yMax;
442         }
443     }
444     if (bbox.xMin > bbox.xMax) {
445         bbox.xMin = 0;
446         bbox.yMin = 0;
447         bbox.xMax = 0;
448         bbox.yMax = 0;
449     }
450     string->bbox.xMin = bbox.xMin;
451     string->bbox.xMax = bbox.xMax;
452     string->bbox.yMin = bbox.yMin;
453     string->bbox.yMax = bbox.yMax;
454 }
455
456 /* create a free type glyph string */
457 gfx_string gfx_string_create(
458     gfx_canvas_t *canvas,
459     FT_Face face,
460     const char *text,
461     int rotation,
462     double tabwidth,
463     double size)
464 {
465
466     FT_GlyphSlot slot = face->glyph;    /* a small shortcut */
467     FT_Bool   use_kerning;
468     FT_UInt   previous;
469     FT_Vector ft_pen;
470
471     gfx_string string = (gfx_string) malloc(sizeof(struct gfx_string_s));
472
473     gfx_char  glyph;    /* current glyph in table */
474     int       n;
475     int       error;
476     int       gottab = 0;
477
478 #ifdef HAVE_MBSTOWCS
479     wchar_t  *cstr;
480     size_t    clen = strlen(text) + 1;
481
482     cstr = malloc(sizeof(wchar_t) * clen);  /* yes we are allocating probably too much here, I know */
483     string->count = mbstowcs(cstr, text, clen);
484     if (string->count == -1) {
485         /* conversion did not work, so lets fall back to just use what we got */
486         string->count = clen - 1;
487         for (n = 0; text[n] != '\0'; n++) {
488             cstr[n] = (unsigned char) text[n];
489         }
490     }
491 #else
492     char     *cstr = strdup(text);
493
494     string->count = strlen(text);
495 #endif
496
497     ft_pen.x = 0;       /* start at (0,0) !! */
498     ft_pen.y = 0;
499
500
501     string->width = 0;
502     string->height = 0;
503     string->glyphs =
504         (gfx_char) calloc(string->count, sizeof(struct gfx_char_s));
505     string->num_glyphs = 0;
506     string->transform.xx =
507         (FT_Fixed) (cos(M_PI * (rotation) / 180.0) * 0x10000);
508     string->transform.xy =
509         (FT_Fixed) (-sin(M_PI * (rotation) / 180.0) * 0x10000);
510     string->transform.yx =
511         (FT_Fixed) (sin(M_PI * (rotation) / 180.0) * 0x10000);
512     string->transform.yy =
513         (FT_Fixed) (cos(M_PI * (rotation) / 180.0) * 0x10000);
514
515     use_kerning = FT_HAS_KERNING(face);
516     previous = 0;
517     glyph = string->glyphs;
518     for (n = 0; n < string->count; glyph++, n++) {
519         FT_Vector vec;
520
521         /* handle the tabs ...
522            have a witespace glyph inserted, but set its width such that the distance
523            of the new right edge is x times tabwidth from 0,0 where x is an integer. */
524         unsigned int letter = cstr[n];
525
526         letter = afm_fix_osx_charset(letter);   /* unsafe macro */
527
528         gottab = 0;
529         if (letter == '\\' && n + 1 < string->count && cstr[n + 1] == 't') {
530             /* we have a tab here so skip the backslash and
531                set t to ' ' so that we get a white space */
532             gottab = 1;
533             n++;
534             letter = ' ';
535         }
536         if (letter == '\t') {
537             letter = ' ';
538             gottab = 1;
539         }
540         /* initialize each struct gfx_char_s */
541         glyph->index = 0;
542         glyph->pos.x = 0;
543         glyph->pos.y = 0;
544         glyph->image = NULL;
545         glyph->index = FT_Get_Char_Index(face, letter);
546
547         /* compute glyph origin */
548         if (use_kerning && previous && glyph->index) {
549             FT_Vector kerning;
550
551             FT_Get_Kerning(face, previous, glyph->index,
552                            ft_kerning_default, &kerning);
553             ft_pen.x += kerning.x;
554             ft_pen.y += kerning.y;
555         }
556
557         /* load the glyph image (in its native format) */
558         /* for now, we take a monochrome glyph bitmap */
559         error =
560             FT_Load_Glyph(face, glyph->index,
561                           size >
562                           canvas->font_aa_threshold ? canvas->aa_type ==
563                           AA_NORMAL ? FT_LOAD_TARGET_NORMAL : canvas->
564                           aa_type ==
565                           AA_LIGHT ? FT_LOAD_TARGET_LIGHT :
566                           FT_LOAD_TARGET_MONO : FT_LOAD_TARGET_MONO);
567         if (error) {
568             RRDPRINTF("couldn't load glyph:  %c\n", letter)
569                 continue;
570         }
571         error = FT_Get_Glyph(slot, &glyph->image);
572         if (error) {
573             RRDPRINTF("couldn't get glyph %c from slot %d\n", letter,
574                       (int) slot)
575                 continue;
576         }
577         /* if we are in tabbing mode, we replace the tab with a space and shift the position
578            of the space so that its left edge is where the tab was supposed to land us */
579         if (gottab) {
580             /* we are in gridfitting mode so the calculations happen in 1/64 pixles */
581             ft_pen.x =
582                 tabwidth * 64.0 * (float) (1 +
583                                            (long) (ft_pen.x /
584                                                    (tabwidth * 64.0))) -
585                 slot->advance.x;
586         }
587         /* store current pen position */
588         glyph->pos.x = ft_pen.x;
589         glyph->pos.y = ft_pen.y;
590
591
592         ft_pen.x += slot->advance.x;
593         ft_pen.y += slot->advance.y;
594
595         /* rotate glyph */
596         vec = glyph->pos;
597         FT_Vector_Transform(&vec, &string->transform);
598         error = FT_Glyph_Transform(glyph->image, &string->transform, &vec);
599         if (error) {
600             RRDPRINTF("couldn't transform glyph id %d\n", letter)
601                 continue;
602         }
603
604         /* convert to a bitmap - destroy native image */
605         error =
606             FT_Glyph_To_Bitmap(&glyph->image,
607                                size >
608                                canvas->font_aa_threshold ? canvas->aa_type ==
609                                AA_NORMAL ? FT_RENDER_MODE_NORMAL : canvas->
610                                aa_type ==
611                                AA_LIGHT ? FT_RENDER_MODE_LIGHT :
612                                FT_RENDER_MODE_MONO : FT_RENDER_MODE_MONO, 0,
613                                1);
614         if (error) {
615             RRDPRINTF("couldn't convert glyph id %d to bitmap\n", letter)
616                 continue;
617         }
618
619         /* increment number of glyphs */
620         previous = glyph->index;
621         string->num_glyphs++;
622     }
623     free(cstr);
624 /*  printf ("number of glyphs = %d\n", string->num_glyphs);*/
625     compute_string_bbox(string);
626     /* the last character was a tab */
627     /* if (gottab) { */
628     string->width = ft_pen.x;
629     /* } else {
630        string->width = string->bbox.xMax - string->bbox.xMin;
631        } */
632     string->height = string->bbox.yMax - string->bbox.yMin;
633     return string;
634 }
635
636
637 static int gfx_save_png(
638     art_u8 * buffer,
639     FILE * fp,
640     long width,
641     long height,
642     long bytes_per_pixel);
643
644 /* render grafics into png image */
645
646 int gfx_render_png(
647     gfx_canvas_t *canvas,
648     art_u32 width,
649     art_u32 height,
650     gfx_color_t background,
651     FILE * fp)
652 {
653
654
655     FT_Library library;
656     gfx_node_t *node = canvas->firstnode;
657
658     /*
659        art_u8 red = background >> 24, green = (background >> 16) & 0xff;
660        art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
661      */
662     unsigned long pys_width = width * canvas->zoom;
663     unsigned long pys_height = height * canvas->zoom;
664     const int bytes_per_pixel = 4;
665     unsigned long rowstride = pys_width * bytes_per_pixel;  /* bytes per pixel */
666
667     /* fill that buffer with out background color */
668     gfx_color_t *buffp = art_new(gfx_color_t, pys_width * pys_height);
669     art_u8   *buffer = (art_u8 *) buffp;
670     unsigned long i;
671
672     for (i = 0; i < pys_width * pys_height; i++) {
673         *(buffp++) = background;
674     }
675     FT_Init_FreeType(&library);
676     while (node) {
677         switch (node->type) {
678         case GFX_LINE:
679         case GFX_AREA:{
680             ArtVpath *vec;
681             double    dst[6];
682             ArtSVP   *svp;
683
684             art_affine_scale(dst, canvas->zoom, canvas->zoom);
685             vec = art_vpath_affine_transform(node->path, dst);
686             if (node->closed_path)
687                 gfx_libart_close_path(node, &vec);
688             /* gfx_round_scaled_coordinates(vec); */
689             /* pvec = art_vpath_perturb(vec);
690                art_free(vec); */
691             if (node->type == GFX_LINE) {
692                 svp = art_svp_vpath_stroke(vec, ART_PATH_STROKE_JOIN_ROUND,
693                                            ART_PATH_STROKE_CAP_ROUND,
694                                            node->size * canvas->zoom, 4,
695                                            0.25);
696             } else {
697                 svp = art_svp_from_vpath(vec);
698                 /* this takes time and is unnecessary since we make
699                    sure elsewhere that the areas are going clock-whise */
700                 /*  svpt = art_svp_uncross( svp );
701                    art_svp_free(svp);
702                    svp  = art_svp_rewind_uncrossed(svpt,ART_WIND_RULE_NONZERO); 
703                    art_svp_free(svpt);
704                  */
705             }
706             art_free(vec);
707             /* this is from gnome since libart does not have this yet */
708             gnome_print_art_rgba_svp_alpha(svp, 0, 0, pys_width, pys_height,
709                                            node->color, buffer, rowstride,
710                                            NULL);
711             art_svp_free(svp);
712             break;
713         }
714         case GFX_TEXT:{
715             unsigned int n;
716             int       error;
717             art_u8    fcolor[4], falpha;
718             FT_Face   face;
719             gfx_char  glyph;
720             gfx_string string;
721             FT_Vector vec;  /* 26.6 */
722
723             float     pen_x = 0.0, pen_y = 0.0;
724
725             /* double x,y; */
726             long      ix, iy;
727
728             fcolor[0] = node->color >> 24;
729             fcolor[1] = (node->color >> 16) & 0xff;
730             fcolor[2] = (node->color >> 8) & 0xff;
731             falpha = node->color & 0xff;
732             error = FT_New_Face(library, (char *) node->filename, 0, &face);
733             if (error) {
734                 rrd_set_error("failed to load %s", node->filename);
735
736                 break;
737             }
738             error = FT_Set_Char_Size(face,  /* handle to face object            */
739                                      (long) (node->size * 64),
740                                      (long) (node->size * 64),
741                                      (long) (100 * canvas->zoom),
742                                      (long) (100 * canvas->zoom));
743             if (error) {
744                 FT_Done_Face(face);
745                 break;
746             }
747             pen_x = node->x * canvas->zoom;
748             pen_y = node->y * canvas->zoom;
749
750             string =
751                 gfx_string_create(canvas, face, node->text, node->angle,
752                                   node->tabwidth, node->size);
753             FT_Done_Face(face);
754
755             switch (node->halign) {
756             case GFX_H_RIGHT:
757                 vec.x = -string->bbox.xMax;
758                 break;
759             case GFX_H_CENTER:
760                 vec.x = abs(string->bbox.xMax) >= abs(string->bbox.xMin) ?
761                     -string->bbox.xMax / 2 : -string->bbox.xMin / 2;
762                 break;
763             case GFX_H_LEFT:
764                 vec.x = -string->bbox.xMin;
765                 break;
766             case GFX_H_NULL:
767                 vec.x = 0;
768                 break;
769             }
770
771             switch (node->valign) {
772             case GFX_V_TOP:
773                 vec.y = string->bbox.yMax;
774                 break;
775             case GFX_V_CENTER:
776                 vec.y = abs(string->bbox.yMax) >= abs(string->bbox.yMin) ?
777                     string->bbox.yMax / 2 : string->bbox.yMin / 2;
778                 break;
779             case GFX_V_BOTTOM:
780                 vec.y = 0;
781                 break;
782             case GFX_V_NULL:
783                 vec.y = 0;
784                 break;
785             }
786             pen_x += vec.x / 64;
787             pen_y += vec.y / 64;
788             glyph = string->glyphs;
789             for (n = 0; n < string->num_glyphs; n++, glyph++) {
790                 int       gr;
791                 FT_Glyph  image;
792                 FT_BitmapGlyph bit;
793
794                 /* long buf_x,comp_n; */
795                 /* make copy to transform */
796                 if (!glyph->image) {
797                     RRDPRINTF("no image\n")
798                         continue;
799                 }
800                 error = FT_Glyph_Copy(glyph->image, &image);
801                 if (error) {
802                     RRDPRINTF("couldn't copy image\n")
803                         continue;
804                 }
805
806                 /* transform it */
807                 vec = glyph->pos;
808                 FT_Vector_Transform(&vec, &string->transform);
809
810                 bit = (FT_BitmapGlyph) image;
811                 gr = bit->bitmap.num_grays - 1;
812 /* 
813                 buf_x = (pen_x + 0.5) + (double)bit->left;
814                 comp_n = buf_x + bit->bitmap.width > pys_width ? pys_width - buf_x : bit->bitmap.width;
815                 if (buf_x < 0 || buf_x >= (long)pys_width) continue;
816                 buf_x *=  bytes_per_pixel ;
817                 for (iy=0; iy < bit->bitmap.rows; iy++){                    
818                     long buf_y = iy+(pen_y+0.5)-(double)bit->top;
819                     if (buf_y < 0 || buf_y >= (long)pys_height) continue;
820                     buf_y *= rowstride;
821                     for (ix=0;ix < bit->bitmap.width;ix++){             
822                         *(letter + (ix*bytes_per_pixel+3)) = *(bit->bitmap.buffer + iy * bit->bitmap.width + ix);
823                     }
824                     art_rgba_rgba_composite(buffer + buf_y + buf_x ,letter,comp_n);
825                  }
826                  art_free(letter);
827 */
828                 switch (bit->bitmap.pixel_mode) {
829                 case FT_PIXEL_MODE_GRAY:
830                     for (iy = 0; iy < bit->bitmap.rows; iy++) {
831                         long      buf_y = iy + (pen_y + 0.5) - bit->top;
832
833                         if (buf_y < 0 || buf_y >= (long) pys_height)
834                             continue;
835                         buf_y *= rowstride;
836                         for (ix = 0; ix < bit->bitmap.width; ix++) {
837                             long      buf_x =
838                                 ix + (pen_x + 0.5) + (double) bit->left;
839                             art_u8    font_alpha;
840
841                             if (buf_x < 0 || buf_x >= (long) pys_width)
842                                 continue;
843                             buf_x *= bytes_per_pixel;
844                             font_alpha =
845                                 *(bit->bitmap.buffer +
846                                   iy * bit->bitmap.pitch + ix);
847                             if (font_alpha > 0) {
848                                 fcolor[3] =
849                                     (art_u8) ((double) font_alpha / gr *
850                                               falpha);
851                                 art_rgba_rgba_composite(buffer + buf_y +
852                                                         buf_x, fcolor, 1);
853                             }
854                         }
855                     }
856                     break;
857
858                 case FT_PIXEL_MODE_MONO:
859                     for (iy = 0; iy < bit->bitmap.rows; iy++) {
860                         long      buf_y = iy + (pen_y + 0.5) - bit->top;
861
862                         if (buf_y < 0 || buf_y >= (long) pys_height)
863                             continue;
864                         buf_y *= rowstride;
865                         for (ix = 0; ix < bit->bitmap.width; ix++) {
866                             long      buf_x =
867                                 ix + (pen_x + 0.5) + (double) bit->left;
868
869                             if (buf_x < 0 || buf_x >= (long) pys_width)
870                                 continue;
871                             buf_x *= bytes_per_pixel;
872                             if ((fcolor[3] =
873                                  falpha *
874                                  ((*
875                                    (bit->bitmap.buffer +
876                                     iy * bit->bitmap.pitch + ix / 8) >> (7 -
877                                                                          (ix %
878                                                                           8)))
879                                   & 1)) > 0)
880                                 art_rgba_rgba_composite(buffer + buf_y +
881                                                         buf_x, fcolor, 1);
882                         }
883                     }
884                     break;
885
886                 default:
887                     rrd_set_error("unknown freetype pixel mode: %d",
888                                   bit->bitmap.pixel_mode);
889                     break;
890                 }
891
892 /*
893                 for (iy=0; iy < bit->bitmap.rows; iy++){                    
894                     long buf_y = iy+(pen_y+0.5)-bit->top;
895                     if (buf_y < 0 || buf_y >= (long)pys_height) continue;
896                     buf_y *= rowstride;
897                     for (ix=0;ix < bit->bitmap.width;ix++){
898                         long buf_x = ix + (pen_x + 0.5) + (double)bit->left ;
899                         art_u8 font_alpha;
900                         
901                         if (buf_x < 0 || buf_x >= (long)pys_width) continue;
902                         buf_x *=  bytes_per_pixel ;
903                         font_alpha =  *(bit->bitmap.buffer + iy * bit->bitmap.width + ix);
904                         font_alpha =  (art_u8)((double)font_alpha / gr * falpha);
905                         for (iz = 0; iz < 3; iz++){
906                             art_u8 *orig = buffer + buf_y + buf_x + iz;
907                             *orig =  (art_u8)((double)*orig / gr * ( gr - font_alpha) +
908                                               (double)fcolor[iz] / gr * (font_alpha));
909                         }
910                     }
911                 }
912 */
913                 FT_Done_Glyph(image);
914             }
915             gfx_string_destroy(string);
916         }
917         }
918         node = node->next;
919     }
920     gfx_save_png(buffer, fp, pys_width, pys_height, bytes_per_pixel);
921     art_free(buffer);
922     FT_Done_FreeType(library);
923     return 0;
924 }
925
926 /* free memory used by nodes this will also remove memory required for
927    associated paths and svcs ... but not for text strings */
928 int gfx_destroy(
929     gfx_canvas_t *canvas)
930 {
931     gfx_node_t *next, *node = canvas->firstnode;
932
933     while (node) {
934         next = node->next;
935         art_free(node->path);
936         free(node->text);
937         free(node->filename);
938         art_free(node);
939         node = next;
940     }
941     art_free(canvas);
942     return 0;
943 }
944
945 static int gfx_save_png(
946     art_u8 * buffer,
947     FILE * fp,
948     long width,
949     long height,
950     long bytes_per_pixel)
951 {
952     png_structp png_ptr = NULL;
953     png_infop info_ptr = NULL;
954     int       i;
955     png_bytep *row_pointers;
956     int       rowstride = width * bytes_per_pixel;
957     png_text  text[2];
958
959     if (fp == NULL)
960         return (1);
961
962     png_ptr =
963         png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
964     if (png_ptr == NULL) {
965         return (1);
966     }
967     row_pointers = (png_bytepp) png_malloc(png_ptr,
968                                            height * sizeof(png_bytep));
969
970     info_ptr = png_create_info_struct(png_ptr);
971
972     if (info_ptr == NULL) {
973         png_free(png_ptr, row_pointers);
974         png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
975         return (1);
976     }
977
978     if (setjmp(png_jmpbuf(png_ptr))) {
979         /* If we get here, we had a problem writing the file */
980         png_destroy_write_struct(&png_ptr, &info_ptr);
981         return (1);
982     }
983
984     png_init_io(png_ptr, fp);
985     png_set_IHDR(png_ptr, info_ptr, width, height,
986                  8, PNG_COLOR_TYPE_RGB_ALPHA,
987                  PNG_INTERLACE_NONE,
988                  PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
989
990     text[0].key = "Software";
991     text[0].text =
992         "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
993     text[0].compression = PNG_TEXT_COMPRESSION_NONE;
994     png_set_text(png_ptr, info_ptr, text, 1);
995
996     /* lets make this fast while ending up with some increass in image size */
997     png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
998     /* png_set_filter(png_ptr,0,PNG_FILTER_SUB); */
999     png_set_compression_level(png_ptr, 1);
1000     /* png_set_compression_strategy(png_ptr,Z_HUFFMAN_ONLY); */
1001     /* 
1002        png_set_filter(png_ptr,PNG_FILTER_TYPE_BASE,PNG_FILTER_SUB);
1003        png_set_compression_strategy(png_ptr,Z_HUFFMAN_ONLY);
1004        png_set_compression_level(png_ptr,Z_BEST_SPEED); */
1005
1006     /* Write header data */
1007     png_write_info(png_ptr, info_ptr);
1008     for (i = 0; i < height; i++)
1009         row_pointers[i] = (png_bytep) (buffer + i * rowstride);
1010
1011     png_write_image(png_ptr, row_pointers);
1012     png_write_end(png_ptr, info_ptr);
1013     png_free(png_ptr, row_pointers);
1014     png_destroy_write_struct(&png_ptr, &info_ptr);
1015     return 1;
1016 }
1017
1018
1019 /* ----- COMMON ROUTINES for pdf, svg and eps */
1020 #define min3(a, b, c) (a < b ? (a < c ? a : c) : (b < c ? b : c))
1021 #define max3(a, b, c) (a > b ? (a > c ? a : c) : (b > c ? b : c))
1022
1023 #define PDF_CALC_DEBUG 0
1024
1025 typedef struct pdf_point {
1026     double    x, y;
1027 } pdf_point;
1028
1029 typedef struct {
1030     double    ascender, descender, baselineY;
1031     pdf_point sizep, minp, maxp;
1032     double    x, y, tdx, tdy;
1033     double    r, cos_r, sin_r;
1034     double    ma, mb, mc, md, mx, my;   /* pdf coord matrix */
1035     double    tmx, tmy; /* last 2 coords of text coord matrix */
1036 #if PDF_CALC_DEBUG
1037     int       debug;
1038 #endif
1039 } pdf_coords;
1040
1041 #if PDF_CALC_DEBUG
1042 static void pdf_dump_calc(
1043     gfx_node_t *node,
1044     pdf_coords * g)
1045 {
1046     fprintf(stderr, "PDF CALC =============================\n");
1047     fprintf(stderr, "   '%s' at %f pt\n", node->text, node->size);
1048     fprintf(stderr, "   align h = %s, v = %s,  sizep = %f, %f\n",
1049             (node->halign == GFX_H_RIGHT ? "r" :
1050              (node->halign == GFX_H_CENTER ? "c" :
1051               (node->halign == GFX_H_LEFT ? "l" : "N"))),
1052             (node->valign == GFX_V_TOP ? "t" :
1053              (node->valign == GFX_V_CENTER ? "c" :
1054               (node->valign == GFX_V_BOTTOM ? "b" : "N"))),
1055             g->sizep.x, g->sizep.y);
1056     fprintf(stderr, "   r = %f = %f, cos = %f, sin = %f\n",
1057             g->r, node->angle, g->cos_r, g->sin_r);
1058     fprintf(stderr, "   ascender = %f, descender = %f, baselineY = %f\n",
1059             g->ascender, g->descender, g->baselineY);
1060     fprintf(stderr, "   sizep: %f, %f\n", g->sizep.x, g->sizep.y);
1061     fprintf(stderr, "   minp: %f, %f     maxp = %f, %f\n",
1062             g->minp.x, g->minp.y, g->maxp.x, g->maxp.y);
1063     fprintf(stderr, "   x = %f, y = %f\n", g->x, g->y);
1064     fprintf(stderr, "   tdx = %f, tdy = %f\n", g->tdx, g->tdy);
1065     fprintf(stderr, "   GM = %f, %f, %f, %f, %f, %f\n",
1066             g->ma, g->mb, g->mc, g->md, g->mx, g->my);
1067     fprintf(stderr, "   TM = %f, %f, %f, %f, %f, %f\n",
1068             g->ma, g->mb, g->mc, g->md, g->tmx, g->tmy);
1069 }
1070 #endif
1071
1072 #if PDF_CALC_DEBUG
1073 #define PDF_DD(x) if (g->debug) x;
1074 #else
1075 #define PDF_DD(x)
1076 #endif
1077
1078 static void pdf_rotate(
1079     pdf_coords * g,
1080     pdf_point *p)
1081 {
1082     double    x2 = g->cos_r * p->x - g->sin_r * p->y;
1083     double    y2 = g->sin_r * p->x + g->cos_r * p->y;
1084
1085     PDF_DD(fprintf
1086            (stderr, "  rotate(%f, %f) -> %f, %f\n", p->x, p->y, x2, y2))
1087         p->x = x2;
1088     p->y = y2;
1089 }
1090
1091
1092 static void pdf_calc(
1093     int page_height,
1094     gfx_node_t *node,
1095     pdf_coords * g)
1096 {
1097     pdf_point a, b, c;
1098
1099 #if PDF_CALC_DEBUG
1100     /* g->debug = !!strstr(node->text, "RevProxy-1") || !!strstr(node->text, "08:00"); */
1101     g->debug = !!strstr(node->text, "sekunder")
1102         || !!strstr(node->text, "Web");
1103 #endif
1104     g->x = node->x;
1105     g->y = page_height - node->y;
1106     if (node->angle) {
1107         g->r = 2 * M_PI * node->angle / 360.0;
1108         g->cos_r = cos(g->r);
1109         g->sin_r = sin(g->r);
1110     } else {
1111         g->r = 0;
1112         g->cos_r = 1;
1113         g->sin_r = 0;
1114     }
1115     g->ascender = afm_get_ascender(node->filename, node->size);
1116     g->descender = afm_get_descender(node->filename, node->size);
1117     g->sizep.x =
1118         afm_get_text_width(0, node->filename, node->size, node->tabwidth,
1119                            node->text);
1120     /* seems like libart ignores the descender when doing vertial-align = bottom,
1121        so we do that too, to get labels v-aligning properly */
1122     g->sizep.y = -g->ascender;  /* + afm_get_descender(font->ps_font, node->size); */
1123     g->baselineY = -g->ascender - g->sizep.y / 2;
1124     a.x = g->sizep.x;
1125     a.y = g->sizep.y;
1126     b.x = g->sizep.x;
1127     b.y = 0;
1128     c.x = 0;
1129     c.y = g->sizep.y;
1130     if (node->angle) {
1131         pdf_rotate(g, &a);
1132         pdf_rotate(g, &b);
1133         pdf_rotate(g, &c);
1134     }
1135     g->minp.x = min3(a.x, b.x, c.x);
1136     g->minp.y = min3(a.y, b.y, c.y);
1137     g->maxp.x = max3(a.x, b.x, c.x);
1138     g->maxp.y = max3(a.y, b.y, c.y);
1139     /* The alignment parameters in node->valign and node->halign
1140        specifies the alignment in the non-rotated coordinate system
1141        (very unlike pdf/postscript), which complicates matters.
1142      */
1143     switch (node->halign) {
1144     case GFX_H_RIGHT:
1145         g->tdx = -g->maxp.x;
1146         break;
1147     case GFX_H_CENTER:
1148         g->tdx = -(g->maxp.x + g->minp.x) / 2;
1149         break;
1150     case GFX_H_LEFT:
1151         g->tdx = -g->minp.x;
1152         break;
1153     case GFX_H_NULL:
1154         g->tdx = 0;
1155         break;
1156     }
1157     switch (node->valign) {
1158     case GFX_V_TOP:
1159         g->tdy = -g->maxp.y;
1160         break;
1161     case GFX_V_CENTER:
1162         g->tdy = -(g->maxp.y + g->minp.y) / 2;
1163         break;
1164     case GFX_V_BOTTOM:
1165         g->tdy = -g->minp.y;
1166         break;
1167     case GFX_V_NULL:
1168         g->tdy = 0;
1169         break;
1170     }
1171     g->ma = g->cos_r;
1172     g->mb = g->sin_r;
1173     g->mc = -g->sin_r;
1174     g->md = g->cos_r;
1175     g->mx = g->x + g->tdx;
1176     g->my = g->y + g->tdy;
1177     g->tmx = g->mx - g->ascender * g->mc;
1178     g->tmy = g->my - g->ascender * g->md;
1179     PDF_DD(pdf_dump_calc(node, g))
1180 }
1181
1182 /* ------- SVG -------
1183    SVG reference:
1184    http://www.w3.org/TR/SVG/
1185 */
1186 static int svg_indent = 0;
1187 static int svg_single_line = 0;
1188 static const char *svg_default_font = "-dummy-";
1189 typedef struct svg_dash {
1190     int       dash_enable;
1191     double    dash_adjust, dash_len, dash_offset;
1192     double    adjusted_on, adjusted_off;
1193 } svg_dash;
1194
1195
1196 static void svg_print_indent(
1197     FILE * fp)
1198 {
1199     int       i;
1200
1201     for (i = svg_indent - svg_single_line; i > 0; i--) {
1202         putc(' ', fp);
1203         putc(' ', fp);
1204     }
1205 }
1206
1207 static void svg_start_tag(
1208     FILE * fp,
1209     const char *name)
1210 {
1211     svg_print_indent(fp);
1212     putc('<', fp);
1213     fputs(name, fp);
1214     svg_indent++;
1215 }
1216
1217 static void svg_close_tag_single_line(
1218     FILE * fp)
1219 {
1220     svg_single_line++;
1221     putc('>', fp);
1222 }
1223
1224 static void svg_close_tag(
1225     FILE * fp)
1226 {
1227     putc('>', fp);
1228     if (!svg_single_line)
1229         putc('\n', fp);
1230 }
1231
1232 static void svg_end_tag(
1233     FILE * fp,
1234     const char *name)
1235 {
1236     /* name is NULL if closing empty-node tag */
1237     svg_indent--;
1238     if (svg_single_line)
1239         svg_single_line--;
1240     else if (name)
1241         svg_print_indent(fp);
1242     if (name != NULL) {
1243         fputs("</", fp);
1244         fputs(name, fp);
1245     } else {
1246         putc('/', fp);
1247     }
1248     svg_close_tag(fp);
1249 }
1250
1251 static void svg_close_tag_empty_node(
1252     FILE * fp)
1253 {
1254     svg_end_tag(fp, NULL);
1255 }
1256
1257 static void svg_write_text(
1258     FILE * fp,
1259     const char *text)
1260 {
1261 #ifdef HAVE_MBSTOWCS
1262     size_t    clen;
1263     wchar_t  *p, *cstr, ch;
1264     int       text_count;
1265
1266     if (!text)
1267         return;
1268     clen = strlen(text) + 1;
1269     cstr = malloc(sizeof(wchar_t) * clen);
1270     text_count = mbstowcs(cstr, text, clen);
1271     if (text_count == -1)
1272         text_count = mbstowcs(cstr, "Enc-Err", 6);
1273     p = cstr;
1274 #else
1275     unsigned char *p = text;
1276     unsigned char *cstr;
1277     char      ch;
1278
1279     if (!p)
1280         return;
1281 #endif
1282     while (1) {
1283         ch = *p++;
1284         ch = afm_fix_osx_charset(ch);   /* unsafe macro */
1285         switch (ch) {
1286         case 0:
1287 #ifdef HAVE_MBSTOWCS
1288             free(cstr);
1289 #endif
1290             return;
1291         case '&':
1292             fputs("&amp;", fp);
1293             break;
1294         case '<':
1295             fputs("&lt;", fp);
1296             break;
1297         case '>':
1298             fputs("&gt;", fp);
1299             break;
1300         case '"':
1301             fputs("&quot;", fp);
1302             break;
1303         default:
1304             if (ch == 32) {
1305 #ifdef HAVE_MBSTOWCS
1306                 if (p <= cstr + 1 || !*p || *p == 32)
1307                     fputs("&#160;", fp);    /* non-breaking space in unicode */
1308                 else
1309 #endif
1310                     fputc(32, fp);
1311             } else if (ch < 32 || ch >= 127)
1312                 fprintf(fp, "&#%d;", (int) ch);
1313             else
1314                 putc((char) ch, fp);
1315         }
1316     }
1317 }
1318
1319 static void svg_format_number(
1320     char *buf,
1321     int bufsize,
1322     double d)
1323 {
1324     /* omit decimals if integer to reduce filesize */
1325     char     *p;
1326
1327     snprintf(buf, bufsize, "%.2f", d);
1328     p = buf;            /* doesn't trust snprintf return value */
1329     while (*p)
1330         p++;
1331     while (--p > buf) {
1332         char      ch = *p;
1333
1334         if (ch == '0') {
1335             *p = '\0';  /* zap trailing zeros */
1336             continue;
1337         }
1338         if (ch == '.')
1339             *p = '\0';  /* zap trailing dot */
1340         break;
1341     }
1342 }
1343
1344 static void svg_write_number(
1345     FILE * fp,
1346     double d)
1347 {
1348     char      buf[60];
1349
1350     svg_format_number(buf, sizeof(buf), d);
1351     fputs(buf, fp);
1352 }
1353
1354 static int svg_color_is_black(
1355     int c)
1356 {
1357     /* gfx_color_t is RRGGBBAA */
1358     return c == 0x000000FF;
1359 }
1360
1361 static void svg_write_color(
1362     FILE * fp,
1363     gfx_color_t c,
1364     const char *attr)
1365 {
1366     /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB and #RGB like html */
1367     gfx_color_t rrggbb = (int) ((c >> 8) & 0xFFFFFF);
1368     gfx_color_t opacity = c & 0xFF;
1369
1370     fprintf(fp, " %s=\"", attr);
1371     if ((rrggbb & 0x0F0F0F) == ((rrggbb >> 4) & 0x0F0F0F)) {
1372         /* css2 short form, #rgb is #rrggbb, not #r0g0b0 */
1373         fprintf(fp, "#%03lX", (((rrggbb >> 8) & 0xF00)
1374                                | ((rrggbb >> 4) & 0x0F0)
1375                                | (rrggbb & 0x00F)));
1376     } else {
1377         fprintf(fp, "#%06lX", rrggbb);
1378     }
1379     fputs("\"", fp);
1380     if (opacity != 0xFF) {
1381         fprintf(fp, " opacity=\"");
1382         svg_write_number(fp, opacity / 255.0);
1383         fputs("\"", fp);
1384     }
1385 }
1386
1387 static void svg_get_dash(
1388     gfx_node_t *node,
1389     svg_dash *d)
1390 {
1391     double    offset;
1392     int       mult;
1393
1394     if (node->dash_on <= 0 || node->dash_off <= 0) {
1395         d->dash_enable = 0;
1396         return;
1397     }
1398     d->dash_enable = 1;
1399     d->dash_len = node->dash_on + node->dash_off;
1400     /* dash on/off adjustment due to round caps */
1401     d->dash_adjust = 0.8 * node->size;
1402     d->adjusted_on = node->dash_on - d->dash_adjust;
1403     if (d->adjusted_on < 0.01)
1404         d->adjusted_on = 0.01;
1405     d->adjusted_off = d->dash_len - d->adjusted_on;
1406     /* dash offset calc */
1407     if (node->path[0].x == node->path[1].x) /* only good for horz/vert lines */
1408         offset = node->path[0].y;
1409     else
1410         offset = node->path[0].x;
1411     mult = (int) fabs(offset / d->dash_len);
1412     d->dash_offset = offset - mult * d->dash_len;
1413     if (node->path[0].x < node->path[1].x
1414         || node->path[0].y < node->path[1].y)
1415         d->dash_offset = d->dash_len - d->dash_offset;
1416 }
1417
1418 static int svg_dash_equal(
1419     svg_dash *a,
1420     svg_dash *b)
1421 {
1422     if (a->dash_enable != b->dash_enable)
1423         return 0;
1424     if (a->adjusted_on != b->adjusted_on)
1425         return 0;
1426     if (a->adjusted_off != b->adjusted_off)
1427         return 0;
1428     /* rest of properties will be the same when on+off are */
1429     return 1;
1430 }
1431
1432 static void svg_common_path_attributes(
1433     FILE * fp,
1434     gfx_node_t *node)
1435 {
1436     svg_dash  dash_info;
1437
1438     svg_get_dash(node, &dash_info);
1439     fputs(" stroke-width=\"", fp);
1440     svg_write_number(fp, node->size);
1441     fputs("\"", fp);
1442     svg_write_color(fp, node->color, "stroke");
1443     fputs(" fill=\"none\"", fp);
1444     if (dash_info.dash_enable) {
1445         if (dash_info.dash_offset != 0) {
1446             fputs(" stroke-dashoffset=\"", fp);
1447             svg_write_number(fp, dash_info.dash_offset);
1448             fputs("\"", fp);
1449         }
1450         fputs(" stroke-dasharray=\"", fp);
1451         svg_write_number(fp, dash_info.adjusted_on);
1452         fputs(",", fp);
1453         svg_write_number(fp, dash_info.adjusted_off);
1454         fputs("\"", fp);
1455     }
1456 }
1457
1458 static int svg_is_int_step(
1459     double a,
1460     double b)
1461 {
1462     double    diff = fabs(a - b);
1463
1464     return floor(diff) == diff;
1465 }
1466
1467 static int svg_path_straight_segment(
1468     FILE * fp,
1469     double lastA,
1470     double currentA,
1471     double currentB,
1472     gfx_node_t *node,
1473     int segment_idx,
1474     int isx,
1475     char absChar,
1476     char relChar)
1477 {
1478     if (!svg_is_int_step(lastA, currentA)) {
1479         putc(absChar, fp);
1480         svg_write_number(fp, currentA);
1481         return 0;
1482     }
1483     if (segment_idx < node->points - 1) {
1484         ArtVpath *vec = node->path + segment_idx + 1;
1485
1486         if (vec->code == ART_LINETO) {
1487             double    nextA = (isx ? vec->x : vec->y) - LINEOFFSET;
1488             double    nextB = (isx ? vec->y : vec->x) - LINEOFFSET;
1489
1490             if (nextB == currentB
1491                 && ((currentA >= lastA) == (nextA >= currentA))
1492                 && svg_is_int_step(currentA, nextA)) {
1493                 return 1;   /* skip to next as it is a straight line  */
1494             }
1495         }
1496     }
1497     putc(relChar, fp);
1498     svg_write_number(fp, currentA - lastA);
1499     return 0;
1500 }
1501
1502 static void svg_path(
1503     FILE * fp,
1504     gfx_node_t *node,
1505     int multi)
1506 {
1507     int       i;
1508     double    lastX = 0, lastY = 0;
1509
1510     /* for straight lines <path..> tags take less space than
1511        <line..> tags because of the efficient packing
1512        in the 'd' attribute */
1513     svg_start_tag(fp, "path");
1514     if (!multi)
1515         svg_common_path_attributes(fp, node);
1516     fputs(" d=\"", fp);
1517     /* specification of the 'd' attribute: */
1518     /* http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation */
1519     for (i = 0; i < node->points; i++) {
1520         ArtVpath *vec = node->path + i;
1521         double    x = vec->x - LINEOFFSET;
1522         double    y = vec->y - LINEOFFSET;
1523
1524         switch (vec->code) {
1525         case ART_MOVETO_OPEN:  /* fall-through */
1526         case ART_MOVETO:
1527             putc('M', fp);
1528             svg_write_number(fp, x);
1529             putc(',', fp);
1530             svg_write_number(fp, y);
1531             break;
1532         case ART_LINETO:
1533             /* try optimize filesize by using minimal lineto commands */
1534             /* without introducing rounding errors. */
1535             if (x == lastX) {
1536                 if (svg_path_straight_segment
1537                     (fp, lastY, y, x, node, i, 0, 'V', 'v'))
1538                     continue;
1539             } else if (y == lastY) {
1540                 if (svg_path_straight_segment
1541                     (fp, lastX, x, y, node, i, 1, 'H', 'h'))
1542                     continue;
1543             } else {
1544                 putc('L', fp);
1545                 svg_write_number(fp, x);
1546                 putc(',', fp);
1547                 svg_write_number(fp, y);
1548             }
1549             break;
1550         case ART_CURVETO:
1551             break;      /* unsupported */
1552         case ART_END:
1553             break;      /* nop */
1554         }
1555         lastX = x;
1556         lastY = y;
1557     }
1558     if (node->closed_path)
1559         fputs(" Z", fp);
1560     fputs("\"", fp);
1561     svg_close_tag_empty_node(fp);
1562 }
1563
1564 static void svg_multi_path(
1565     FILE * fp,
1566     gfx_node_t **nodeR)
1567 {
1568     /* optimize for multiple paths with the same color, penwidth, etc. */
1569     int       num = 1;
1570     gfx_node_t *node = *nodeR;
1571     gfx_node_t *next = node->next;
1572
1573     while (next) {
1574         if (next->type != node->type
1575             || next->size != node->size
1576             || next->color != node->color
1577             || next->dash_on != node->dash_on
1578             || next->dash_off != node->dash_off)
1579             break;
1580         next = next->next;
1581         num++;
1582     }
1583     if (num == 1) {
1584         svg_path(fp, node, 0);
1585         return;
1586     }
1587     svg_start_tag(fp, "g");
1588     svg_common_path_attributes(fp, node);
1589     svg_close_tag(fp);
1590     while (num && node) {
1591         svg_path(fp, node, 1);
1592         if (!--num)
1593             break;
1594         node = node->next;
1595         *nodeR = node;
1596     }
1597     svg_end_tag(fp, "g");
1598 }
1599
1600 static void svg_area(
1601     FILE * fp,
1602     gfx_node_t *node)
1603 {
1604     int       i;
1605     double    startX = 0, startY = 0;
1606
1607     svg_start_tag(fp, "polygon");
1608     fputs(" ", fp);
1609     svg_write_color(fp, node->color, "fill");
1610     fputs(" points=\"", fp);
1611     for (i = 0; i < node->points; i++) {
1612         ArtVpath *vec = node->path + i;
1613         double    x = vec->x - LINEOFFSET;
1614         double    y = vec->y - LINEOFFSET;
1615
1616         switch (vec->code) {
1617         case ART_MOVETO_OPEN:  /* fall-through */
1618         case ART_MOVETO:
1619             svg_write_number(fp, x);
1620             putc(',', fp);
1621             svg_write_number(fp, y);
1622             startX = x;
1623             startY = y;
1624             break;
1625         case ART_LINETO:
1626             if (i == node->points - 2
1627                 && node->path[i + 1].code == ART_END
1628                 && fabs(x - startX) < 0.001 && fabs(y - startY) < 0.001) {
1629                 break;  /* poly area always closed, no need for last point */
1630             }
1631             putc(' ', fp);
1632             svg_write_number(fp, x);
1633             putc(',', fp);
1634             svg_write_number(fp, y);
1635             break;
1636         case ART_CURVETO:
1637             break;      /* unsupported */
1638         case ART_END:
1639             break;      /* nop */
1640         }
1641     }
1642     fputs("\"", fp);
1643     svg_close_tag_empty_node(fp);
1644 }
1645
1646 static void svg_text(
1647     FILE * fp,
1648     gfx_node_t *node)
1649 {
1650     pdf_coords g;
1651     const char *fontname;
1652
1653     /* as svg has 0,0 in top-left corner (like most screens) instead of
1654        bottom-left corner like pdf and eps, we have to fake the coords
1655        using offset and inverse sin(r) value */
1656     int       page_height = 1000;
1657
1658     pdf_calc(page_height, node, &g);
1659     if (node->angle != 0) {
1660         svg_start_tag(fp, "g");
1661         /* can't use svg_write_number as 2 decimals is far from enough to avoid
1662            skewed text */
1663         fprintf(fp, " transform=\"matrix(%f,%f,%f,%f,%f,%f)\"",
1664                 g.ma, -g.mb, -g.mc, g.md, g.tmx, page_height - g.tmy);
1665         svg_close_tag(fp);
1666     }
1667     svg_start_tag(fp, "text");
1668     if (!node->angle) {
1669         fputs(" x=\"", fp);
1670         svg_write_number(fp, g.tmx);
1671         fputs("\" y=\"", fp);
1672         svg_write_number(fp, page_height - g.tmy);
1673         fputs("\"", fp);
1674     }
1675     fontname = afm_get_font_name(node->filename);
1676     if (strcmp(fontname, svg_default_font))
1677         fprintf(fp, " font-family=\"%s\"", fontname);
1678     fputs(" font-size=\"", fp);
1679     svg_write_number(fp, node->size);
1680     fputs("\"", fp);
1681     if (!svg_color_is_black(node->color))
1682         svg_write_color(fp, node->color, "fill");
1683     svg_close_tag_single_line(fp);
1684     /* support for node->tabwidth missing */
1685     svg_write_text(fp, node->text);
1686     svg_end_tag(fp, "text");
1687     if (node->angle != 0)
1688         svg_end_tag(fp, "g");
1689 }
1690
1691 int gfx_render_svg(
1692     gfx_canvas_t *canvas,
1693     art_u32 width,
1694     art_u32 height,
1695     gfx_color_t background,
1696     FILE * fp)
1697 {
1698     gfx_node_t *node = canvas->firstnode;
1699
1700     /* Find the first font used, and assume it is the mostly used
1701        one. It reduces the number of font-familty attributes. */
1702     while (node) {
1703         if (node->type == GFX_TEXT && node->filename) {
1704             svg_default_font = afm_get_font_name(node->filename);
1705             break;
1706         }
1707         node = node->next;
1708     }
1709     fputs("<?xml version=\"1.0\" standalone=\"no\"?>\n"
1710           "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"
1711           "   \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
1712           "<!--\n"
1713           "   SVG file created by\n"
1714           "        RRDtool " PACKAGE_VERSION
1715           " Tobias Oetiker, http://tobi.oetiker.ch\n" "\n"
1716           "   The width/height attributes in the outhermost svg node\n"
1717           "   are just default sizes for the browser which is used\n"
1718           "   if the svg file is openened directly without being\n"
1719           "   embedded in an html file.\n"
1720           "   The viewBox is the local coord system for rrdtool.\n" "-->\n",
1721           fp);
1722     svg_start_tag(fp, "svg");
1723     fputs(" width=\"", fp);
1724     svg_write_number(fp, width * canvas->zoom);
1725     fputs("\" height=\"", fp);
1726     svg_write_number(fp, height * canvas->zoom);
1727     fputs("\" x=\"0\" y=\"0\" viewBox=\"", fp);
1728     svg_write_number(fp, -LINEOFFSET);
1729     fputs(" ", fp);
1730     svg_write_number(fp, -LINEOFFSET);
1731     fputs(" ", fp);
1732     svg_write_number(fp, width - LINEOFFSET);
1733     fputs(" ", fp);
1734     svg_write_number(fp, height - LINEOFFSET);
1735     fputs("\" preserveAspectRatio=\"xMidYMid\"", fp);
1736     fprintf(fp, " font-family=\"%s\"", svg_default_font);   /* default font */
1737     fputs(" stroke-linecap=\"round\" stroke-linejoin=\"round\"", fp);
1738     fputs(" xmlns=\"http://www.w3.org/2000/svg\"", fp);
1739     fputs(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"", fp);
1740     svg_close_tag(fp);
1741     svg_start_tag(fp, "rect");
1742     fprintf(fp, " x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", width, height);
1743     svg_write_color(fp, background, "fill");
1744     svg_close_tag_empty_node(fp);
1745     node = canvas->firstnode;
1746     while (node) {
1747         switch (node->type) {
1748         case GFX_LINE:
1749             svg_multi_path(fp, &node);
1750             break;
1751         case GFX_AREA:
1752             svg_area(fp, node);
1753             break;
1754         case GFX_TEXT:
1755             svg_text(fp, node);
1756         }
1757         node = node->next;
1758     }
1759     svg_end_tag(fp, "svg");
1760     return 0;
1761 }
1762
1763 /* ------- EPS -------
1764    EPS and Postscript references:
1765    http://partners.adobe.com/asn/developer/technotes/postscript.html
1766 */
1767
1768 typedef struct eps_font {
1769     const char *ps_font;
1770     int       id;
1771     struct eps_font *next;
1772 } eps_font;
1773
1774 typedef struct eps_state {
1775     FILE     *fp;
1776     gfx_canvas_t *canvas;
1777     art_u32   page_width, page_height;
1778     eps_font *font_list;
1779   /*--*/
1780     gfx_color_t color;
1781     const char *font;
1782     double    font_size;
1783     double    line_width;
1784     int       linecap, linejoin;
1785     int       has_dash;
1786 } eps_state;
1787
1788 static void eps_set_color(
1789     eps_state *state,
1790     gfx_color_t color)
1791 {
1792 #if USE_EPS_FAKE_ALPHA
1793     double    a1, a2;
1794 #endif
1795     /* gfx_color_t is RRGGBBAA */
1796     if (state->color == color)
1797         return;
1798 #if USE_EPS_FAKE_ALPHA
1799     a1 = (color & 255) / 255.0;
1800     a2 = 255 * (1 - a1);
1801 #define eps_color_calc(x) (int)( ((x) & 255) * a1 + a2)
1802 #else
1803 #define eps_color_calc(x) (int)( (x) & 255)
1804 #endif
1805     /* gfx_color_t is RRGGBBAA */
1806     if (state->color == color)
1807         return;
1808     fprintf(state->fp, "%d %d %d Rgb\n",
1809             eps_color_calc(color >> 24),
1810             eps_color_calc(color >> 16), eps_color_calc(color >> 8));
1811     state->color = color;
1812 }
1813
1814 static int eps_add_font(
1815     eps_state *state,
1816     gfx_node_t *node)
1817 {
1818     /* The fonts list could be postponed to the end using
1819        (atend), but let's be nice and have them in the header. */
1820     const char *ps_font = afm_get_font_postscript_name(node->filename);
1821     eps_font *ef;
1822
1823     for (ef = state->font_list; ef; ef = ef->next) {
1824         if (!strcmp(ps_font, ef->ps_font))
1825             return 0;
1826     }
1827     ef = malloc(sizeof(eps_font));
1828     if (ef == NULL) {
1829         rrd_set_error("malloc for eps_font");
1830         return -1;
1831     }
1832     ef->next = state->font_list;
1833     ef->ps_font = ps_font;
1834     state->font_list = ef;
1835     return 0;
1836 }
1837
1838 static void eps_list_fonts(
1839     eps_state *state,
1840     const char *dscName)
1841 {
1842     eps_font *ef;
1843     int       lineLen = strlen(dscName);
1844
1845     if (!state->font_list)
1846         return;
1847     fputs(dscName, state->fp);
1848     for (ef = state->font_list; ef; ef = ef->next) {
1849         int       nameLen = strlen(ef->ps_font);
1850
1851         if (lineLen + nameLen > 100 && lineLen) {
1852             fputs("\n", state->fp);
1853             fputs("%%- \n", state->fp);
1854             lineLen = 5;
1855         } else {
1856             fputs(" ", state->fp);
1857             lineLen++;
1858         }
1859         fputs(ef->ps_font, state->fp);
1860         lineLen += nameLen;
1861     }
1862     fputs("\n", state->fp);
1863 }
1864
1865 static void eps_define_fonts(
1866     eps_state *state)
1867 {
1868     eps_font *ef;
1869
1870     if (!state->font_list)
1871         return;
1872     for (ef = state->font_list; ef; ef = ef->next) {
1873         /* PostScript¨ LANGUAGE REFERENCE third edition
1874            page 349 */
1875         fprintf(state->fp,
1876                 "%%\n"
1877                 "/%s findfont dup length dict begin\n"
1878                 "{ 1 index /FID ne {def} {pop pop} ifelse } forall\n"
1879                 "/Encoding ISOLatin1Encoding def\n"
1880                 "currentdict end\n"
1881                 "/%s-ISOLatin1 exch definefont pop\n"
1882                 "/SetFont-%s { /%s-ISOLatin1 findfont exch scalefont setfont } bd\n",
1883                 ef->ps_font, ef->ps_font, ef->ps_font, ef->ps_font);
1884     }
1885 }
1886
1887 static int eps_prologue(
1888     eps_state *state)
1889 {
1890     gfx_node_t *node;
1891
1892     fputs("%!PS-Adobe-3.0 EPSF-3.0\n"
1893           "%%Creator: RRDtool " PACKAGE_VERSION
1894           " Tobias Oetiker, http://tobi.oetiker.ch\n"
1895           /* can't like weird chars here */
1896           "%%Title: (RRDtool output)\n"
1897           "%%DocumentData: Clean7Bit\n" "", state->fp);
1898     fprintf(state->fp, "%%%%BoundingBox: 0 0 %d %d\n",
1899             state->page_width, state->page_height);
1900     for (node = state->canvas->firstnode; node; node = node->next) {
1901         if (node->type == GFX_TEXT && eps_add_font(state, node) == -1)
1902             return -1;
1903     }
1904     eps_list_fonts(state, "%%DocumentFonts:");
1905     eps_list_fonts(state, "%%DocumentNeededFonts:");
1906     fputs("%%EndComments\n" "%%BeginProlog\n" "%%EndProlog\n"   /* must have, or BoundingBox is ignored */
1907           "/bd { bind def } bind def\n" "", state->fp);
1908     fprintf(state->fp, "/X { %.2f add } bd\n", LINEOFFSET);
1909     fputs("/X2 {X exch X exch} bd\n"
1910           "/M {X2 moveto} bd\n"
1911           "/L {X2 lineto} bd\n"
1912           "/m {moveto} bd\n"
1913           "/l {lineto} bd\n"
1914           "/S {stroke} bd\n"
1915           "/CP {closepath} bd\n"
1916           "/WS {setlinewidth stroke} bd\n"
1917           "/F {fill} bd\n"
1918           "/T1 {gsave} bd\n"
1919           "/T2 {concat 0 0 moveto show grestore} bd\n"
1920           "/T   {moveto show} bd\n"
1921           "/Rgb { 255.0 div 3 1 roll\n"
1922           "       255.0 div 3 1 roll \n"
1923           "       255.0 div 3 1 roll setrgbcolor } bd\n" "", state->fp);
1924     eps_define_fonts(state);
1925     return 0;
1926 }
1927
1928 static void eps_clear_dash(
1929     eps_state *state)
1930 {
1931     if (!state->has_dash)
1932         return;
1933     state->has_dash = 0;
1934     fputs("[1 0] 0 setdash\n", state->fp);
1935 }
1936
1937 static void eps_write_linearea(
1938     eps_state *state,
1939     gfx_node_t *node)
1940 {
1941     int       i;
1942     FILE     *fp = state->fp;
1943     int       useOffset = 0;
1944     int       clearDashIfAny = 1;
1945
1946     eps_set_color(state, node->color);
1947     if (node->type == GFX_LINE) {
1948         svg_dash  dash_info;
1949
1950         if (state->linecap != 1) {
1951             fputs("1 setlinecap\n", fp);
1952             state->linecap = 1;
1953         }
1954         if (state->linejoin != 1) {
1955             fputs("1 setlinejoin\n", fp);
1956             state->linejoin = 1;
1957         }
1958         svg_get_dash(node, &dash_info);
1959         if (dash_info.dash_enable) {
1960             clearDashIfAny = 0;
1961             state->has_dash = 1;
1962             fputs("[", fp);
1963             svg_write_number(fp, dash_info.adjusted_on);
1964             fputs(" ", fp);
1965             svg_write_number(fp, dash_info.adjusted_off);
1966             fputs("] ", fp);
1967             svg_write_number(fp, dash_info.dash_offset);
1968             fputs(" setdash\n", fp);
1969         }
1970     }
1971     if (clearDashIfAny)
1972         eps_clear_dash(state);
1973     for (i = 0; i < node->points; i++) {
1974         ArtVpath *vec = node->path + i;
1975         double    x = vec->x;
1976         double    y = state->page_height - vec->y;
1977
1978         if (vec->code == ART_MOVETO_OPEN || vec->code == ART_MOVETO)
1979             useOffset = (fabs(x - floor(x) - 0.5) < 0.01
1980                          && fabs(y - floor(y) - 0.5) < 0.01);
1981         if (useOffset) {
1982             x -= LINEOFFSET;
1983             y -= LINEOFFSET;
1984         }
1985         switch (vec->code) {
1986         case ART_MOVETO_OPEN:  /* fall-through */
1987         case ART_MOVETO:
1988             svg_write_number(fp, x);
1989             fputc(' ', fp);
1990             svg_write_number(fp, y);
1991             fputc(' ', fp);
1992             fputs(useOffset ? "M\n" : "m\n", fp);
1993             break;
1994         case ART_LINETO:
1995             svg_write_number(fp, x);
1996             fputc(' ', fp);
1997             svg_write_number(fp, y);
1998             fputc(' ', fp);
1999             fputs(useOffset ? "L\n" : "l\n", fp);
2000             break;
2001         case ART_CURVETO:
2002             break;      /* unsupported */
2003         case ART_END:
2004             break;      /* nop */
2005         }
2006     }
2007     if (node->type == GFX_LINE) {
2008         if (node->closed_path)
2009             fputs("CP ", fp);
2010         if (node->size != state->line_width) {
2011             state->line_width = node->size;
2012             svg_write_number(fp, state->line_width);
2013             fputs(" WS\n", fp);
2014         } else {
2015             fputs("S\n", fp);
2016         }
2017     } else {
2018         fputs("F\n", fp);
2019     }
2020 }
2021
2022 static void eps_write_text(
2023     eps_state *state,
2024     gfx_node_t *node)
2025 {
2026     FILE     *fp = state->fp;
2027     const char *ps_font = afm_get_font_postscript_name(node->filename);
2028     int       lineLen = 0;
2029     pdf_coords g;
2030
2031 #ifdef HAVE_MBSTOWCS
2032     size_t    clen;
2033     wchar_t  *p, *cstr, ch;
2034     int       text_count;
2035
2036     if (!node->text)
2037         return;
2038     clen = strlen(node->text) + 1;
2039     cstr = malloc(sizeof(wchar_t) * clen);
2040     text_count = mbstowcs(cstr, node->text, clen);
2041     if (text_count == -1)
2042         text_count = mbstowcs(cstr, "Enc-Err", 6);
2043     p = cstr;
2044 #else
2045     const unsigned char *p = node->text;
2046     unsigned char ch;
2047
2048     if (!p)
2049         return;
2050 #endif
2051     pdf_calc(state->page_height, node, &g);
2052     eps_set_color(state, node->color);
2053     if (strcmp(ps_font, state->font) || node->size != state->font_size) {
2054         state->font = ps_font;
2055         state->font_size = node->size;
2056         svg_write_number(fp, state->font_size);
2057         fprintf(fp, " SetFont-%s\n", state->font);
2058     }
2059     if (node->angle)
2060         fputs("T1 ", fp);
2061     fputs("(", fp);
2062     lineLen = 20;
2063     while (1) {
2064         ch = *p;
2065         if (!ch)
2066             break;
2067         ch = afm_fix_osx_charset(ch);   /* unsafe macro */
2068         if (++lineLen > 70) {
2069             fputs("\\\n", fp);  /* backslash and \n */
2070             lineLen = 0;
2071         }
2072         switch (ch) {
2073         case '%':
2074         case '(':
2075         case ')':
2076         case '\\':
2077             fputc('\\', fp);
2078             fputc(ch, fp);
2079             break;
2080         case '\n':
2081             fputs("\\n", fp);
2082             break;
2083         case '\r':
2084             fputs("\\r", fp);
2085             break;
2086         case '\t':
2087             fputs("\\t", fp);
2088             break;
2089         default:
2090             if (ch > 255) {
2091                 fputc('?', fp);
2092             } else if (ch >= 126 || ch < 32) {
2093                 fprintf(fp, "\\%03o", (unsigned int) ch);
2094                 lineLen += 3;
2095             } else {
2096                 fputc(ch, fp);
2097             }
2098         }
2099         p++;
2100     }
2101 #ifdef HAVE_MBSTOWCS
2102     free(cstr);
2103 #endif
2104     if (node->angle) {
2105         /* can't use svg_write_number as 2 decimals is far from enough to avoid
2106            skewed text */
2107         fprintf(fp, ") [%f %f %f %f %f %f] T2\n",
2108                 g.ma, g.mb, g.mc, g.md, g.tmx, g.tmy);
2109     } else {
2110         fputs(") ", fp);
2111         svg_write_number(fp, g.tmx);
2112         fputs(" ", fp);
2113         svg_write_number(fp, g.tmy);
2114         fputs(" T\n", fp);
2115     }
2116 }
2117
2118 static int eps_write_content(
2119     eps_state *state)
2120 {
2121     gfx_node_t *node;
2122
2123     fputs("%\n", state->fp);
2124     for (node = state->canvas->firstnode; node; node = node->next) {
2125         switch (node->type) {
2126         case GFX_LINE:
2127         case GFX_AREA:
2128             eps_write_linearea(state, node);
2129             break;
2130         case GFX_TEXT:
2131             eps_write_text(state, node);
2132             break;
2133         }
2134     }
2135     return 0;
2136 }
2137
2138 int gfx_render_eps(
2139     gfx_canvas_t *canvas,
2140     art_u32 width,
2141     art_u32 height,
2142     gfx_color_t background,
2143     FILE * fp)
2144 {
2145     struct eps_state state;
2146
2147     state.fp = fp;
2148     state.canvas = canvas;
2149     state.page_width = width;
2150     state.page_height = height;
2151     state.font = "no-default-font";
2152     state.font_size = -1;
2153     state.color = 0;    /* black */
2154     state.font_list = NULL;
2155     state.linecap = -1;
2156     state.linejoin = -1;
2157     state.has_dash = 0;
2158     state.line_width = 1;
2159     if (eps_prologue(&state) == -1)
2160         return -1;
2161     eps_set_color(&state, background);
2162     fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
2163             height, width, height, width);
2164     if (eps_write_content(&state) == -1)
2165         return 0;
2166     fputs("showpage\n", fp);
2167     fputs("%%EOF\n", fp);
2168     while (state.font_list) {
2169         eps_font *next = state.font_list->next;
2170
2171         free(state.font_list);
2172         state.font_list = next;
2173     }
2174     return 0;
2175 }
2176
2177 /* ------- PDF -------
2178    PDF references page:
2179    http://partners.adobe.com/public/developer/pdf/index_reference.html
2180 */
2181
2182 typedef struct pdf_buffer {
2183     int       id, is_obj, is_dict, is_stream, pdf_file_pos;
2184     char     *data;
2185     int       alloc_size, current_size;
2186     struct pdf_buffer *previous_buffer, *next_buffer;
2187     struct pdf_state *state;
2188 } pdf_buffer;
2189
2190 typedef struct pdf_font {
2191     const char *ps_font;
2192     pdf_buffer obj;
2193     struct pdf_font *next;
2194 } pdf_font;
2195
2196 typedef struct pdf_state {
2197     FILE     *fp;
2198     gfx_canvas_t *canvas;
2199     art_u32   page_width, page_height;
2200     pdf_font *font_list;
2201     pdf_buffer *first_buffer, *last_buffer;
2202     int       pdf_file_pos;
2203     int       has_failed;
2204   /*--*/
2205     gfx_color_t stroke_color, fill_color;
2206     int       font_id;
2207     double    font_size;
2208     double    line_width;
2209     svg_dash  dash;
2210     int       linecap, linejoin;
2211     int       last_obj_id;
2212   /*--*/
2213     pdf_buffer pdf_header;
2214     pdf_buffer info_obj, catalog_obj, pages_obj, page1_obj;
2215     pdf_buffer fontsdict_obj;
2216     pdf_buffer graph_stream;
2217 } pdf_state;
2218
2219 static void pdf_init_buffer(
2220     pdf_state *state,
2221     pdf_buffer *buf)
2222 {
2223     int       initial_size = 32;
2224
2225     buf->state = state;
2226     buf->id = -42;
2227     buf->alloc_size = 0;
2228     buf->current_size = 0;
2229     buf->data = (char *) malloc(initial_size);
2230     buf->is_obj = 0;
2231     buf->previous_buffer = NULL;
2232     buf->next_buffer = NULL;
2233     if (buf->data == NULL) {
2234         rrd_set_error("malloc for pdf_buffer data");
2235         state->has_failed = 1;
2236         return;
2237     }
2238     buf->alloc_size = initial_size;
2239     if (state->last_buffer)
2240         state->last_buffer->next_buffer = buf;
2241     if (state->first_buffer == NULL)
2242         state->first_buffer = buf;
2243     buf->previous_buffer = state->last_buffer;
2244     state->last_buffer = buf;
2245 }
2246
2247 static void pdf_put(
2248     pdf_buffer *buf,
2249     const char *text,
2250     int len)
2251 {
2252     if (len <= 0)
2253         return;
2254     if (buf->alloc_size < buf->current_size + len) {
2255         int       new_size = buf->alloc_size;
2256         char     *new_buf;
2257
2258         while (new_size < buf->current_size + len)
2259             new_size *= 4;
2260         new_buf = (char *) malloc(new_size);
2261         if (new_buf == NULL) {
2262             rrd_set_error("re-malloc for pdf_buffer data");
2263             buf->state->has_failed = 1;
2264             return;
2265         }
2266         memcpy(new_buf, buf->data, buf->current_size);
2267         free(buf->data);
2268         buf->data = new_buf;
2269         buf->alloc_size = new_size;
2270     }
2271     memcpy(buf->data + buf->current_size, text, len);
2272     buf->current_size += len;
2273 }
2274
2275 static void pdf_put_char(
2276     pdf_buffer *buf,
2277     char c)
2278 {
2279     if (buf->alloc_size >= buf->current_size + 1) {
2280         buf->data[buf->current_size++] = c;
2281     } else {
2282         char      tmp[1];
2283
2284         tmp[0] = (char) c;
2285         pdf_put(buf, tmp, 1);
2286     }
2287 }
2288
2289 static void pdf_puts(
2290     pdf_buffer *buf,
2291     const char *text)
2292 {
2293     pdf_put(buf, text, strlen(text));
2294 }
2295
2296 static void pdf_indent(
2297     pdf_buffer *buf)
2298 {
2299     pdf_puts(buf, "\t");
2300 }
2301
2302 static void pdf_putsi(
2303     pdf_buffer *buf,
2304     const char *text)
2305 {
2306     pdf_indent(buf);
2307     pdf_puts(buf, text);
2308 }
2309
2310 static void pdf_putint(
2311     pdf_buffer *buf,
2312     int i)
2313 {
2314     char      tmp[20];
2315
2316     sprintf(tmp, "%d", i);
2317     pdf_puts(buf, tmp);
2318 }
2319
2320 static void pdf_putnumber(
2321     pdf_buffer *buf,
2322     double d)
2323 {
2324     char      tmp[50];
2325
2326     svg_format_number(tmp, sizeof(tmp), d);
2327     pdf_puts(buf, tmp);
2328 }
2329
2330 static void pdf_put_string_contents_wide(
2331     pdf_buffer *buf,
2332     const afm_char * text)
2333 {
2334     const afm_char *p = text;
2335
2336     while (1) {
2337         afm_char  ch = *p;
2338
2339         ch = afm_fix_osx_charset(ch);   /* unsafe macro */
2340         switch (ch) {
2341         case 0:
2342             return;
2343         case '(':
2344             pdf_puts(buf, "\\(");
2345             break;
2346         case ')':
2347             pdf_puts(buf, "\\)");
2348             break;
2349         case '\\':
2350             pdf_puts(buf, "\\\\");
2351             break;
2352         case '\n':
2353             pdf_puts(buf, "\\n");
2354             break;
2355         case '\r':
2356             pdf_puts(buf, "\\r");
2357             break;
2358         case '\t':
2359             pdf_puts(buf, "\\t");
2360             break;
2361         default:
2362             if (ch > 255) {
2363                 pdf_put_char(buf, '?');
2364             } else if (ch > 125 || ch < 32) {
2365                 pdf_put_char(buf, ch);
2366             } else {
2367                 char      tmp[10];
2368
2369                 snprintf(tmp, sizeof(tmp), "\\%03o", (int) ch);
2370                 pdf_puts(buf, tmp);
2371             }
2372         }
2373         p++;
2374     }
2375 }
2376
2377 static void pdf_put_string_contents(
2378     pdf_buffer *buf,
2379     const char *text)
2380 {
2381 #ifdef HAVE_MBSTOWCS
2382     size_t    clen = strlen(text) + 1;
2383     wchar_t  *cstr = malloc(sizeof(wchar_t) * clen);
2384     int       text_count = mbstowcs(cstr, text, clen);
2385
2386     if (text_count == -1)
2387         text_count = mbstowcs(cstr, "Enc-Err", 6);
2388     pdf_put_string_contents_wide(buf, cstr);
2389 #if 0
2390     if (*text == 'W') {
2391         fprintf(stderr, "Decoding utf8 for '%s'\n", text);
2392         wchar_t  *p = cstr;
2393         char     *pp = text;
2394
2395         fprintf(stderr, "sz wc = %d\n", sizeof(wchar_t));
2396         while (*p) {
2397             fprintf(stderr, "  %d = %c  versus %d = %c\n", *p, (char) *p,
2398                     255 & (int) *pp, *pp);
2399             p++;
2400             pp++;
2401         }
2402     }
2403 #endif
2404     free(cstr);
2405 #else
2406     pdf_put_string_contents_wide(buf, text);
2407 #endif
2408 }
2409
2410 static void pdf_init_object(
2411     pdf_state *state,
2412     pdf_buffer *buf)
2413 {
2414     pdf_init_buffer(state, buf);
2415     buf->id = ++state->last_obj_id;
2416     buf->is_obj = 1;
2417     buf->is_stream = 0;
2418 }
2419
2420 static void pdf_init_dict(
2421     pdf_state *state,
2422     pdf_buffer *buf)
2423 {
2424     pdf_init_object(state, buf);
2425     buf->is_dict = 1;
2426 }
2427
2428 static void pdf_set_color(
2429     pdf_buffer *buf,
2430     gfx_color_t color,
2431     gfx_color_t *current_color,
2432     const char *op)
2433 {
2434 #if USE_PDF_FAKE_ALPHA
2435     double    a1, a2;
2436 #endif
2437     /* gfx_color_t is RRGGBBAA */
2438     if (*current_color == color)
2439         return;
2440 #if USE_PDF_FAKE_ALPHA
2441     a1 = (color & 255) / 255.0;
2442     a2 = 1 - a1;
2443 #define pdf_color_calc(x) ( ((x)  & 255) / 255.0 * a1 + a2)
2444 #else
2445 #define pdf_color_calc(x) ( ((x)  & 255) / 255.0)
2446 #endif
2447     pdf_putnumber(buf, pdf_color_calc(color >> 24));
2448     pdf_puts(buf, " ");
2449     pdf_putnumber(buf, pdf_color_calc(color >> 16));
2450     pdf_puts(buf, " ");
2451     pdf_putnumber(buf, pdf_color_calc(color >> 8));
2452     pdf_puts(buf, " ");
2453     pdf_puts(buf, op);
2454     pdf_puts(buf, "\n");
2455     *current_color = color;
2456 }
2457
2458 static void pdf_set_stroke_color(
2459     pdf_buffer *buf,
2460     gfx_color_t color)
2461 {
2462     pdf_set_color(buf, color, &buf->state->stroke_color, "RG");
2463 }
2464
2465 static void pdf_set_fill_color(
2466     pdf_buffer *buf,
2467     gfx_color_t color)
2468 {
2469     pdf_set_color(buf, color, &buf->state->fill_color, "rg");
2470 }
2471
2472 static pdf_font *pdf_find_font(
2473     pdf_state *state,
2474     gfx_node_t *node)
2475 {
2476     const char *ps_font = afm_get_font_postscript_name(node->filename);
2477     pdf_font *ef;
2478
2479     for (ef = state->font_list; ef; ef = ef->next) {
2480         if (!strcmp(ps_font, ef->ps_font))
2481             return ef;
2482     }
2483     return NULL;
2484 }
2485
2486 static void pdf_add_font(
2487     pdf_state *state,
2488     gfx_node_t *node)
2489 {
2490     pdf_font *ef = pdf_find_font(state, node);
2491
2492     if (ef)
2493         return;
2494     ef = malloc(sizeof(pdf_font));
2495     if (ef == NULL) {
2496         rrd_set_error("malloc for pdf_font");
2497         state->has_failed = 1;
2498         return;
2499     }
2500     pdf_init_dict(state, &ef->obj);
2501     ef->next = state->font_list;
2502     ef->ps_font = afm_get_font_postscript_name(node->filename);
2503     state->font_list = ef;
2504     /* fonts dict */
2505     pdf_putsi(&state->fontsdict_obj, "/F");
2506     pdf_putint(&state->fontsdict_obj, ef->obj.id);
2507     pdf_puts(&state->fontsdict_obj, " ");
2508     pdf_putint(&state->fontsdict_obj, ef->obj.id);
2509     pdf_puts(&state->fontsdict_obj, " 0 R\n");
2510     /* fonts def */
2511     pdf_putsi(&ef->obj, "/Type /Font\n");
2512     pdf_putsi(&ef->obj, "/Subtype /Type1\n");
2513     pdf_putsi(&ef->obj, "/Name /F");
2514     pdf_putint(&ef->obj, ef->obj.id);
2515     pdf_puts(&ef->obj, "\n");
2516     pdf_putsi(&ef->obj, "/BaseFont /");
2517     pdf_puts(&ef->obj, ef->ps_font);
2518     pdf_puts(&ef->obj, "\n");
2519     pdf_putsi(&ef->obj, "/Encoding /WinAnsiEncoding\n");
2520     /*  'Cp1252' (this is latin 1 extended with 27 characters;
2521        the encoding is also known as 'winansi')
2522        http://www.lowagie.com/iText/tutorial/ch09.html */
2523 }
2524
2525 static void pdf_create_fonts(
2526     pdf_state *state)
2527 {
2528     gfx_node_t *node;
2529
2530     for (node = state->canvas->firstnode; node; node = node->next) {
2531         if (node->type == GFX_TEXT)
2532             pdf_add_font(state, node);
2533     }
2534 }
2535
2536 static void pdf_write_linearea(
2537     pdf_state *state,
2538     gfx_node_t *node)
2539 {
2540     int       i;
2541     pdf_buffer *s = &state->graph_stream;
2542
2543     if (node->type == GFX_LINE) {
2544         svg_dash  dash_info;
2545
2546         svg_get_dash(node, &dash_info);
2547         if (!svg_dash_equal(&dash_info, &state->dash)) {
2548             state->dash = dash_info;
2549             if (dash_info.dash_enable) {
2550                 pdf_puts(s, "[");
2551                 pdf_putnumber(s, dash_info.adjusted_on);
2552                 pdf_puts(s, " ");
2553                 pdf_putnumber(s, dash_info.adjusted_off);
2554                 pdf_puts(s, "] ");
2555                 pdf_putnumber(s, dash_info.dash_offset);
2556                 pdf_puts(s, " d\n");
2557             } else {
2558                 pdf_puts(s, "[] 0 d\n");
2559             }
2560         }
2561         pdf_set_stroke_color(s, node->color);
2562         if (state->linecap != 1) {
2563             pdf_puts(s, "1 j\n");
2564             state->linecap = 1;
2565         }
2566         if (state->linejoin != 1) {
2567             pdf_puts(s, "1 J\n");
2568             state->linejoin = 1;
2569         }
2570         if (node->size != state->line_width) {
2571             state->line_width = node->size;
2572             pdf_putnumber(s, state->line_width);
2573             pdf_puts(s, " w\n");
2574         }
2575     } else {
2576         pdf_set_fill_color(s, node->color);
2577     }
2578     for (i = 0; i < node->points; i++) {
2579         ArtVpath *vec = node->path + i;
2580         double    x = vec->x;
2581         double    y = state->page_height - vec->y;
2582
2583         if (node->type == GFX_AREA) {
2584             x += LINEOFFSET;    /* adjust for libart handling of areas */
2585             y -= LINEOFFSET;
2586         }
2587         switch (vec->code) {
2588         case ART_MOVETO_OPEN:  /* fall-through */
2589         case ART_MOVETO:
2590             pdf_putnumber(s, x);
2591             pdf_puts(s, " ");
2592             pdf_putnumber(s, y);
2593             pdf_puts(s, " m\n");
2594             break;
2595         case ART_LINETO:
2596             pdf_putnumber(s, x);
2597             pdf_puts(s, " ");
2598             pdf_putnumber(s, y);
2599             pdf_puts(s, " l\n");
2600             break;
2601         case ART_CURVETO:
2602             break;      /* unsupported */
2603         case ART_END:
2604             break;      /* nop */
2605         }
2606     }
2607     if (node->type == GFX_LINE) {
2608         pdf_puts(s, node->closed_path ? "s\n" : "S\n");
2609     } else {
2610         pdf_puts(s, "f\n");
2611     }
2612 }
2613
2614
2615 static void pdf_write_matrix(
2616     pdf_state *state,
2617     gfx_node_t *node,
2618     pdf_coords * g,
2619     int useTM)
2620 {
2621     char      tmp[150];
2622     pdf_buffer *s = &state->graph_stream;
2623
2624     if (node->angle == 0) {
2625         pdf_puts(s, "1 0 0 1 ");
2626         pdf_putnumber(s, useTM ? g->tmx : g->mx);
2627         pdf_puts(s, " ");
2628         pdf_putnumber(s, useTM ? g->tmy : g->my);
2629     } else {
2630         /* can't use svg_write_number as 2 decimals is far from enough to avoid
2631            skewed text */
2632         sprintf(tmp, "%f %f %f %f %f %f",
2633                 g->ma, g->mb, g->mc, g->md,
2634                 useTM ? g->tmx : g->mx, useTM ? g->tmy : g->my);
2635         pdf_puts(s, tmp);
2636     }
2637 }
2638
2639 static void pdf_write_text(
2640     pdf_state *state,
2641     gfx_node_t *node,
2642     int last_was_text,
2643     int next_is_text)
2644 {
2645     pdf_coords g;
2646     pdf_buffer *s = &state->graph_stream;
2647     pdf_font *font = pdf_find_font(state, node);
2648
2649     if (font == NULL) {
2650         rrd_set_error("font disappeared");
2651         state->has_failed = 1;
2652         return;
2653     }
2654     pdf_calc(state->page_height, node, &g);
2655 #if PDF_CALC_DEBUG
2656     pdf_puts(s, "q % debug green box\n");
2657     pdf_write_matrix(state, node, &g, 0);
2658     pdf_puts(s, " cm\n");
2659     pdf_set_fill_color(s, 0x90FF9000);
2660     pdf_puts(s, "0 0.4 0 rg\n");
2661     pdf_puts(s, "0 0 ");
2662     pdf_putnumber(s, g.sizep.x);
2663     pdf_puts(s, " ");
2664     pdf_putnumber(s, g.sizep.y);
2665     pdf_puts(s, " re\n");
2666     pdf_puts(s, "f\n");
2667     pdf_puts(s, "Q\n");
2668 #endif
2669     pdf_set_fill_color(s, node->color);
2670     if (PDF_CALC_DEBUG || !last_was_text)
2671         pdf_puts(s, "BT\n");
2672     if (state->font_id != font->obj.id || node->size != state->font_size) {
2673         state->font_id = font->obj.id;
2674         state->font_size = node->size;
2675         pdf_puts(s, "/F");
2676         pdf_putint(s, font->obj.id);
2677         pdf_puts(s, " ");
2678         pdf_putnumber(s, node->size);
2679         pdf_puts(s, " Tf\n");
2680     }
2681     pdf_write_matrix(state, node, &g, 1);
2682     pdf_puts(s, " Tm\n");
2683     pdf_puts(s, "(");
2684     pdf_put_string_contents(s, node->text);
2685     pdf_puts(s, ") Tj\n");
2686     if (PDF_CALC_DEBUG || !next_is_text)
2687         pdf_puts(s, "ET\n");
2688 }
2689
2690 static void pdf_write_content(
2691     pdf_state *state)
2692 {
2693     gfx_node_t *node;
2694     int       last_was_text = 0, next_is_text;
2695
2696     for (node = state->canvas->firstnode; node; node = node->next) {
2697         switch (node->type) {
2698         case GFX_LINE:
2699         case GFX_AREA:
2700             pdf_write_linearea(state, node);
2701             break;
2702         case GFX_TEXT:
2703             next_is_text = node->next && node->next->type == GFX_TEXT;
2704             pdf_write_text(state, node, last_was_text, next_is_text);
2705             break;
2706         }
2707         last_was_text = node->type == GFX_TEXT;
2708     }
2709 }
2710
2711 static void pdf_init_document(
2712     pdf_state *state)
2713 {
2714     pdf_init_buffer(state, &state->pdf_header);
2715     pdf_init_dict(state, &state->catalog_obj);
2716     pdf_init_dict(state, &state->info_obj);
2717     pdf_init_dict(state, &state->pages_obj);
2718     pdf_init_dict(state, &state->page1_obj);
2719     pdf_init_dict(state, &state->fontsdict_obj);
2720     pdf_create_fonts(state);
2721     if (state->has_failed)
2722         return;
2723     /* make stream last object in file */
2724     pdf_init_object(state, &state->graph_stream);
2725     state->graph_stream.is_stream = 1;
2726 }
2727
2728 static void pdf_setup_document(
2729     pdf_state *state)
2730 {
2731     const char *creator =
2732         "RRDtool " PACKAGE_VERSION " Tobias Oetiker, http://tobi.oetiker.ch";
2733     /* all objects created by now, so init code can reference them */
2734     /* HEADER */
2735     pdf_puts(&state->pdf_header, "%PDF-1.3\n");
2736     /* following 8 bit comment is recommended by Adobe for
2737        indicating binary file to file transfer applications */
2738     pdf_puts(&state->pdf_header, "%\xE2\xE3\xCF\xD3\n");
2739     /* INFO */
2740     pdf_putsi(&state->info_obj, "/Creator (");
2741     pdf_put_string_contents(&state->info_obj, creator);
2742     pdf_puts(&state->info_obj, ")\n");
2743     /* CATALOG */
2744     pdf_putsi(&state->catalog_obj, "/Type /Catalog\n");
2745     pdf_putsi(&state->catalog_obj, "/Pages ");
2746     pdf_putint(&state->catalog_obj, state->pages_obj.id);
2747     pdf_puts(&state->catalog_obj, " 0 R\n");
2748     /* PAGES */
2749     pdf_putsi(&state->pages_obj, "/Type /Pages\n");
2750     pdf_putsi(&state->pages_obj, "/Kids [");
2751     pdf_putint(&state->pages_obj, state->page1_obj.id);
2752     pdf_puts(&state->pages_obj, " 0 R]\n");
2753     pdf_putsi(&state->pages_obj, "/Count 1\n");
2754     /* PAGE 1 */
2755     pdf_putsi(&state->page1_obj, "/Type /Page\n");
2756     pdf_putsi(&state->page1_obj, "/Parent ");
2757     pdf_putint(&state->page1_obj, state->pages_obj.id);
2758     pdf_puts(&state->page1_obj, " 0 R\n");
2759     pdf_putsi(&state->page1_obj, "/MediaBox [0 0 ");
2760     pdf_putint(&state->page1_obj, state->page_width);
2761     pdf_puts(&state->page1_obj, " ");
2762     pdf_putint(&state->page1_obj, state->page_height);
2763     pdf_puts(&state->page1_obj, "]\n");
2764     pdf_putsi(&state->page1_obj, "/Contents ");
2765     pdf_putint(&state->page1_obj, state->graph_stream.id);
2766     pdf_puts(&state->page1_obj, " 0 R\n");
2767     pdf_putsi(&state->page1_obj, "/Resources << /Font ");
2768     pdf_putint(&state->page1_obj, state->fontsdict_obj.id);
2769     pdf_puts(&state->page1_obj, " 0 R >>\n");
2770 }
2771
2772 static void pdf_write_string_to_file(
2773     pdf_state *state,
2774     const char *text)
2775 {
2776     fputs(text, state->fp);
2777     state->pdf_file_pos += strlen(text);
2778 }
2779
2780 static void pdf_write_buf_to_file(
2781     pdf_state *state,
2782     pdf_buffer *buf)
2783 {
2784     char      tmp[40];
2785
2786     buf->pdf_file_pos = state->pdf_file_pos;
2787     if (buf->is_obj) {
2788         snprintf(tmp, sizeof(tmp), "%d 0 obj\n", buf->id);
2789         pdf_write_string_to_file(state, tmp);
2790     }
2791     if (buf->is_dict)
2792         pdf_write_string_to_file(state, "<<\n");
2793     if (buf->is_stream) {
2794         snprintf(tmp, sizeof(tmp), "<< /Length %d >>\n", buf->current_size);
2795         pdf_write_string_to_file(state, tmp);
2796         pdf_write_string_to_file(state, "stream\n");
2797     }
2798     fwrite(buf->data, 1, buf->current_size, state->fp);
2799     state->pdf_file_pos += buf->current_size;
2800     if (buf->is_stream)
2801         pdf_write_string_to_file(state, "endstream\n");
2802     if (buf->is_dict)
2803         pdf_write_string_to_file(state, ">>\n");
2804     if (buf->is_obj)
2805         pdf_write_string_to_file(state, "endobj\n");
2806 }
2807
2808 static void pdf_write_to_file(
2809     pdf_state *state)
2810 {
2811     pdf_buffer *buf = state->first_buffer;
2812     int       xref_pos;
2813
2814     state->pdf_file_pos = 0;
2815     pdf_write_buf_to_file(state, &state->pdf_header);
2816     while (buf) {
2817         if (buf->is_obj)
2818             pdf_write_buf_to_file(state, buf);
2819         buf = buf->next_buffer;
2820     }
2821     xref_pos = state->pdf_file_pos;
2822     fprintf(state->fp, "xref\n");
2823     fprintf(state->fp, "%d %d\n", 0, state->last_obj_id + 1);
2824     /* TOC lines must be exactly 20 bytes including \n */
2825     fprintf(state->fp, "%010d %05d f\x20\n", 0, 65535);
2826     for (buf = state->first_buffer; buf; buf = buf->next_buffer) {
2827         if (buf->is_obj)
2828             fprintf(state->fp, "%010d %05d n\x20\n", buf->pdf_file_pos, 0);
2829     }
2830     fprintf(state->fp, "trailer\n");
2831     fprintf(state->fp, "<<\n");
2832     fprintf(state->fp, "\t/Size %d\n", state->last_obj_id + 1);
2833     fprintf(state->fp, "\t/Root %d 0 R\n", state->catalog_obj.id);
2834     fprintf(state->fp, "\t/Info %d 0 R\n", state->info_obj.id);
2835     fprintf(state->fp, ">>\n");
2836     fprintf(state->fp, "startxref\n");
2837     fprintf(state->fp, "%d\n", xref_pos);
2838     fputs("%%EOF\n", state->fp);
2839 }
2840
2841 static void pdf_free_resources(
2842     pdf_state *state)
2843 {
2844     pdf_buffer *buf = state->first_buffer;
2845
2846     while (buf) {
2847         free(buf->data);
2848         buf->data = NULL;
2849         buf->alloc_size = buf->current_size = 0;
2850         buf = buf->next_buffer;
2851     }
2852     while (state->font_list) {
2853         pdf_font *next = state->font_list->next;
2854
2855         free(state->font_list);
2856         state->font_list = next;
2857     }
2858 }
2859
2860 int gfx_render_pdf(
2861     gfx_canvas_t *canvas,
2862     art_u32 width,
2863     art_u32 height,
2864     gfx_color_t UNUSED(background),
2865     FILE * fp)
2866 {
2867     struct pdf_state state;
2868
2869     memset(&state, 0, sizeof(pdf_state));
2870     state.fp = fp;
2871     state.canvas = canvas;
2872     state.page_width = width;
2873     state.page_height = height;
2874     state.font_id = -1;
2875     state.font_size = -1;
2876     state.font_list = NULL;
2877     state.linecap = -1;
2878     state.linejoin = -1;
2879     pdf_init_document(&state);
2880     /*
2881        pdf_set_color(&state, background);
2882        fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
2883        height, width, height, width);
2884      */
2885     if (!state.has_failed)
2886         pdf_write_content(&state);
2887     if (!state.has_failed)
2888         pdf_setup_document(&state);
2889     if (!state.has_failed)
2890         pdf_write_to_file(&state);
2891     pdf_free_resources(&state);
2892     return state.has_failed ? -1 : 0;
2893 }