I think I found a very small bug in rrd graph PDF output : the is_stream member of
[rrdtool.git] / src / rrd_gfx.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd_gfx.c  graphics wrapper for rrdtool
5   **************************************************************************/
6
7 /* #define DEBUG */
8
9 #ifdef DEBUG
10 # define DPRINT(x)    (void)(printf x, printf("\n"))
11 #else
12 # define DPRINT(x)
13 #endif
14 #include "rrd_tool.h"
15 #include <png.h>
16 #include <ft2build.h>
17 #include FT_FREETYPE_H
18
19 #include "rrd_gfx.h"
20 #include "rrd_afm.h"
21
22 /* lines are better drawn on the pixle than between pixles */
23 #define LINEOFFSET 0.5
24
25 static
26 gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){
27   gfx_node_t *node = art_new(gfx_node_t,1);
28   if (node == NULL) return NULL;
29   node->type = type;
30   node->color = 0x0;        /* color of element  0xRRGGBBAA  alpha 0xff is solid*/
31   node->size =0.0;         /* font size, line width */
32   node->path = NULL;        /* path */
33   node->points = 0;
34   node->points_max =0;
35   node->closed_path = 0;
36   node->svp = NULL;         /* svp */
37   node->filename = NULL;             /* font or image filename */
38   node->text = NULL;
39   node->x = 0.0;
40   node->y = 0.0;          /* position */
41   node->angle = 0;  
42   node->halign = GFX_H_NULL; /* text alignement */
43   node->valign = GFX_V_NULL; /* text alignement */
44   node->tabwidth = 0.0; 
45   node->next = NULL; 
46   if (canvas->lastnode != NULL){
47       canvas->lastnode->next = node;
48   }
49   if (canvas->firstnode == NULL){
50       canvas->firstnode = node;
51   }  
52   canvas->lastnode = node;
53   return node;
54 }
55
56 gfx_canvas_t *gfx_new_canvas (void) {
57     gfx_canvas_t *canvas = art_new(gfx_canvas_t,1);
58     canvas->firstnode = NULL;
59     canvas->lastnode = NULL;
60     canvas->imgformat = IF_PNG; /* we default to PNG output */
61     canvas->interlaced = 0;
62     canvas->zoom = 1.0;
63     return canvas;    
64 }
65
66 /* create a new line */
67 gfx_node_t  *gfx_new_line(gfx_canvas_t *canvas, 
68                            double X0, double Y0, 
69                            double X1, double Y1,
70                            double width, gfx_color_t color){
71   return gfx_new_dashed_line(canvas, X0, Y0, X1, Y1, width, color, 0, 0);
72 }
73
74 gfx_node_t  *gfx_new_dashed_line(gfx_canvas_t *canvas, 
75                            double X0, double Y0, 
76                            double X1, double Y1,
77                            double width, gfx_color_t color,
78                            double dash_on, double dash_off){
79
80   gfx_node_t *node;
81   ArtVpath *vec;
82   node = gfx_new_node(canvas,GFX_LINE);
83   if (node == NULL) return NULL;
84   vec = art_new(ArtVpath, 3);
85   if (vec == NULL) return NULL;
86   vec[0].code = ART_MOVETO_OPEN; vec[0].x=X0+LINEOFFSET; vec[0].y=Y0+LINEOFFSET;
87   vec[1].code = ART_LINETO; vec[1].x=X1+LINEOFFSET; vec[1].y=Y1+LINEOFFSET;
88   vec[2].code = ART_END;
89   
90   node->points = 3;
91   node->points_max = 3;
92   node->color = color;
93   node->size  = width;
94   node->dash_on = dash_on;
95   node->dash_off = dash_off;
96   node->path  = vec;
97   return node;
98 }
99
100 /* create a new area */
101 gfx_node_t   *gfx_new_area   (gfx_canvas_t *canvas, 
102                               double X0, double Y0,
103                               double X1, double Y1,
104                               double X2, double Y2,
105                               gfx_color_t color) {
106
107   gfx_node_t *node;
108   ArtVpath *vec;
109   node = gfx_new_node(canvas,GFX_AREA);
110   if (node == NULL) return NULL;
111   vec = art_new(ArtVpath, 5);
112   if (vec == NULL) return NULL;
113   vec[0].code = ART_MOVETO; vec[0].x=X0; vec[0].y=Y0;
114   vec[1].code = ART_LINETO; vec[1].x=X1; vec[1].y=Y1;
115   vec[2].code = ART_LINETO; vec[2].x=X2; vec[2].y=Y2;
116   vec[3].code = ART_LINETO; vec[3].x=X0; vec[3].y=Y0;
117   vec[4].code = ART_END;
118   
119   node->points = 5;
120   node->points_max = 5;
121   node->color = color;
122   node->path  = vec;
123
124   return node;
125 }
126
127 /* add a point to a line or to an area */
128 int           gfx_add_point  (gfx_node_t *node, 
129                               double x, double y){
130   if (node == NULL) return 1;
131   if (node->type == GFX_AREA) {
132     double X0 = node->path[0].x;
133     double Y0 = node->path[0].y;
134     node->points -= 2;
135     art_vpath_add_point (&(node->path),
136                          &(node->points),
137                          &(node->points_max),
138                          ART_LINETO,
139                          x,y);
140     art_vpath_add_point (&(node->path),
141                          &(node->points),
142                          &(node->points_max),
143                          ART_LINETO,
144                          X0,Y0);
145     art_vpath_add_point (&(node->path),
146                          &(node->points),
147                          &(node->points_max),
148                          ART_END,
149                          0,0);
150   } else if (node->type == GFX_LINE) {
151     node->points -= 1;
152     art_vpath_add_point (&(node->path),
153                          &(node->points),
154                          &(node->points_max),
155                          ART_LINETO,
156                          x+LINEOFFSET,y+LINEOFFSET);
157     art_vpath_add_point (&(node->path),
158                          &(node->points),
159                          &(node->points_max),
160                          ART_END,
161                          0,0);
162     
163   } else {
164     /* can only add point to areas and lines */
165     return 1;
166   }
167   return 0;
168 }
169
170 void           gfx_close_path  (gfx_node_t *node) {
171     node->closed_path = 1;
172     if (node->path[0].code == ART_MOVETO_OPEN)
173         node->path[0].code = ART_MOVETO;
174 }
175
176 /* create a text node */
177 gfx_node_t   *gfx_new_text   (gfx_canvas_t *canvas,  
178                               double x, double y, gfx_color_t color,
179                               char* font, double size,                        
180                               double tabwidth, double angle,
181                               enum gfx_h_align_en h_align,
182                               enum gfx_v_align_en v_align,
183                               char* text){
184    gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
185    if (angle != 0.0){
186        /* currently we only support 0 and 270 */
187        angle = 270.0;
188    }
189    
190    node->text = strdup(text);
191    node->size = size;
192    node->filename = strdup(font);
193    node->x = x;
194    node->y = y;
195    node->angle = angle;   
196    node->color = color;
197    node->tabwidth = tabwidth;
198    node->halign = h_align;
199    node->valign = v_align;
200 #if 0
201   /* debugging: show text anchor
202      green is along x-axis, red is downward y-axis */
203    if (1) {
204      double a = 2 * M_PI * -node->angle / 360.0;
205      double cos_a = cos(a);
206      double sin_a = sin(a);
207      double len = 3;
208      gfx_new_line(canvas,
209          x, y,
210          x + len * cos_a, y - len * sin_a,
211          0.2, 0x00FF0000);
212      gfx_new_line(canvas,
213          x, y,
214          x + len * sin_a, y + len * cos_a,
215          0.2, 0xFF000000);
216    }
217 #endif
218    return node;
219 }
220
221 int           gfx_render(gfx_canvas_t *canvas, 
222                               art_u32 width, art_u32 height, 
223                               gfx_color_t background, FILE *fp){
224   switch (canvas->imgformat) {
225   case IF_PNG: 
226     return gfx_render_png (canvas, width, height, background, fp);
227   case IF_SVG: 
228     return gfx_render_svg (canvas, width, height, background, fp);
229   case IF_EPS:
230     return gfx_render_eps (canvas, width, height, background, fp);
231   case IF_PDF:
232     return gfx_render_pdf (canvas, width, height, background, fp);
233   default:
234     return -1;
235   }
236 }
237
238 double gfx_get_text_width ( gfx_canvas_t *canvas,
239                             double start, char* font, double size,
240                             double tabwidth, char* text){
241   switch (canvas->imgformat) {
242   case IF_PNG: 
243     return gfx_get_text_width_libart (canvas, start, font, size, tabwidth, text);
244   case IF_SVG: /* fall through */ 
245   case IF_EPS:
246   case IF_PDF:
247     return afm_get_text_width(start, font, size, tabwidth, text);
248   default:
249     return size * strlen(text);
250   }
251 }
252
253 double gfx_get_text_width_libart ( gfx_canvas_t *canvas,
254                             double start, char* font, double size,
255                             double tabwidth, char* text){
256
257   FT_GlyphSlot  slot;
258   FT_UInt       previous=0;
259   FT_UInt       glyph_index=0;
260   FT_Bool       use_kerning;
261   int           error;
262   FT_Face       face;
263   FT_Library    library=NULL;  
264   double        text_width=0;
265   FT_Init_FreeType( &library );
266   error = FT_New_Face( library, font, 0, &face );
267   if ( error ) return -1;
268   error = FT_Set_Char_Size(face,  size*64,size*64,  100,100);
269   if ( error ) return -1;
270
271   use_kerning = FT_HAS_KERNING(face);
272   slot = face->glyph;
273   for(;*text;text++) {  
274     previous = glyph_index;
275     glyph_index = FT_Get_Char_Index( face, *text);
276     
277     if (use_kerning && previous && glyph_index){
278       FT_Vector  delta;
279       FT_Get_Kerning( face, previous, glyph_index,
280                       0, &delta );
281       text_width += (double)delta.x / 64.0;
282       
283     }
284     error = FT_Load_Glyph( face, glyph_index, 0 );
285     if ( error ) {
286       FT_Done_FreeType(library);
287       return -1;
288     }
289     if (! previous) {
290       text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */        
291     }
292     text_width += (double)slot->metrics.horiAdvance / 64.0;
293   }
294   text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
295   text_width += (double)slot->metrics.width / 64.0; /* add just char width */
296   text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
297   FT_Done_FreeType(library);
298   return text_width;
299 }
300  
301 static void gfx_libart_close_path(gfx_canvas_t *canvas,
302         gfx_node_t *node, ArtVpath **vec)
303 {
304     /* libart must have end==start for closed paths,
305        even if using ART_MOVETO and not ART_MOVETO_OPEN
306        so add extra point which is the same as the starting point */
307     int points_max = node->points; /* scaled array has exact size */
308     int points = node->points - 1;
309     art_vpath_add_point (vec, &points, &points_max, ART_LINETO,
310             (**vec).x, (**vec).y);
311     art_vpath_add_point (vec, &points, &points_max, ART_END, 0, 0);
312 }
313
314 static void gfx_round_scaled_coordinates(gfx_canvas_t *canvas,
315         gfx_node_t *node, ArtVpath *vec)
316 {
317     while (vec->code != ART_END) {
318         vec->x = floor(vec->x - LINEOFFSET + 0.5) + LINEOFFSET;
319         vec->y = floor(vec->y - LINEOFFSET + 0.5) + LINEOFFSET;
320         vec++;
321     }
322 }
323
324 static int gfx_save_png (art_u8 *buffer, FILE *fp,
325                      long width, long height, long bytes_per_pixel);
326 /* render grafics into png image */
327 int           gfx_render_png (gfx_canvas_t *canvas, 
328                               art_u32 width, art_u32 height, 
329                               gfx_color_t background, FILE *fp){
330     
331     
332     FT_Library    library;
333     gfx_node_t *node = canvas->firstnode;    
334     art_u8 red = background >> 24, green = (background >> 16) & 0xff;
335     art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
336     unsigned long pys_width = width * canvas->zoom;
337     unsigned long pys_height = height * canvas->zoom;
338     const int bytes_per_pixel = 3;
339     unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
340     art_u8 *buffer = art_new (art_u8, rowstride*pys_height);
341     art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
342     FT_Init_FreeType( &library );
343     while(node){
344         switch (node->type) {
345         case GFX_LINE:
346         case GFX_AREA: {   
347             ArtVpath *vec;
348             double dst[6];     
349             ArtSVP *svp;
350             art_affine_scale(dst,canvas->zoom,canvas->zoom);
351             vec = art_vpath_affine_transform(node->path,dst);
352             if (node->closed_path)
353                 gfx_libart_close_path(canvas, node, &vec);
354             gfx_round_scaled_coordinates(canvas, node, vec);
355             if(node->type == GFX_LINE){
356                 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
357                                              ART_PATH_STROKE_CAP_ROUND,
358                                              node->size*canvas->zoom,1,1);
359             } else {
360                 svp = art_svp_from_vpath ( vec );
361             }
362             art_free(vec);
363             art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
364                                node->color, buffer, rowstride, NULL);
365             art_free(svp);
366             break;
367         }
368         case GFX_TEXT: {
369             int  error;
370             float text_width=0.0, text_height = 0.0;
371             unsigned char *text;
372             art_u8 fcolor[3],falpha;
373             FT_Face       face;
374             FT_GlyphSlot  slot;
375             FT_UInt       previous=0;
376             FT_UInt       glyph_index=0;
377             FT_Bool       use_kerning;
378
379             float pen_x = 0.0 , pen_y = 0.0;
380             /* double x,y; */
381             long   ix,iy,iz;
382             
383             fcolor[0] = node->color >> 24;
384             fcolor[1] = (node->color >> 16) & 0xff;
385             fcolor[2] = (node->color >> 8) & 0xff;
386             falpha = node->color & 0xff;
387             error = FT_New_Face( library,
388                                  (char *)node->filename,
389                                  0,
390                                  &face );
391             if ( error ) break;
392             use_kerning = FT_HAS_KERNING(face);
393
394             error = FT_Set_Char_Size(face,   /* handle to face object            */
395                                      (long)(node->size*64),
396                                      (long)(node->size*64),
397                                      (long)(100*canvas->zoom),
398                                      (long)(100*canvas->zoom));
399             if ( error ) break;
400             pen_x = node->x * canvas->zoom;
401             pen_y = node->y * canvas->zoom;
402             slot = face->glyph;
403
404             for(text=(unsigned char *)node->text;*text;text++) {        
405                 previous = glyph_index;
406                 glyph_index = FT_Get_Char_Index( face, *text);
407                 
408                 if (use_kerning && previous && glyph_index){
409                     FT_Vector  delta;
410                     FT_Get_Kerning( face, previous, glyph_index,
411                                     0, &delta );
412                     text_width += (double)delta.x / 64.0;
413                     
414                 }
415                 error = FT_Load_Glyph( face, glyph_index, 0 );
416                 if ( error ) break;
417                 if (previous == 0){
418                   pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */   
419                   text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */    
420                 }
421                 if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
422                   text_height = (double)slot->metrics.horiBearingY / 64.0;
423                 }
424                 text_width += (double)slot->metrics.horiAdvance / 64.0;
425             }
426             text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
427             text_width += (double)slot->metrics.width / 64.0; /* add just char width */
428             text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
429             
430             switch(node->halign){
431             case GFX_H_RIGHT:  pen_x -= text_width; break;
432             case GFX_H_CENTER: pen_x -= text_width / 2.0; break;          
433             case GFX_H_LEFT: break;          
434             case GFX_H_NULL: break;          
435             }
436
437             switch(node->valign){
438             case GFX_V_TOP:    pen_y += text_height; break;
439             case GFX_V_CENTER: pen_y += text_height / 2.0; break;          
440             case GFX_V_BOTTOM: break;          
441             case GFX_V_NULL: break;          
442             }
443
444             glyph_index=0;
445             for(text=(unsigned char *)node->text;*text;text++) {
446                 int gr;          
447                 previous = glyph_index;
448                 glyph_index = FT_Get_Char_Index( face, *text);
449                 
450                 if (use_kerning && previous && glyph_index){
451                     FT_Vector  delta;
452                     FT_Get_Kerning( face, previous, glyph_index,
453                                     0, &delta );
454                     pen_x += (double)delta.x / 64.0;
455                     
456                 }
457                 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
458                 if ( error ) break;
459                 gr = slot->bitmap.num_grays -1;
460                 for (iy=0; iy < slot->bitmap.rows; iy++){
461                     long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
462                     if (buf_y < 0 || buf_y >= pys_height) continue;
463                     buf_y *= rowstride;
464                     for (ix=0;ix < slot->bitmap.width;ix++){
465                         long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
466                         art_u8 font_alpha;
467                         
468                         if (buf_x < 0 || buf_x >= pys_width) continue;
469                         buf_x *=  bytes_per_pixel ;
470                         font_alpha =  *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
471                         font_alpha =  (art_u8)((double)font_alpha / gr * falpha);
472                         for (iz = 0; iz < 3; iz++){
473                             art_u8 *orig = buffer + buf_y + buf_x + iz;
474                             *orig =  (art_u8)((double)*orig / gr * ( gr - font_alpha) +
475                                               (double)fcolor[iz] / gr * (font_alpha));
476                         }
477                     }
478                 }
479                 pen_x += (double)slot->metrics.horiAdvance / 64.0;
480             }
481         }
482         }
483         node = node->next;
484     }  
485     gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
486     art_free(buffer);
487     FT_Done_FreeType( library );
488     return 0;    
489 }
490
491 /* free memory used by nodes this will also remove memory required for
492    associated paths and svcs ... but not for text strings */
493 int
494 gfx_destroy    (gfx_canvas_t *canvas){  
495   gfx_node_t *next,*node = canvas->firstnode;
496   while(node){
497     next = node->next;
498     art_free(node->path);
499     art_free(node->svp);
500     free(node->text);
501     free(node->filename);
502     art_free(node);
503     node = next;
504   }
505   return 0;
506 }
507  
508 static int gfx_save_png (art_u8 *buffer, FILE *fp,  long width, long height, long bytes_per_pixel){
509   png_structp png_ptr = NULL;
510   png_infop   info_ptr = NULL;
511   int i;
512   png_bytep *row_pointers;
513   int rowstride = width * bytes_per_pixel;
514   png_text text[2];
515   
516   if (fp == NULL)
517     return (1);
518
519   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
520   if (png_ptr == NULL)
521    {
522       return (1);
523    }
524    row_pointers = (png_bytepp)png_malloc(png_ptr,
525                                      height*sizeof(png_bytep));
526
527   info_ptr = png_create_info_struct(png_ptr);
528
529   if (info_ptr == NULL)
530     {
531       png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
532       return (1);
533     }
534
535   if (setjmp(png_jmpbuf(png_ptr)))
536     {
537       /* If we get here, we had a problem writing the file */
538       png_destroy_write_struct(&png_ptr, &info_ptr);
539       return (1);
540     }
541
542   png_init_io(png_ptr, fp);
543   png_set_IHDR (png_ptr, info_ptr,width, height,
544                 8, PNG_COLOR_TYPE_RGB,
545                 PNG_INTERLACE_NONE,
546                 PNG_COMPRESSION_TYPE_DEFAULT,
547                 PNG_FILTER_TYPE_DEFAULT);
548
549   text[0].key = "Software";
550   text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
551   text[0].compression = PNG_TEXT_COMPRESSION_NONE;
552   png_set_text (png_ptr, info_ptr, text, 1);
553
554   /* Write header data */
555   png_write_info (png_ptr, info_ptr);
556
557   for (i = 0; i < height; i++)
558     row_pointers[i] = (png_bytep) (buffer + i*rowstride);
559
560   png_write_image(png_ptr, row_pointers);
561   png_write_end(png_ptr, info_ptr);
562   png_destroy_write_struct(&png_ptr, &info_ptr);
563   return 1;
564 }
565
566  
567 /* ------- SVG -------
568    SVG reference:
569    http://www.w3.org/TR/SVG/
570 */
571 static int svg_indent = 0;
572 static int svg_single_line = 0;
573 static const char *svg_default_font = "Helvetica";
574 typedef struct svg_dash
575 {
576   int dash_enable;
577   double dash_adjust, dash_len, dash_offset;
578   double adjusted_on, adjusted_off;
579 } svg_dash;
580
581
582 static void svg_print_indent(FILE *fp)
583 {
584   int i;
585    for (i = svg_indent - svg_single_line; i > 0; i--) {
586      putc(' ', fp);
587      putc(' ', fp);
588    }
589 }
590  
591 static void svg_start_tag(FILE *fp, const char *name)
592 {
593    svg_print_indent(fp);
594    putc('<', fp);
595    fputs(name, fp);
596    svg_indent++;
597 }
598  
599 static void svg_close_tag_single_line(FILE *fp)
600 {
601    svg_single_line++;
602    putc('>', fp);
603 }
604  
605 static void svg_close_tag(FILE *fp)
606 {
607    putc('>', fp);
608    if (!svg_single_line)
609      putc('\n', fp);
610 }
611  
612 static void svg_end_tag(FILE *fp, const char *name)
613 {
614    /* name is NULL if closing empty-node tag */
615    svg_indent--;
616    if (svg_single_line)
617      svg_single_line--;
618    else if (name)
619      svg_print_indent(fp);
620    if (name != NULL) {
621      fputs("</", fp);
622      fputs(name, fp);
623    } else {
624      putc('/', fp);
625    }
626    svg_close_tag(fp);
627 }
628  
629 static void svg_close_tag_empty_node(FILE *fp)
630 {
631    svg_end_tag(fp, NULL);
632 }
633  
634 static void svg_write_text(FILE *fp, const char *text)
635 {
636    const unsigned char *p, *start, *last;
637    unsigned int ch;
638    p = (const unsigned char*)text;
639    if (!p)
640      return;
641    /* trim leading spaces */
642    while (*p == ' ')
643      p++;
644    start = p;
645    /* trim trailing spaces */
646    last = p - 1;
647    while ((ch = *p) != 0) {
648      if (ch != ' ')
649        last = p;
650      p++;
651   }
652   /* encode trimmed text */
653   p = start;
654   while (p <= last) {
655     ch = *p++;
656     ch = afm_host2unicode(ch); /* unsafe macro */
657     switch (ch) {
658     case '&': fputs("&amp;", fp); break;
659     case '<': fputs("&lt;", fp); break;
660     case '>': fputs("&gt;", fp); break;
661     case '"': fputs("&quot;", fp); break;
662     default:
663       if (ch >= 127)
664         fprintf(fp, "&#%d;", ch);
665       else
666         putc(ch, fp);
667      }
668    }
669 }
670  
671 static void svg_format_number(char *buf, int bufsize, double d)
672 {
673    /* omit decimals if integer to reduce filesize */
674    char *p;
675    snprintf(buf, bufsize, "%.2f", d);
676    p = buf; /* doesn't trust snprintf return value */
677    while (*p)
678      p++;
679    while (--p > buf) {
680      char ch = *p;
681      if (ch == '0') {
682        *p = '\0'; /* zap trailing zeros */
683        continue;
684      }
685      if (ch == '.')
686        *p = '\0'; /* zap trailing dot */
687      break;
688    }
689 }
690  
691 static void svg_write_number(FILE *fp, double d)
692 {
693    char buf[60];
694    svg_format_number(buf, sizeof(buf), d);
695    fputs(buf, fp);
696 }
697
698 static int svg_color_is_black(int c)
699 {
700   /* gfx_color_t is RRGGBBAA */
701   return c == 0x000000FF;
702 }
703  
704 static void svg_write_color(FILE *fp, gfx_color_t c, const char *attr)
705 {
706   /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB and #RGB like html */
707   gfx_color_t rrggbb = (int)((c >> 8) & 0xFFFFFF);
708   gfx_color_t opacity = c & 0xFF;
709   fprintf(fp, " %s=\"", attr);
710   if ((rrggbb & 0x0F0F0F) == ((rrggbb >> 4) & 0x0F0F0F)) {
711      /* css2 short form, #rgb is #rrggbb, not #r0g0b0 */
712     fprintf(fp, "#%03lX",
713           ( ((rrggbb >> 8) & 0xF00)
714           | ((rrggbb >> 4) & 0x0F0)
715           | ( rrggbb       & 0x00F)));
716    } else {
717     fprintf(fp, "#%06lX", rrggbb);
718    }
719   fputs("\"", fp);
720   if (opacity != 0xFF) {
721     fprintf(fp, " stroke-opacity=\"");
722     svg_write_number(fp, opacity / 255.0);
723     fputs("\"", fp);
724  }
725 }
726  
727 static void svg_get_dash(gfx_node_t *node, svg_dash *d)
728 {
729   double offset;
730   int mult;
731   if (node->dash_on <= 0 || node->dash_off <= 0) {
732     d->dash_enable = 0;
733     return;
734   }
735   d->dash_enable = 1;
736   d->dash_len = node->dash_on + node->dash_off;
737   /* dash on/off adjustment due to round caps */
738   d->dash_adjust = 0.8 * node->size;
739   d->adjusted_on = node->dash_on - d->dash_adjust;
740   if (d->adjusted_on < 0.01)
741       d->adjusted_on = 0.01;
742   d->adjusted_off = d->dash_len - d->adjusted_on;
743   /* dash offset calc */
744   if (node->path[0].x == node->path[1].x) /* only good for horz/vert lines */
745     offset = node->path[0].y;
746   else
747     offset = node->path[0].x;
748   mult = (int)fabs(offset / d->dash_len);
749   d->dash_offset = offset - mult * d->dash_len;
750   if (node->path[0].x < node->path[1].x || node->path[0].y < node->path[1].y)
751     d->dash_offset = d->dash_len - d->dash_offset;
752 }
753
754 static int svg_dash_equal(svg_dash *a, svg_dash *b)
755 {
756   if (a->dash_enable != b->dash_enable)
757     return 0;
758   if (a->adjusted_on != b->adjusted_on)
759     return 0;
760   if (a->adjusted_off != b->adjusted_off)
761     return 0;
762   /* rest of properties will be the same when on+off are */
763   return 1;
764 }
765
766 static void svg_common_path_attributes(FILE *fp, gfx_node_t *node)
767 {
768   svg_dash dash_info;
769   svg_get_dash(node, &dash_info);
770   fputs(" stroke-width=\"", fp);
771   svg_write_number(fp, node->size);
772   fputs("\"", fp);
773   svg_write_color(fp, node->color, "stroke");
774   fputs(" fill=\"none\"", fp);
775   if (dash_info.dash_enable) {
776     if (dash_info.dash_offset != 0) {
777       fputs(" stroke-dashoffset=\"", fp);
778       svg_write_number(fp, dash_info.dash_offset);
779       fputs("\"", fp);
780     }
781     fputs(" stroke-dasharray=\"", fp);
782     svg_write_number(fp, dash_info.adjusted_on);
783     fputs(",", fp);
784     svg_write_number(fp, dash_info.adjusted_off);
785     fputs("\"", fp);
786   }
787 }
788
789 static int svg_is_int_step(double a, double b)
790 {
791    double diff = fabs(a - b);
792    return floor(diff) == diff;
793 }
794  
795 static int svg_path_straight_segment(FILE *fp,
796      double lastA, double currentA, double currentB,
797      gfx_node_t *node,
798      int segment_idx, int isx, char absChar, char relChar)
799 {
800    if (!svg_is_int_step(lastA, currentA)) {
801      putc(absChar, fp);
802      svg_write_number(fp, currentA);
803      return 0;
804    }
805    if (segment_idx < node->points - 1) {
806      ArtVpath *vec = node->path + segment_idx + 1;
807      if (vec->code == ART_LINETO) {
808        double nextA = (isx ? vec->x : vec->y) - LINEOFFSET;
809        double nextB = (isx ? vec->y : vec->x) - LINEOFFSET;
810        if (nextB == currentB
811            && ((currentA >= lastA) == (nextA >= currentA))
812            && svg_is_int_step(currentA, nextA)) {
813          return 1; /* skip to next as it is a straight line  */
814        }
815      }
816    }
817    putc(relChar, fp);
818    svg_write_number(fp, currentA - lastA);
819    return 0;
820 }
821  
822 static void svg_path(FILE *fp, gfx_node_t *node, int multi)
823 {
824    int i;
825    double lastX = 0, lastY = 0;
826    /* for straight lines <path..> tags take less space than
827       <line..> tags because of the efficient packing
828       in the 'd' attribute */
829    svg_start_tag(fp, "path");
830   if (!multi)
831     svg_common_path_attributes(fp, node);
832    fputs(" d=\"", fp);
833    /* specification of the 'd' attribute: */
834    /* http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation */
835    for (i = 0; i < node->points; i++) {
836      ArtVpath *vec = node->path + i;
837      double x = vec->x - LINEOFFSET;
838      double y = vec->y - LINEOFFSET;
839      switch (vec->code) {
840      case ART_MOVETO_OPEN: /* fall-through */
841      case ART_MOVETO:
842        putc('M', fp);
843        svg_write_number(fp, x);
844        putc(',', fp);
845        svg_write_number(fp, y);
846        break;
847      case ART_LINETO:
848        /* try optimize filesize by using minimal lineto commands */
849        /* without introducing rounding errors. */
850        if (x == lastX) {
851          if (svg_path_straight_segment(fp, lastY, y, x, node, i, 0, 'V', 'v'))
852            continue;
853        } else if (y == lastY) {
854          if (svg_path_straight_segment(fp, lastX, x, y, node, i, 1, 'H', 'h'))
855            continue;
856        } else {
857          putc('L', fp);
858          svg_write_number(fp, x);
859          putc(',', fp);
860          svg_write_number(fp, y);
861        }
862        break;
863      case ART_CURVETO: break; /* unsupported */
864      case ART_END: break; /* nop */
865      }
866      lastX = x;
867      lastY = y;
868    }
869   if (node->closed_path)
870     fputs(" Z", fp);
871    fputs("\"", fp);
872    svg_close_tag_empty_node(fp);
873 }
874  
875 static void svg_multi_path(FILE *fp, gfx_node_t **nodeR)
876 {
877    /* optimize for multiple paths with the same color, penwidth, etc. */
878    int num = 1;
879    gfx_node_t *node = *nodeR;
880    gfx_node_t *next = node->next;
881    while (next) {
882      if (next->type != node->type
883          || next->size != node->size
884         || next->color != node->color
885         || next->dash_on != node->dash_on
886         || next->dash_off != node->dash_off)
887        break;
888      next = next->next;
889      num++;
890    }
891    if (num == 1) {
892      svg_path(fp, node, 0);
893      return;
894    }
895    svg_start_tag(fp, "g");
896   svg_common_path_attributes(fp, node);
897    svg_close_tag(fp);
898    while (num && node) {
899      svg_path(fp, node, 1);
900      if (!--num)
901        break;
902      node = node->next;
903      *nodeR = node;
904    }
905    svg_end_tag(fp, "g");
906 }
907  
908 static void svg_area(FILE *fp, gfx_node_t *node)
909 {
910    int i;
911    double startX = 0, startY = 0;
912    svg_start_tag(fp, "polygon");
913   fputs(" ", fp);
914   svg_write_color(fp, node->color, "fill");
915   fputs(" points=\"", fp);
916    for (i = 0; i < node->points; i++) {
917      ArtVpath *vec = node->path + i;
918      double x = vec->x - LINEOFFSET;
919      double y = vec->y - LINEOFFSET;
920      switch (vec->code) {
921        case ART_MOVETO_OPEN: /* fall-through */
922        case ART_MOVETO:
923          svg_write_number(fp, x);
924          putc(',', fp);
925          svg_write_number(fp, y);
926          startX = x;
927          startY = y;
928          break;
929        case ART_LINETO:
930          if (i == node->points - 2
931                         && node->path[i + 1].code == ART_END
932              && fabs(x - startX) < 0.001 && fabs(y - startY) < 0.001) {
933            break; /* poly area always closed, no need for last point */
934          }
935          putc(' ', fp);
936          svg_write_number(fp, x);
937          putc(',', fp);
938          svg_write_number(fp, y);
939          break;
940        case ART_CURVETO: break; /* unsupported */
941        case ART_END: break; /* nop */
942      }
943    }
944    fputs("\"", fp);
945    svg_close_tag_empty_node(fp);
946 }
947  
948 static void svg_text(FILE *fp, gfx_node_t *node)
949 {
950    double x = node->x - LINEOFFSET;
951    double y = node->y - LINEOFFSET;
952    if (node->angle != 0) {
953      svg_start_tag(fp, "g");
954      fputs(" transform=\"translate(", fp);
955      svg_write_number(fp, x);
956      fputs(",", fp);
957      svg_write_number(fp, y);
958      fputs(") rotate(", fp);
959      svg_write_number(fp, node->angle);
960      fputs(")\"", fp);
961      x = y = 0;
962      svg_close_tag(fp);
963    }
964    switch (node->valign) {
965    case GFX_V_TOP:  y += node->size; break;
966    case GFX_V_CENTER: y += node->size / 3; break;
967    case GFX_V_BOTTOM: break;
968    case GFX_V_NULL: break;
969    }
970    svg_start_tag(fp, "text");
971    fputs(" x=\"", fp);
972    svg_write_number(fp, x);
973    fputs("\" y=\"", fp);
974    svg_write_number(fp, y);
975   if (strcmp(node->filename, svg_default_font))
976     fprintf(fp, " font-family=\"%s\"", node->filename);
977    fputs("\" font-size=\"", fp);
978    svg_write_number(fp, node->size);
979    fputs("\"", fp);
980   if (!svg_color_is_black(node->color))
981     svg_write_color(fp, node->color, "fill");
982    switch (node->halign) {
983    case GFX_H_RIGHT:  fputs(" text-anchor=\"end\"", fp); break;
984    case GFX_H_CENTER: fputs(" text-anchor=\"middle\"", fp); break;
985    case GFX_H_LEFT: break;
986    case GFX_H_NULL: break;
987    }
988    svg_close_tag_single_line(fp);
989    /* support for node->tabwidth missing */
990    svg_write_text(fp, node->text);
991    svg_end_tag(fp, "text");
992    if (node->angle != 0)
993      svg_end_tag(fp, "g");
994 }
995  
996 int       gfx_render_svg (gfx_canvas_t *canvas,
997                  art_u32 width, art_u32 height,
998                  gfx_color_t background, FILE *fp){
999    gfx_node_t *node = canvas->firstnode;
1000    fputs(
1001 "<?xml version=\"1.0\" standalone=\"no\"?>\n"
1002 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"
1003 "   \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
1004 "<!--\n"
1005 "   SVG file created by RRDtool,\n"
1006 "   Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch\n"
1007 "\n"
1008 "   The width/height attributes in the outhermost svg node\n"
1009 "   are just default sizes for the browser which is used\n"
1010 "   if the svg file is openened directly without being\n"
1011 "   embedded in an html file.\n"
1012 "   The viewBox is the local coord system for rrdtool.\n"
1013 "-->\n", fp);
1014    svg_start_tag(fp, "svg");
1015    fputs(" width=\"", fp);
1016   svg_write_number(fp, width * canvas->zoom);
1017    fputs("\" height=\"", fp);
1018   svg_write_number(fp, height * canvas->zoom);
1019    fputs("\" x=\"0\" y=\"0\" viewBox=\"", fp);
1020    svg_write_number(fp, -LINEOFFSET);
1021    fputs(" ", fp);
1022    svg_write_number(fp, -LINEOFFSET);
1023    fputs(" ", fp);
1024    svg_write_number(fp, width - LINEOFFSET);
1025    fputs(" ", fp);
1026    svg_write_number(fp, height - LINEOFFSET);
1027    fputs("\" preserveAspectRatio=\"xMidYMid\"", fp);
1028   fprintf(fp, " font-family=\"%s\"", svg_default_font); /* default font */
1029   fputs(" stroke-linecap=\"round\" stroke-linejoin=\"round\"", fp);
1030    svg_close_tag(fp);
1031    svg_start_tag(fp, "rect");
1032    fprintf(fp, " x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", width, height);
1033   svg_write_color(fp, background, "fill");
1034    svg_close_tag_empty_node(fp);
1035    while (node) {
1036      switch (node->type) {
1037      case GFX_LINE:
1038        svg_multi_path(fp, &node);
1039        break;
1040      case GFX_AREA:
1041        svg_area(fp, node);
1042        break;
1043      case GFX_TEXT:
1044        svg_text(fp, node);
1045      }
1046      node = node->next;
1047    }
1048    svg_end_tag(fp, "svg");
1049    return 0;
1050 }
1051
1052 /* ------- EPS -------
1053    EPS and Postscript references:
1054    http://partners.adobe.com/asn/developer/technotes/postscript.html
1055 */
1056
1057 typedef struct eps_font
1058 {
1059   const char *ps_font;
1060   int id;
1061   struct eps_font *next;
1062 } eps_font;
1063
1064 typedef struct eps_state
1065 {
1066   FILE *fp;
1067   gfx_canvas_t *canvas;
1068   art_u32 page_width, page_height;
1069   eps_font *font_list;
1070   /*--*/
1071   gfx_color_t color;
1072   const char *font;
1073   double font_size;
1074   double line_width;
1075   int linecap, linejoin;
1076   int has_dash;
1077 } eps_state;
1078
1079 static void eps_set_color(eps_state *state, gfx_color_t color)
1080 {
1081    /* gfx_color_t is RRGGBBAA */
1082   if (state->color == color)
1083     return;
1084   fprintf(state->fp, "%d %d %d Rgb\n",
1085       (int)((color >> 24) & 255),
1086       (int)((color >> 16) & 255),
1087       (int)((color >>  8) & 255));
1088   state->color = color;
1089 }
1090
1091 static int eps_add_font(eps_state *state, gfx_node_t *node)
1092 {
1093   /* The fonts list could be postponed to the end using
1094      (atend), but let's be nice and have them in the header. */
1095   const char *ps_font = afm_get_font_postscript_name(node->filename);
1096   eps_font *ef;
1097   for (ef = state->font_list; ef; ef = ef->next) {
1098     if (!strcmp(ps_font, ef->ps_font))
1099       return 0;
1100   }
1101   ef = malloc(sizeof(eps_font));
1102   if (ef == NULL) {
1103     rrd_set_error("malloc for eps_font");
1104     return -1;
1105   }
1106   ef->next = state->font_list;
1107   ef->ps_font = ps_font;
1108   state->font_list = ef;
1109   return 0;
1110 }
1111
1112 static void eps_list_fonts(eps_state *state, const char *dscName)
1113 {
1114   eps_font *ef;
1115   int lineLen = strlen(dscName);
1116   if (!state->font_list)
1117     return;
1118   fputs(dscName, state->fp);
1119   for (ef = state->font_list; ef; ef = ef->next) {
1120     int nameLen = strlen(ef->ps_font);
1121     if (lineLen + nameLen > 100 && lineLen) {
1122       fputs("\n", state->fp);
1123       fputs("%%- \n", state->fp);
1124       lineLen = 5;
1125     } else {
1126       fputs(" ", state->fp);
1127       lineLen++;
1128     }
1129     fputs(ef->ps_font, state->fp);
1130     lineLen += nameLen;
1131   }
1132   fputs("\n", state->fp);
1133 }
1134
1135 static void eps_define_fonts(eps_state *state)
1136 {
1137   eps_font *ef;
1138   if (!state->font_list)
1139     return;
1140   for (ef = state->font_list; ef; ef = ef->next) {
1141     /* PostScript¨ LANGUAGE REFERENCE third edition
1142        page 349 */
1143     fprintf(state->fp,
1144         "%%\n"
1145         "/%s findfont dup length dict begin\n"
1146         "{ 1 index /FID ne {def} {pop pop} ifelse } forall\n"
1147         "/Encoding ISOLatin1Encoding def\n"
1148         "currentdict end\n"
1149         "/%s-ISOLatin1 exch definefont pop\n"
1150         "/SetFont-%s { /%s-ISOLatin1 findfont exch scalefont setfont } bd\n",
1151         ef->ps_font, ef->ps_font, ef->ps_font, ef->ps_font);
1152   }
1153 }
1154
1155 static int eps_prologue(eps_state *state)
1156 {
1157   gfx_node_t *node;
1158   fputs(
1159     "%!PS-Adobe-3.0 EPSF-3.0\n"
1160     "%%Creator: RRDtool 1.1.x, Tobias Oetiker, http://tobi.oetiker.ch\n"
1161     /* can't like weird chars here */
1162     "%%Title: (RRDTool output)\n"
1163     "%%DocumentData: Clean7Bit\n"
1164     "", state->fp);
1165   fprintf(state->fp, "%%%%BoundingBox: 0 0 %d %d\n",
1166     state->page_width, state->page_height);
1167   for (node = state->canvas->firstnode; node; node = node->next) {
1168     if (node->type == GFX_TEXT && eps_add_font(state, node) == -1)
1169       return -1;
1170   }
1171   eps_list_fonts(state, "%%DocumentFonts:");
1172   eps_list_fonts(state, "%%DocumentNeededFonts:");
1173   fputs(
1174       "%%EndComments\n"
1175       "%%BeginProlog\n"
1176       "%%EndProlog\n" /* must have, or BoundingBox is ignored */
1177       "/bd { bind def } bind def\n"
1178       "", state->fp);
1179   fprintf(state->fp, "/X { %.2f add } bd\n", LINEOFFSET);
1180   fputs(
1181       "/X2 {X exch X exch} bd\n"
1182       "/M {X2 moveto} bd\n"
1183       "/L {X2 lineto} bd\n"
1184       "/m {moveto} bd\n"
1185       "/l {lineto} bd\n"
1186       "/S {stroke} bd\n"
1187       "/CP {closepath} bd\n"
1188       "/WS {setlinewidth stroke} bd\n"
1189       "/F {fill} bd\n"
1190       "/TaL { } bd\n"
1191       "/TaC {dup stringwidth pop neg 2 div 0 rmoveto } bd\n"
1192       "/TaR {dup stringwidth pop neg 0 rmoveto } bd\n"
1193       "/TL {moveto TaL show} bd\n"
1194       "/TC {moveto TaC show} bd\n"
1195       "/TR {moveto TaR show} bd\n"
1196       "/Rgb { 255.0 div 3 1 roll\n"
1197       "       255.0 div 3 1 roll \n"
1198       "       255.0 div 3 1 roll setrgbcolor } bd\n"
1199       "", state->fp);
1200   eps_define_fonts(state);
1201   return 0;
1202 }
1203
1204 static void eps_clear_dash(eps_state *state)
1205 {
1206   if (!state->has_dash)
1207     return;
1208   state->has_dash = 0;
1209   fputs("[1 0] 0 setdash\n", state->fp);
1210 }
1211
1212 static void eps_write_linearea(eps_state *state, gfx_node_t *node)
1213 {
1214   int i;
1215   FILE *fp = state->fp;
1216   int useOffset = 0;
1217   int clearDashIfAny = 1;
1218   eps_set_color(state, node->color);
1219   if (node->type == GFX_LINE) {
1220     svg_dash dash_info;
1221     if (state->linecap != 1) {
1222       fputs("1 setlinecap\n", fp);
1223       state->linecap = 1;
1224     }
1225     if (state->linejoin != 1) {
1226       fputs("1 setlinejoin\n", fp);
1227       state->linejoin = 1;
1228     }
1229     svg_get_dash(node, &dash_info);
1230     if (dash_info.dash_enable) {
1231       clearDashIfAny = 0;
1232       state->has_dash = 1;
1233       fputs("[", fp);
1234       svg_write_number(fp, dash_info.adjusted_on);
1235       fputs(" ", fp);
1236       svg_write_number(fp, dash_info.adjusted_off);
1237       fputs("] ", fp);
1238       svg_write_number(fp, dash_info.dash_offset);
1239       fputs(" setdash\n", fp);
1240     }
1241   }
1242   if (clearDashIfAny)
1243     eps_clear_dash(state);
1244   for (i = 0; i < node->points; i++) {
1245     ArtVpath *vec = node->path + i;
1246     double x = vec->x;
1247     double y = state->page_height - vec->y;
1248     if (vec->code == ART_MOVETO_OPEN || vec->code == ART_MOVETO)
1249       useOffset = (fabs(x - floor(x) - 0.5) < 0.01 && fabs(y - floor(y) - 0.5) < 0.01);
1250     if (useOffset) {
1251       x -= LINEOFFSET;
1252       y -= LINEOFFSET;
1253     }
1254     switch (vec->code) {
1255     case ART_MOVETO_OPEN: /* fall-through */
1256     case ART_MOVETO:
1257       svg_write_number(fp, x);
1258       fputc(' ', fp);
1259       svg_write_number(fp, y);
1260       fputc(' ', fp);
1261       fputs(useOffset ? "M\n" : "m\n", fp);
1262       break;
1263     case ART_LINETO:
1264       svg_write_number(fp, x);
1265       fputc(' ', fp);
1266       svg_write_number(fp, y);
1267       fputc(' ', fp);
1268       fputs(useOffset ? "L\n" : "l\n", fp);
1269       break;
1270     case ART_CURVETO: break; /* unsupported */
1271     case ART_END: break; /* nop */
1272     }
1273   }
1274   if (node->type == GFX_LINE) {
1275     if (node->closed_path)
1276       fputs("CP ", fp);
1277     if (node->size != state->line_width) {
1278       state->line_width = node->size;
1279       svg_write_number(fp, state->line_width);
1280       fputs(" WS\n", fp);
1281     } else {
1282       fputs("S\n", fp);
1283     }
1284    } else {
1285     fputs("F\n", fp);
1286    }
1287 }
1288
1289 static void eps_write_text(eps_state *state, gfx_node_t *node)
1290 {
1291   FILE *fp = state->fp;
1292   const unsigned char *p;
1293   const char *ps_font = afm_get_font_postscript_name(node->filename);
1294   char align = 'L';
1295   double x = node->x;
1296   double y = state->page_height - node->y, ydelta = 0;
1297   int lineLen = 0;
1298   eps_set_color(state, node->color);
1299   if (strcmp(ps_font, state->font) || node->size != state->font_size) {
1300     state->font = ps_font;
1301     state->font_size = node->size;
1302     svg_write_number(fp, state->font_size);
1303     fprintf(fp, " SetFont-%s\n", state->font);
1304   }
1305   fputs("(", fp);
1306   lineLen = 20;
1307   for (p = (const unsigned char*)node->text; *p; p++) {
1308     if (lineLen > 70) {
1309       fputs("\\\n", fp); /* backslash and \n */
1310       lineLen = 0;
1311     }
1312     switch (*p) {
1313       case '(':
1314       case ')':
1315       case '\\':
1316       case '\n':
1317       case '\r':
1318       case '\t':
1319         fputc('\\', fp);
1320         lineLen++;
1321         /* fall-through */
1322       default:
1323         if (*p >= 126)
1324           fprintf(fp, "\\%03o", *p);
1325         else
1326           fputc(*p, fp);
1327         lineLen++;
1328     }
1329   }
1330   fputs(") ", fp);
1331   switch(node->valign){
1332   case GFX_V_TOP:    ydelta = -node->size; break;
1333   case GFX_V_CENTER: ydelta = -node->size / 3.0; break;          
1334   case GFX_V_BOTTOM: break;          
1335   case GFX_V_NULL: break;          
1336   }
1337   if (node->angle == 0)
1338     y += ydelta;
1339   switch (node->halign) {
1340   case GFX_H_RIGHT:  align = 'R'; break;
1341   case GFX_H_CENTER: align = 'C'; break;
1342   case GFX_H_LEFT: align= 'L'; break;
1343   case GFX_H_NULL: align= 'L'; break;
1344   }
1345   if (node->angle != 0) {
1346     fputs("\n", fp);
1347     fputs("  gsave ", fp);
1348     svg_write_number(fp, x);
1349     fputc(' ', fp);
1350     svg_write_number(fp, y);
1351     fputs(" translate ", fp);
1352     svg_write_number(fp, -node->angle);
1353     fputs(" rotate 0 ", fp);
1354     svg_write_number(fp, ydelta);
1355     fputs(" moveto ", fp);
1356     fprintf(fp, "Ta%c", align);
1357     fputs(" show grestore\n", fp);
1358   } else {
1359     svg_write_number(fp, x);
1360     fputc(' ', fp);
1361     svg_write_number(fp, y);
1362     fputs(" T", fp);
1363     fputc(align, fp);
1364     fputc('\n', fp);
1365   }
1366 }
1367
1368 static int eps_write_content(eps_state *state)
1369 {
1370   gfx_node_t *node;
1371   fputs("%\n", state->fp);
1372   for (node = state->canvas->firstnode; node; node = node->next) {
1373     switch (node->type) {
1374     case GFX_LINE:
1375     case GFX_AREA:
1376       eps_write_linearea(state, node);
1377       break;
1378     case GFX_TEXT:
1379       eps_write_text(state, node);
1380       break;
1381     }
1382   }
1383   return 0;
1384 }
1385
1386 int       gfx_render_eps (gfx_canvas_t *canvas,
1387                  art_u32 width, art_u32 height,
1388                  gfx_color_t background, FILE *fp){
1389   struct eps_state state;
1390   state.fp = fp;
1391   state.canvas = canvas;
1392   state.page_width = width;
1393   state.page_height = height;
1394   state.font = "no-default-font";
1395   state.font_size = -1;
1396   state.color = 0; /* black */
1397   state.font_list = NULL;
1398   state.linecap = -1;
1399   state.linejoin = -1;
1400   state.has_dash = 0;
1401   if (eps_prologue(&state) == -1)
1402     return -1;
1403   eps_set_color(&state, background);
1404   fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
1405       height, width, height, width);
1406   if (eps_write_content(&state) == -1)
1407     return 0;
1408   fputs("showpage\n", fp);
1409   fputs("%%EOF\n", fp);
1410   while (state.font_list) {
1411     eps_font *next = state.font_list->next;
1412     free(state.font_list);
1413     state.font_list = next;
1414   }
1415   return 0;
1416 }
1417
1418 /* ------- PDF -------
1419    PDF references page:
1420    http://partners.adobe.com/asn/developer/technotes/acrobatpdf.html
1421 */
1422
1423 typedef struct pdf_buffer
1424 {
1425   int id, is_obj, is_dict, is_stream, pdf_file_pos;
1426   char *data;
1427   int alloc_size, current_size;
1428   struct pdf_buffer *previous_buffer, *next_buffer;
1429   struct pdf_state *state;
1430 } pdf_buffer;
1431
1432 typedef struct pdf_font
1433 {
1434   const char *ps_font;
1435   pdf_buffer obj;
1436   struct pdf_font *next;
1437 } pdf_font;
1438
1439 typedef struct pdf_state
1440 {
1441   FILE *fp;
1442   gfx_canvas_t *canvas;
1443   art_u32 page_width, page_height;
1444   pdf_font *font_list;
1445   pdf_buffer *first_buffer, *last_buffer;
1446   int pdf_file_pos;
1447   int has_failed;
1448   /*--*/
1449   gfx_color_t stroke_color, fill_color;
1450   int font_id;
1451   double font_size;
1452   double line_width;
1453   svg_dash dash;
1454   int linecap, linejoin;
1455   int last_obj_id;
1456   /*--*/
1457   pdf_buffer pdf_header;
1458   pdf_buffer catalog_obj, pages_obj, page1_obj;
1459   pdf_buffer fontsdict_obj;
1460   pdf_buffer graph_stream;
1461 } pdf_state;
1462
1463 static void pdf_init_buffer(pdf_state *state, pdf_buffer *buf)
1464 {
1465   int initial_size = 32;
1466   buf->state = state;
1467   buf->id = -42;
1468   buf->alloc_size = 0;
1469   buf->current_size = 0;
1470   buf->data = (char*)malloc(initial_size);
1471   buf->is_obj = 0;
1472   buf->previous_buffer = NULL;
1473   buf->next_buffer = NULL;
1474   if (buf->data == NULL) {
1475     rrd_set_error("malloc for pdf_buffer data");
1476     state->has_failed = 1;
1477     return;
1478   }
1479   buf->alloc_size = initial_size;
1480   if (state->last_buffer)
1481     state->last_buffer->next_buffer = buf;
1482   if (state->first_buffer == NULL)
1483     state->first_buffer = buf;
1484   buf->previous_buffer = state->last_buffer;
1485   state->last_buffer = buf;
1486 }
1487
1488 static void pdf_put(pdf_buffer *buf, const char *text, int len)
1489 {
1490   if (len <= 0)
1491     return;
1492   if (buf->alloc_size < buf->current_size + len) {
1493     int new_size = buf->alloc_size;
1494     char *new_buf;
1495     while (new_size < buf->current_size + len)
1496       new_size *= 4;
1497     new_buf = (char*)malloc(new_size);
1498     if (new_buf == NULL) {
1499       rrd_set_error("re-malloc for pdf_buffer data");
1500       buf->state->has_failed = 1;
1501       return;
1502     }
1503     memcpy(new_buf, buf->data, buf->current_size);
1504     free(buf->data);
1505     buf->data = new_buf;
1506     buf->alloc_size = new_size;
1507   }
1508   memcpy(buf->data + buf->current_size, text, len);
1509   buf->current_size += len;
1510 }
1511
1512 static void pdf_puts(pdf_buffer *buf, const char *text)
1513 {
1514   pdf_put(buf, text, strlen(text));
1515 }
1516
1517 static void pdf_indent(pdf_buffer *buf)
1518 {
1519   pdf_puts(buf, "\t");
1520 }
1521
1522 static void pdf_putsi(pdf_buffer *buf, const char *text)
1523 {
1524   pdf_indent(buf);
1525   pdf_puts(buf, text);
1526 }
1527
1528 static void pdf_putint(pdf_buffer *buf, int i)
1529 {
1530   char tmp[20];
1531   sprintf(tmp, "%d", i);
1532   pdf_puts(buf, tmp);
1533 }
1534
1535 static void pdf_putnumber(pdf_buffer *buf, double d)
1536 {
1537   char tmp[50];
1538   svg_format_number(tmp, sizeof(tmp), d);
1539   pdf_puts(buf, tmp);
1540 }
1541
1542 static void pdf_init_object(pdf_state *state, pdf_buffer *buf)
1543 {
1544   pdf_init_buffer(state, buf);
1545   buf->id = ++state->last_obj_id;
1546   buf->is_obj = 1;
1547   buf->is_stream = 0;
1548 }
1549
1550 static void pdf_init_dict(pdf_state *state, pdf_buffer *buf)
1551 {
1552   pdf_init_object(state, buf);
1553   buf->is_dict = 1;
1554 }
1555
1556 static void pdf_set_color(pdf_buffer *buf, gfx_color_t color,
1557         gfx_color_t *current_color, const char *op)
1558 {
1559    /* gfx_color_t is RRGGBBAA */
1560   if (*current_color == color)
1561     return;
1562   pdf_putnumber(buf, ((color >> 24) & 255) / 255.0);
1563   pdf_puts(buf, " ");
1564   pdf_putnumber(buf, ((color >> 16) & 255) / 255.0);
1565   pdf_puts(buf, " ");
1566   pdf_putnumber(buf, ((color >>  8) & 255) / 255.0);
1567   pdf_puts(buf, " ");
1568   pdf_puts(buf, op);
1569   pdf_puts(buf, "\n");
1570   *current_color = color;
1571 }
1572
1573 static void pdf_set_stroke_color(pdf_buffer *buf, gfx_color_t color)
1574 {
1575     pdf_set_color(buf, color, &buf->state->stroke_color, "RG");
1576 }
1577
1578 static void pdf_set_fill_color(pdf_buffer *buf, gfx_color_t color)
1579 {
1580     pdf_set_color(buf, color, &buf->state->fill_color, "rg");
1581 }
1582
1583 static pdf_font *pdf_find_font(pdf_state *state, gfx_node_t *node)
1584 {
1585   const char *ps_font = afm_get_font_postscript_name(node->filename);
1586   pdf_font *ef;
1587   for (ef = state->font_list; ef; ef = ef->next) {
1588     if (!strcmp(ps_font, ef->ps_font))
1589       return ef;
1590   }
1591   return NULL;
1592 }
1593
1594 static void pdf_add_font(pdf_state *state, gfx_node_t *node)
1595 {
1596   pdf_font *ef = pdf_find_font(state, node);
1597   if (ef)
1598     return;
1599   ef = malloc(sizeof(pdf_font));
1600   if (ef == NULL) {
1601     rrd_set_error("malloc for pdf_font");
1602     state->has_failed = 1;
1603     return;
1604   }
1605   pdf_init_dict(state, &ef->obj);
1606   ef->next = state->font_list;
1607   ef->ps_font = afm_get_font_postscript_name(node->filename);
1608   state->font_list = ef;
1609   /* fonts dict */
1610   pdf_putsi(&state->fontsdict_obj, "/F");
1611   pdf_putint(&state->fontsdict_obj, ef->obj.id);
1612   pdf_puts(&state->fontsdict_obj, " ");
1613   pdf_putint(&state->fontsdict_obj, ef->obj.id);
1614   pdf_puts(&state->fontsdict_obj, " 0 R\n");
1615   /* fonts def */
1616   pdf_putsi(&ef->obj, "/Type /Font\n");
1617   pdf_putsi(&ef->obj, "/Subtype /Type1\n");
1618   pdf_putsi(&ef->obj, "/Name /F");
1619   pdf_putint(&ef->obj, ef->obj.id);
1620   pdf_puts(&ef->obj, "\n");
1621   pdf_putsi(&ef->obj, "/BaseFont /");
1622   pdf_puts(&ef->obj, ef->ps_font);
1623   pdf_puts(&ef->obj, "\n");
1624   pdf_putsi(&ef->obj, "/Encoding /WinAnsiEncoding\n");
1625   /*  'Cp1252' (this is latin 1 extended with 27 characters;
1626       the encoding is also known as 'winansi')
1627       http://www.lowagie.com/iText/tutorial/ch09.html */
1628 }
1629
1630 static void pdf_create_fonts(pdf_state *state)
1631 {
1632   gfx_node_t *node;
1633   for (node = state->canvas->firstnode; node; node = node->next) {
1634     if (node->type == GFX_TEXT)
1635       pdf_add_font(state, node);
1636   }
1637 }
1638
1639 static void pdf_write_linearea(pdf_state *state, gfx_node_t *node)
1640 {
1641   int i;
1642   pdf_buffer *s = &state->graph_stream;
1643   if (node->type == GFX_LINE) {
1644     svg_dash dash_info;
1645     svg_get_dash(node, &dash_info);
1646     if (!svg_dash_equal(&dash_info, &state->dash)) {
1647       state->dash = dash_info;
1648       if (dash_info.dash_enable) {
1649         pdf_puts(s, "[");
1650         pdf_putnumber(s, dash_info.adjusted_on);
1651         pdf_puts(s, " ");
1652         pdf_putnumber(s, dash_info.adjusted_off);
1653         pdf_puts(s, "] ");
1654         pdf_putnumber(s, dash_info.dash_offset);
1655         pdf_puts(s, " d\n");
1656       } else {
1657         pdf_puts(s, "[] 0 d\n");
1658       }
1659     }
1660     pdf_set_stroke_color(s, node->color);
1661     if (state->linecap != 1) {
1662       pdf_puts(s, "1 j\n");
1663       state->linecap = 1;
1664     }
1665     if (state->linejoin != 1) {
1666       pdf_puts(s, "1 J\n");
1667       state->linejoin = 1;
1668     }
1669     if (node->size != state->line_width) {
1670       state->line_width = node->size;
1671       pdf_putnumber(s, state->line_width);
1672       pdf_puts(s, " w\n");
1673     }
1674   } else {
1675     pdf_set_fill_color(s, node->color);
1676   }
1677   for (i = 0; i < node->points; i++) {
1678     ArtVpath *vec = node->path + i;
1679     double x = vec->x;
1680     double y = state->page_height - vec->y;
1681     if (node->type == GFX_AREA) {
1682       x += LINEOFFSET; /* adjust for libart handling of areas */
1683       y -= LINEOFFSET;
1684     }
1685     switch (vec->code) {
1686     case ART_MOVETO_OPEN: /* fall-through */
1687     case ART_MOVETO:
1688       pdf_putnumber(s, x);
1689       pdf_puts(s, " ");
1690       pdf_putnumber(s, y);
1691       pdf_puts(s, " m\n");
1692       break;
1693     case ART_LINETO:
1694       pdf_putnumber(s, x);
1695       pdf_puts(s, " ");
1696       pdf_putnumber(s, y);
1697       pdf_puts(s, " l\n");
1698       break;
1699     case ART_CURVETO: break; /* unsupported */
1700     case ART_END: break; /* nop */
1701     }
1702   }
1703   if (node->type == GFX_LINE) {
1704     pdf_puts(s, node->closed_path ? "s\n" : "S\n");
1705    } else {
1706     pdf_puts(s, "f\n");
1707    }
1708 }
1709
1710 static void pdf_write_text(pdf_state *state, gfx_node_t *node, 
1711     int last_was_text, int next_is_text)
1712 {
1713   char tmp[30];
1714   pdf_buffer *s = &state->graph_stream;
1715   const unsigned char *p;
1716   pdf_font *font = pdf_find_font(state, node);
1717   double x = node->x;
1718   double y = state->page_height - node->y;
1719   double dx = 0, dy = 0;
1720   double cos_a = 0, sin_a = 0;
1721   if (font == NULL) {
1722     rrd_set_error("font disappeared");
1723     state->has_failed = 1;
1724     return;
1725   }
1726   switch(node->valign){
1727   case GFX_V_TOP:    dy = -node->size; break;
1728   case GFX_V_CENTER: dy = -node->size / 3.0; break;          
1729   case GFX_V_BOTTOM: break;          
1730   case GFX_V_NULL: break;          
1731   }
1732   switch (node->halign) {
1733   case GFX_H_RIGHT:  dx = -afm_get_text_width(0, font->ps_font, 
1734                          node->size, node->tabwidth, node->text);
1735                      break;
1736   case GFX_H_CENTER: dx = -afm_get_text_width(0, font->ps_font, 
1737                          node->size, node->tabwidth, node->text) / 2;
1738                      break;
1739   case GFX_H_LEFT: break;
1740   case GFX_H_NULL: break;
1741   }
1742   pdf_set_fill_color(s, node->color);
1743   if (node->angle != 0) {
1744     double a = 2 * M_PI * -node->angle / 360.0;
1745     double new_x, new_y;
1746     cos_a = cos(a);
1747     sin_a = sin(a);
1748     new_x = cos_a * dx - sin_a * dy + x;
1749     new_y = sin_a * dx + cos_a * dy + y;
1750     x = new_x;
1751     y = new_y;
1752   } else {
1753     x += dx;
1754     y += dy;
1755   }
1756   if (!last_was_text)
1757     pdf_puts(s, "BT\n");
1758   if (state->font_id != font->obj.id || node->size != state->font_size) {
1759     state->font_id = font->obj.id;
1760     state->font_size = node->size;
1761     pdf_puts(s, "/F");
1762     pdf_putint(s, font->obj.id);
1763     pdf_puts(s, " ");
1764     pdf_putnumber(s, node->size);
1765     pdf_puts(s, " Tf\n");
1766   }
1767   if (node->angle == 0) {
1768     pdf_puts(s, "1 0 0 1 ");
1769   } else {
1770     pdf_putnumber(s, cos_a);
1771     pdf_puts(s, " ");
1772     pdf_putnumber(s, sin_a);
1773     pdf_puts(s, " ");
1774     pdf_putnumber(s, -sin_a);
1775     pdf_puts(s, " ");
1776     pdf_putnumber(s, cos_a);
1777     pdf_puts(s, " ");
1778   }
1779   pdf_putnumber(s, x);
1780   pdf_puts(s, " ");
1781   pdf_putnumber(s, y);
1782   pdf_puts(s, " Tm\n");
1783   pdf_puts(s, "(");
1784   for (p = (const unsigned char*)node->text; *p; p++) {
1785     switch (*p) {
1786       case '(':
1787       case ')':
1788       case '\\':
1789       case '\n':
1790       case '\r':
1791       case '\t':
1792         pdf_puts(s, "\\");
1793         /* fall-through */
1794       default:
1795         if (*p >= 126) {
1796           snprintf(tmp, sizeof(tmp), "\\%03o", *p);
1797           pdf_puts(s, tmp);
1798         } else {
1799           pdf_put(s, (const char*)p, 1);
1800         }
1801     }
1802   }
1803   pdf_puts(s, ") Tj\n");
1804   if (!next_is_text)
1805     pdf_puts(s, "ET\n");
1806 }
1807  
1808 static void pdf_write_content(pdf_state *state)
1809 {
1810   gfx_node_t *node;
1811   int last_was_text = 0, next_is_text;
1812   for (node = state->canvas->firstnode; node; node = node->next) {
1813     switch (node->type) {
1814     case GFX_LINE:
1815     case GFX_AREA:
1816       pdf_write_linearea(state, node);
1817       break;
1818     case GFX_TEXT:
1819       next_is_text = node->next && node->next->type == GFX_TEXT;
1820       pdf_write_text(state, node, last_was_text, next_is_text);
1821       break;
1822     }
1823     last_was_text = node->type == GFX_TEXT;
1824   }
1825 }
1826
1827 static void pdf_init_document(pdf_state *state)
1828 {
1829   pdf_init_buffer(state, &state->pdf_header);
1830   pdf_init_dict(state, &state->catalog_obj);
1831   pdf_init_dict(state, &state->pages_obj);
1832   pdf_init_dict(state, &state->page1_obj);
1833   pdf_init_dict(state, &state->fontsdict_obj);
1834   pdf_create_fonts(state);
1835   if (state->has_failed)
1836     return;
1837   /* make stream last object in file */
1838   pdf_init_object(state, &state->graph_stream);
1839   state->graph_stream.is_stream = 1;
1840 }
1841
1842 static void pdf_setup_document(pdf_state *state)
1843 {
1844   /* all objects created by now, so init code can reference them */
1845   /* HEADER */
1846   pdf_puts(&state->pdf_header, "%PDF-1.3\n");
1847   /* following 8 bit comment is recommended by Adobe for
1848      indicating binary file to file transfer applications */
1849   pdf_puts(&state->pdf_header, "%\xE2\xE3\xCF\xD3\n");
1850   /* CATALOG */
1851   pdf_putsi(&state->catalog_obj, "/Type /Catalog\n");
1852   pdf_putsi(&state->catalog_obj, "/Pages ");
1853   pdf_putint(&state->catalog_obj, state->pages_obj.id);
1854   pdf_puts(&state->catalog_obj, " 0 R\n");
1855   /* PAGES */
1856   pdf_putsi(&state->pages_obj, "/Type /Pages\n");
1857   pdf_putsi(&state->pages_obj, "/Kids [");
1858   pdf_putint(&state->pages_obj, state->page1_obj.id);
1859   pdf_puts(&state->pages_obj, " 0 R]\n");
1860   pdf_putsi(&state->pages_obj, "/Count 1\n");
1861   /* PAGE 1 */
1862   pdf_putsi(&state->page1_obj, "/Type /Page\n");
1863   pdf_putsi(&state->page1_obj, "/Parent ");
1864   pdf_putint(&state->page1_obj, state->pages_obj.id);
1865   pdf_puts(&state->page1_obj, " 0 R\n");
1866   pdf_putsi(&state->page1_obj, "/MediaBox [0 0 ");
1867   pdf_putint(&state->page1_obj, state->page_width);
1868   pdf_puts(&state->page1_obj, " ");
1869   pdf_putint(&state->page1_obj, state->page_height);
1870   pdf_puts(&state->page1_obj, "]\n");
1871   pdf_putsi(&state->page1_obj, "/Contents ");
1872   pdf_putint(&state->page1_obj, state->graph_stream.id);
1873   pdf_puts(&state->page1_obj, " 0 R\n");
1874   pdf_putsi(&state->page1_obj, "/Resources << /Font ");
1875   pdf_putint(&state->page1_obj, state->fontsdict_obj.id);
1876   pdf_puts(&state->page1_obj, " 0 R >>\n");
1877 }
1878
1879 static void pdf_write_string_to_file(pdf_state *state, const char *text)
1880 {
1881     fputs(text, state->fp);
1882     state->pdf_file_pos += strlen(text);
1883 }
1884
1885 static void pdf_write_buf_to_file(pdf_state *state, pdf_buffer *buf)
1886 {
1887   char tmp[40];
1888   buf->pdf_file_pos = state->pdf_file_pos;
1889   if (buf->is_obj) {
1890     snprintf(tmp, sizeof(tmp), "%d 0 obj\n", buf->id);
1891     pdf_write_string_to_file(state, tmp);
1892   }
1893   if (buf->is_dict)
1894     pdf_write_string_to_file(state, "<<\n");
1895   if (buf->is_stream) {
1896     snprintf(tmp, sizeof(tmp), "<< /Length %d >>\n", buf->current_size);
1897     pdf_write_string_to_file(state, tmp);
1898     pdf_write_string_to_file(state, "stream\n");
1899   }
1900   fwrite(buf->data, 1, buf->current_size, state->fp);
1901   state->pdf_file_pos += buf->current_size;
1902   if (buf->is_stream)
1903     pdf_write_string_to_file(state, "endstream\n");
1904   if (buf->is_dict)
1905     pdf_write_string_to_file(state, ">>\n");
1906   if (buf->is_obj)
1907     pdf_write_string_to_file(state, "endobj\n");
1908 }
1909
1910 static void pdf_write_to_file(pdf_state *state)
1911 {
1912   pdf_buffer *buf = state->first_buffer;
1913   int xref_pos;
1914   state->pdf_file_pos = 0;
1915   pdf_write_buf_to_file(state, &state->pdf_header);
1916   while (buf) {
1917     if (buf->is_obj)
1918       pdf_write_buf_to_file(state, buf);
1919     buf = buf->next_buffer;
1920   }
1921   xref_pos = state->pdf_file_pos;
1922   fprintf(state->fp, "xref\n");
1923   fprintf(state->fp, "%d %d\n", 0, state->last_obj_id + 1);
1924   /* TOC lines must be exactly 20 bytes including \n */
1925   fprintf(state->fp, "%010d %05d f\x20\n", 0, 65535);
1926   for (buf = state->first_buffer; buf; buf = buf->next_buffer) {
1927     if (buf->is_obj)
1928       fprintf(state->fp, "%010d %05d n\x20\n", buf->pdf_file_pos, 0);
1929   }
1930   fprintf(state->fp, "trailer\n");
1931   fprintf(state->fp, "<<\n");
1932   fprintf(state->fp, "\t/Size %d\n", state->last_obj_id + 1);
1933   fprintf(state->fp, "\t/Root %d 0 R\n", state->catalog_obj.id);
1934   fprintf(state->fp, ">>\n");
1935   fprintf(state->fp, "startxref\n");
1936   fprintf(state->fp, "%d\n", xref_pos);
1937   fputs("%%EOF\n", state->fp);
1938 }
1939
1940 static void pdf_free_resources(pdf_state *state)
1941 {
1942   pdf_buffer *buf = state->first_buffer;
1943   while (buf) {
1944     free(buf->data);
1945     buf->data = NULL;
1946     buf->alloc_size = buf->current_size = 0;
1947     buf = buf->next_buffer;
1948   }
1949   while (state->font_list) {
1950     pdf_font *next = state->font_list->next;
1951     free(state->font_list);
1952     state->font_list = next;
1953   }
1954 }
1955
1956 int       gfx_render_pdf (gfx_canvas_t *canvas,
1957                  art_u32 width, art_u32 height,
1958                  gfx_color_t background, FILE *fp){
1959   struct pdf_state state;
1960   memset(&state, 0, sizeof(pdf_state));
1961   state.fp = fp;
1962   state.canvas = canvas;
1963   state.page_width = width;
1964   state.page_height = height;
1965   state.font_id = -1;
1966   state.font_size = -1;
1967   state.font_list = NULL;
1968   state.linecap = -1;
1969   state.linejoin = -1;
1970   pdf_init_document(&state);
1971   /*
1972   pdf_set_color(&state, background);
1973   fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
1974       height, width, height, width);
1975   */
1976   if (!state.has_failed)
1977     pdf_write_content(&state);
1978   if (!state.has_failed)
1979     pdf_setup_document(&state);
1980   if (!state.has_failed)
1981     pdf_write_to_file(&state);
1982   pdf_free_resources(&state);
1983   return state.has_failed ? -1 : 0;
1984 }
1985