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