Patch for supporting PDF as output format
[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 }
1548
1549 static void pdf_init_dict(pdf_state *state, pdf_buffer *buf)
1550 {
1551   pdf_init_object(state, buf);
1552   buf->is_dict = 1;
1553 }
1554
1555 static void pdf_set_color(pdf_buffer *buf, gfx_color_t color,
1556         gfx_color_t *current_color, const char *op)
1557 {
1558    /* gfx_color_t is RRGGBBAA */
1559   if (*current_color == color)
1560     return;
1561   pdf_putnumber(buf, ((color >> 24) & 255) / 255.0);
1562   pdf_puts(buf, " ");
1563   pdf_putnumber(buf, ((color >> 16) & 255) / 255.0);
1564   pdf_puts(buf, " ");
1565   pdf_putnumber(buf, ((color >>  8) & 255) / 255.0);
1566   pdf_puts(buf, " ");
1567   pdf_puts(buf, op);
1568   pdf_puts(buf, "\n");
1569   *current_color = color;
1570 }
1571
1572 static void pdf_set_stroke_color(pdf_buffer *buf, gfx_color_t color)
1573 {
1574     pdf_set_color(buf, color, &buf->state->stroke_color, "RG");
1575 }
1576
1577 static void pdf_set_fill_color(pdf_buffer *buf, gfx_color_t color)
1578 {
1579     pdf_set_color(buf, color, &buf->state->fill_color, "rg");
1580 }
1581
1582 static pdf_font *pdf_find_font(pdf_state *state, gfx_node_t *node)
1583 {
1584   const char *ps_font = afm_get_font_postscript_name(node->filename);
1585   pdf_font *ef;
1586   for (ef = state->font_list; ef; ef = ef->next) {
1587     if (!strcmp(ps_font, ef->ps_font))
1588       return ef;
1589   }
1590   return NULL;
1591 }
1592
1593 static void pdf_add_font(pdf_state *state, gfx_node_t *node)
1594 {
1595   pdf_font *ef = pdf_find_font(state, node);
1596   if (ef)
1597     return;
1598   ef = malloc(sizeof(pdf_font));
1599   if (ef == NULL) {
1600     rrd_set_error("malloc for pdf_font");
1601     state->has_failed = 1;
1602     return;
1603   }
1604   pdf_init_dict(state, &ef->obj);
1605   ef->next = state->font_list;
1606   ef->ps_font = afm_get_font_postscript_name(node->filename);
1607   state->font_list = ef;
1608   /* fonts dict */
1609   pdf_putsi(&state->fontsdict_obj, "/F");
1610   pdf_putint(&state->fontsdict_obj, ef->obj.id);
1611   pdf_puts(&state->fontsdict_obj, " ");
1612   pdf_putint(&state->fontsdict_obj, ef->obj.id);
1613   pdf_puts(&state->fontsdict_obj, " 0 R\n");
1614   /* fonts def */
1615   pdf_putsi(&ef->obj, "/Type /Font\n");
1616   pdf_putsi(&ef->obj, "/Subtype /Type1\n");
1617   pdf_putsi(&ef->obj, "/Name /F");
1618   pdf_putint(&ef->obj, ef->obj.id);
1619   pdf_puts(&ef->obj, "\n");
1620   pdf_putsi(&ef->obj, "/BaseFont /");
1621   pdf_puts(&ef->obj, ef->ps_font);
1622   pdf_puts(&ef->obj, "\n");
1623   pdf_putsi(&ef->obj, "/Encoding /WinAnsiEncoding\n");
1624   /*  'Cp1252' (this is latin 1 extended with 27 characters;
1625       the encoding is also known as 'winansi')
1626       http://www.lowagie.com/iText/tutorial/ch09.html */
1627 }
1628
1629 static void pdf_create_fonts(pdf_state *state)
1630 {
1631   gfx_node_t *node;
1632   for (node = state->canvas->firstnode; node; node = node->next) {
1633     if (node->type == GFX_TEXT)
1634       pdf_add_font(state, node);
1635   }
1636 }
1637
1638 static void pdf_write_linearea(pdf_state *state, gfx_node_t *node)
1639 {
1640   int i;
1641   pdf_buffer *s = &state->graph_stream;
1642   if (node->type == GFX_LINE) {
1643     svg_dash dash_info;
1644     svg_get_dash(node, &dash_info);
1645     if (!svg_dash_equal(&dash_info, &state->dash)) {
1646       state->dash = dash_info;
1647       if (dash_info.dash_enable) {
1648         pdf_puts(s, "[");
1649         pdf_putnumber(s, dash_info.adjusted_on);
1650         pdf_puts(s, " ");
1651         pdf_putnumber(s, dash_info.adjusted_off);
1652         pdf_puts(s, "] ");
1653         pdf_putnumber(s, dash_info.dash_offset);
1654         pdf_puts(s, " d\n");
1655       } else {
1656         pdf_puts(s, "[] 0 d\n");
1657       }
1658     }
1659     pdf_set_stroke_color(s, node->color);
1660     if (state->linecap != 1) {
1661       pdf_puts(s, "1 j\n");
1662       state->linecap = 1;
1663     }
1664     if (state->linejoin != 1) {
1665       pdf_puts(s, "1 J\n");
1666       state->linejoin = 1;
1667     }
1668     if (node->size != state->line_width) {
1669       state->line_width = node->size;
1670       pdf_putnumber(s, state->line_width);
1671       pdf_puts(s, " w\n");
1672     }
1673   } else {
1674     pdf_set_fill_color(s, node->color);
1675   }
1676   for (i = 0; i < node->points; i++) {
1677     ArtVpath *vec = node->path + i;
1678     double x = vec->x;
1679     double y = state->page_height - vec->y;
1680     if (node->type == GFX_AREA) {
1681       x += LINEOFFSET; /* adjust for libart handling of areas */
1682       y -= LINEOFFSET;
1683     }
1684     switch (vec->code) {
1685     case ART_MOVETO_OPEN: /* fall-through */
1686     case ART_MOVETO:
1687       pdf_putnumber(s, x);
1688       pdf_puts(s, " ");
1689       pdf_putnumber(s, y);
1690       pdf_puts(s, " m\n");
1691       break;
1692     case ART_LINETO:
1693       pdf_putnumber(s, x);
1694       pdf_puts(s, " ");
1695       pdf_putnumber(s, y);
1696       pdf_puts(s, " l\n");
1697       break;
1698     case ART_CURVETO: break; /* unsupported */
1699     case ART_END: break; /* nop */
1700     }
1701   }
1702   if (node->type == GFX_LINE) {
1703     pdf_puts(s, node->closed_path ? "s\n" : "S\n");
1704    } else {
1705     pdf_puts(s, "f\n");
1706    }
1707 }
1708
1709 static void pdf_write_text(pdf_state *state, gfx_node_t *node, 
1710     int last_was_text, int next_is_text)
1711 {
1712   char tmp[30];
1713   pdf_buffer *s = &state->graph_stream;
1714   const unsigned char *p;
1715   pdf_font *font = pdf_find_font(state, node);
1716   double x = node->x;
1717   double y = state->page_height - node->y;
1718   double dx = 0, dy = 0;
1719   double cos_a = 0, sin_a = 0;
1720   if (font == NULL) {
1721     rrd_set_error("font disappeared");
1722     state->has_failed = 1;
1723     return;
1724   }
1725   switch(node->valign){
1726   case GFX_V_TOP:    dy = -node->size; break;
1727   case GFX_V_CENTER: dy = -node->size / 3.0; break;          
1728   case GFX_V_BOTTOM: break;          
1729   case GFX_V_NULL: break;          
1730   }
1731   switch (node->halign) {
1732   case GFX_H_RIGHT:  dx = -afm_get_text_width(0, font->ps_font, 
1733                          node->size, node->tabwidth, node->text);
1734                      break;
1735   case GFX_H_CENTER: dx = -afm_get_text_width(0, font->ps_font, 
1736                          node->size, node->tabwidth, node->text) / 2;
1737                      break;
1738   case GFX_H_LEFT: break;
1739   case GFX_H_NULL: break;
1740   }
1741   pdf_set_fill_color(s, node->color);
1742   if (node->angle != 0) {
1743     double a = 2 * M_PI * -node->angle / 360.0;
1744     double new_x, new_y;
1745     cos_a = cos(a);
1746     sin_a = sin(a);
1747     new_x = cos_a * dx - sin_a * dy + x;
1748     new_y = sin_a * dx + cos_a * dy + y;
1749     x = new_x;
1750     y = new_y;
1751   } else {
1752     x += dx;
1753     y += dy;
1754   }
1755   if (!last_was_text)
1756     pdf_puts(s, "BT\n");
1757   if (state->font_id != font->obj.id || node->size != state->font_size) {
1758     state->font_id = font->obj.id;
1759     state->font_size = node->size;
1760     pdf_puts(s, "/F");
1761     pdf_putint(s, font->obj.id);
1762     pdf_puts(s, " ");
1763     pdf_putnumber(s, node->size);
1764     pdf_puts(s, " Tf\n");
1765   }
1766   if (node->angle == 0) {
1767     pdf_puts(s, "1 0 0 1 ");
1768   } else {
1769     pdf_putnumber(s, cos_a);
1770     pdf_puts(s, " ");
1771     pdf_putnumber(s, sin_a);
1772     pdf_puts(s, " ");
1773     pdf_putnumber(s, -sin_a);
1774     pdf_puts(s, " ");
1775     pdf_putnumber(s, cos_a);
1776     pdf_puts(s, " ");
1777   }
1778   pdf_putnumber(s, x);
1779   pdf_puts(s, " ");
1780   pdf_putnumber(s, y);
1781   pdf_puts(s, " Tm\n");
1782   pdf_puts(s, "(");
1783   for (p = (const unsigned char*)node->text; *p; p++) {
1784     switch (*p) {
1785       case '(':
1786       case ')':
1787       case '\\':
1788       case '\n':
1789       case '\r':
1790       case '\t':
1791         pdf_puts(s, "\\");
1792         /* fall-through */
1793       default:
1794         if (*p >= 126) {
1795           snprintf(tmp, sizeof(tmp), "\\%03o", *p);
1796           pdf_puts(s, tmp);
1797         } else {
1798           pdf_put(s, (const char*)p, 1);
1799         }
1800     }
1801   }
1802   pdf_puts(s, ") Tj\n");
1803   if (!next_is_text)
1804     pdf_puts(s, "ET\n");
1805 }
1806  
1807 static void pdf_write_content(pdf_state *state)
1808 {
1809   gfx_node_t *node;
1810   int last_was_text = 0, next_is_text;
1811   for (node = state->canvas->firstnode; node; node = node->next) {
1812     switch (node->type) {
1813     case GFX_LINE:
1814     case GFX_AREA:
1815       pdf_write_linearea(state, node);
1816       break;
1817     case GFX_TEXT:
1818       next_is_text = node->next && node->next->type == GFX_TEXT;
1819       pdf_write_text(state, node, last_was_text, next_is_text);
1820       break;
1821     }
1822     last_was_text = node->type == GFX_TEXT;
1823   }
1824 }
1825
1826 static void pdf_init_document(pdf_state *state)
1827 {
1828   pdf_init_buffer(state, &state->pdf_header);
1829   pdf_init_dict(state, &state->catalog_obj);
1830   pdf_init_dict(state, &state->pages_obj);
1831   pdf_init_dict(state, &state->page1_obj);
1832   pdf_init_dict(state, &state->fontsdict_obj);
1833   state->graph_stream.is_stream = 1;
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 }
1840
1841 static void pdf_setup_document(pdf_state *state)
1842 {
1843   /* all objects created by now, so init code can reference them */
1844   /* HEADER */
1845   pdf_puts(&state->pdf_header, "%PDF-1.3\n");
1846   /* following 8 bit comment is recommended by Adobe for
1847      indicating binary file to file transfer applications */
1848   pdf_puts(&state->pdf_header, "%\xE2\xE3\xCF\xD3\n");
1849   /* CATALOG */
1850   pdf_putsi(&state->catalog_obj, "/Type /Catalog\n");
1851   pdf_putsi(&state->catalog_obj, "/Pages ");
1852   pdf_putint(&state->catalog_obj, state->pages_obj.id);
1853   pdf_puts(&state->catalog_obj, " 0 R\n");
1854   /* PAGES */
1855   pdf_putsi(&state->pages_obj, "/Type /Pages\n");
1856   pdf_putsi(&state->pages_obj, "/Kids [");
1857   pdf_putint(&state->pages_obj, state->page1_obj.id);
1858   pdf_puts(&state->pages_obj, " 0 R]\n");
1859   pdf_putsi(&state->pages_obj, "/Count 1\n");
1860   /* PAGE 1 */
1861   pdf_putsi(&state->page1_obj, "/Type /Page\n");
1862   pdf_putsi(&state->page1_obj, "/Parent ");
1863   pdf_putint(&state->page1_obj, state->pages_obj.id);
1864   pdf_puts(&state->page1_obj, " 0 R\n");
1865   pdf_putsi(&state->page1_obj, "/MediaBox [0 0 ");
1866   pdf_putint(&state->page1_obj, state->page_width);
1867   pdf_puts(&state->page1_obj, " ");
1868   pdf_putint(&state->page1_obj, state->page_height);
1869   pdf_puts(&state->page1_obj, "]\n");
1870   pdf_putsi(&state->page1_obj, "/Contents ");
1871   pdf_putint(&state->page1_obj, state->graph_stream.id);
1872   pdf_puts(&state->page1_obj, " 0 R\n");
1873   pdf_putsi(&state->page1_obj, "/Resources << /Font ");
1874   pdf_putint(&state->page1_obj, state->fontsdict_obj.id);
1875   pdf_puts(&state->page1_obj, " 0 R >>\n");
1876 }
1877
1878 static void pdf_write_string_to_file(pdf_state *state, const char *text)
1879 {
1880     fputs(text, state->fp);
1881     state->pdf_file_pos += strlen(text);
1882 }
1883
1884 static void pdf_write_buf_to_file(pdf_state *state, pdf_buffer *buf)
1885 {
1886   char tmp[40];
1887   buf->pdf_file_pos = state->pdf_file_pos;
1888   if (buf->is_obj) {
1889     snprintf(tmp, sizeof(tmp), "%d 0 obj\n", buf->id);
1890     pdf_write_string_to_file(state, tmp);
1891   }
1892   if (buf->is_dict)
1893     pdf_write_string_to_file(state, "<<\n");
1894   if (buf->is_stream) {
1895     snprintf(tmp, sizeof(tmp), "<< /Length %d >>\n", buf->current_size);
1896     pdf_write_string_to_file(state, tmp);
1897     pdf_write_string_to_file(state, "stream\n");
1898   }
1899   fwrite(buf->data, 1, buf->current_size, state->fp);
1900   state->pdf_file_pos += buf->current_size;
1901   if (buf->is_stream)
1902     pdf_write_string_to_file(state, "endstream\n");
1903   if (buf->is_dict)
1904     pdf_write_string_to_file(state, ">>\n");
1905   if (buf->is_obj)
1906     pdf_write_string_to_file(state, "endobj\n");
1907 }
1908
1909 static void pdf_write_to_file(pdf_state *state)
1910 {
1911   pdf_buffer *buf = state->first_buffer;
1912   int xref_pos;
1913   state->pdf_file_pos = 0;
1914   pdf_write_buf_to_file(state, &state->pdf_header);
1915   while (buf) {
1916     if (buf->is_obj)
1917       pdf_write_buf_to_file(state, buf);
1918     buf = buf->next_buffer;
1919   }
1920   xref_pos = state->pdf_file_pos;
1921   fprintf(state->fp, "xref\n");
1922   fprintf(state->fp, "%d %d\n", 0, state->last_obj_id + 1);
1923   /* TOC lines must be exactly 20 bytes including \n */
1924   fprintf(state->fp, "%010d %05d f\x20\n", 0, 65535);
1925   for (buf = state->first_buffer; buf; buf = buf->next_buffer) {
1926     if (buf->is_obj)
1927       fprintf(state->fp, "%010d %05d n\x20\n", buf->pdf_file_pos, 0);
1928   }
1929   fprintf(state->fp, "trailer\n");
1930   fprintf(state->fp, "<<\n");
1931   fprintf(state->fp, "\t/Size %d\n", state->last_obj_id + 1);
1932   fprintf(state->fp, "\t/Root %d 0 R\n", state->catalog_obj.id);
1933   fprintf(state->fp, ">>\n");
1934   fprintf(state->fp, "startxref\n");
1935   fprintf(state->fp, "%d\n", xref_pos);
1936   fputs("%%EOF\n", state->fp);
1937 }
1938
1939 static void pdf_free_resources(pdf_state *state)
1940 {
1941   pdf_buffer *buf = state->first_buffer;
1942   while (buf) {
1943     free(buf->data);
1944     buf->data = NULL;
1945     buf->alloc_size = buf->current_size = 0;
1946     buf = buf->next_buffer;
1947   }
1948   while (state->font_list) {
1949     pdf_font *next = state->font_list->next;
1950     free(state->font_list);
1951     state->font_list = next;
1952   }
1953 }
1954
1955 int       gfx_render_pdf (gfx_canvas_t *canvas,
1956                  art_u32 width, art_u32 height,
1957                  gfx_color_t background, FILE *fp){
1958   struct pdf_state state;
1959   memset(&state, 0, sizeof(pdf_state));
1960   state.fp = fp;
1961   state.canvas = canvas;
1962   state.page_width = width;
1963   state.page_height = height;
1964   state.font_id = -1;
1965   state.font_size = -1;
1966   state.font_list = NULL;
1967   state.linecap = -1;
1968   state.linejoin = -1;
1969   pdf_init_document(&state);
1970   /*
1971   pdf_set_color(&state, background);
1972   fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
1973       height, width, height, width);
1974   */
1975   if (!state.has_failed)
1976     pdf_write_content(&state);
1977   if (!state.has_failed)
1978     pdf_setup_document(&state);
1979   if (!state.has_failed)
1980     pdf_write_to_file(&state);
1981   pdf_free_resources(&state);
1982   return state.has_failed ? -1 : 0;
1983 }
1984