1 /****************************************************************************
2 * RRDtool 1.2.23 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd_gfx.c graphics wrapper for rrdtool
5 **************************************************************************/
9 /* stupid MSVC doesnt support variadic macros = no debug for now! */
14 # define RRDPRINTF(...) fprintf(stderr, __VA_ARGS__);
16 # define RRDPRINTF(...)
22 #include FT_FREETYPE_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;
37 FT_UInt index; /* glyph index */
38 FT_Vector pos; /* location from baseline in 26.6 */
39 FT_Glyph image; /* glyph bitmap */
42 typedef struct gfx_string_s *gfx_string;
46 int count; /* number of characters */
53 /* compute string bbox */
54 static void compute_string_bbox(
57 /* create a freetype glyph string */
58 gfx_string gfx_string_create(
66 /* create a freetype glyph string */
67 static void gfx_string_destroy(
71 gfx_node_t *gfx_new_node(
75 gfx_node_t *node = art_new(gfx_node_t, 1);
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 */
85 node->closed_path = 0;
86 node->filename = NULL; /* font or image filename */
89 node->y = 0.0; /* position */
91 node->halign = GFX_H_NULL; /* text alignement */
92 node->valign = GFX_V_NULL; /* text alignement */
95 if (canvas->lastnode != NULL) {
96 canvas->lastnode->next = node;
98 if (canvas->firstnode == NULL) {
99 canvas->firstnode = node;
101 canvas->lastnode = node;
105 gfx_canvas_t *gfx_new_canvas(
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;
115 canvas->font_aa_threshold = -1.0;
116 canvas->aa_type = AA_NORMAL;
120 /* create a new line */
121 gfx_node_t *gfx_new_line(
122 gfx_canvas_t *canvas,
130 return gfx_new_dashed_line(canvas, X0, Y0, X1, Y1, width, color, 0, 0);
133 gfx_node_t *gfx_new_dashed_line(
134 gfx_canvas_t *canvas,
148 node = gfx_new_node(canvas, GFX_LINE);
151 vec = art_new(ArtVpath, 3);
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;
165 node->points_max = 3;
168 node->dash_on = dash_on;
169 node->dash_off = dash_off;
174 /* create a new area */
175 gfx_node_t *gfx_new_area(
176 gfx_canvas_t *canvas,
189 node = gfx_new_node(canvas, GFX_AREA);
192 vec = art_new(ArtVpath, 5);
195 vec[0].code = ART_MOVETO;
198 vec[1].code = ART_LINETO;
201 vec[2].code = ART_LINETO;
204 vec[3].code = ART_LINETO;
207 vec[4].code = ART_END;
212 node->points_max = 5;
219 /* add a point to a line or to an area */
227 if (node->type == GFX_AREA) {
228 double X0 = node->path[0].x;
229 double Y0 = node->path[0].y;
232 art_vpath_add_point(&(node->path),
234 &(node->points_max), ART_LINETO, x, y);
235 art_vpath_add_point(&(node->path),
237 &(node->points_max), ART_LINETO, X0, Y0);
238 art_vpath_add_point(&(node->path),
240 &(node->points_max), ART_END, 0, 0);
241 } else if (node->type == GFX_LINE) {
243 art_vpath_add_point(&(node->path),
246 ART_LINETO, x + LINEOFFSET, y + LINEOFFSET);
247 art_vpath_add_point(&(node->path),
249 &(node->points_max), ART_END, 0, 0);
252 /* can only add point to areas and lines */
261 node->closed_path = 1;
262 if (node->path[0].code == ART_MOVETO_OPEN)
263 node->path[0].code = ART_MOVETO;
266 /* create a text node */
267 gfx_node_t *gfx_new_text(
268 gfx_canvas_t *canvas,
276 enum gfx_h_align_en h_align,
277 enum gfx_v_align_en v_align,
280 gfx_node_t *node = gfx_new_node(canvas, GFX_TEXT);
282 node->text = strdup(text);
284 node->filename = strdup(font);
289 node->tabwidth = tabwidth;
290 node->halign = h_align;
291 node->valign = v_align;
293 /* debugging: show text anchor
294 green is along x-axis, red is downward y-axis */
296 double a = 2 * M_PI * -node->angle / 360.0;
297 double cos_a = cos(a);
298 double sin_a = sin(a);
302 x, y, x + len * cos_a, y - len * sin_a, 0.2, 0x00FF0000);
304 x, y, x + len * sin_a, y + len * cos_a, 0.2, 0xFF000000);
311 gfx_canvas_t *canvas,
314 gfx_color_t background,
317 switch (canvas->imgformat) {
319 return gfx_render_png(canvas, width, height, background, fp);
321 return gfx_render_svg(canvas, width, height, background, fp);
323 return gfx_render_eps(canvas, width, height, background, fp);
325 return gfx_render_pdf(canvas, width, height, background, fp);
331 static void gfx_string_destroy(
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);
345 double gfx_get_text_width(
346 gfx_canvas_t *canvas,
354 switch (canvas->imgformat) {
356 return gfx_get_text_width_libart(canvas, start, font, size, tabwidth,
358 case IF_SVG: /* fall through */
361 return afm_get_text_width(start, font, size, tabwidth, text);
363 return size * strlen(text);
367 double gfx_get_text_width_libart(
368 gfx_canvas_t *canvas,
369 double UNUSED(start),
378 double text_width = 0;
380 FT_Library library = NULL;
383 FT_Init_FreeType(&library);
384 error = FT_New_Face(library, font, 0, &face);
386 FT_Done_FreeType(library);
389 error = FT_Set_Char_Size(face, size * 64, size * 64, 100, 100);
391 FT_Done_FreeType(library);
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;
401 static void gfx_libart_close_path(
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);
417 /* find bbox of a string */
418 static void compute_string_bbox(
424 bbox.xMin = bbox.yMin = 32000;
425 bbox.xMax = bbox.yMax = -32000;
426 for (n = 0; n < string->num_glyphs; n++) {
429 FT_Glyph_Get_CBox(string->glyphs[n].image, ft_glyph_bbox_gridfit,
431 if (glyph_bbox.xMin < bbox.xMin) {
432 bbox.xMin = glyph_bbox.xMin;
434 if (glyph_bbox.yMin < bbox.yMin) {
435 bbox.yMin = glyph_bbox.yMin;
437 if (glyph_bbox.xMax > bbox.xMax) {
438 bbox.xMax = glyph_bbox.xMax;
440 if (glyph_bbox.yMax > bbox.yMax) {
441 bbox.yMax = glyph_bbox.yMax;
444 if (bbox.xMin > bbox.xMax) {
450 string->bbox.xMin = bbox.xMin;
451 string->bbox.xMax = bbox.xMax;
452 string->bbox.yMin = bbox.yMin;
453 string->bbox.yMax = bbox.yMax;
456 /* create a free type glyph string */
457 gfx_string gfx_string_create(
458 gfx_canvas_t *canvas,
466 FT_GlyphSlot slot = face->glyph; /* a small shortcut */
471 gfx_string string = (gfx_string) malloc(sizeof(struct gfx_string_s));
473 gfx_char glyph; /* current glyph in table */
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];
492 char *cstr = strdup(text);
494 string->count = strlen(text);
497 ft_pen.x = 0; /* start at (0,0) !! */
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);
517 glyph = string->glyphs;
518 for (n = 0; n < string->count; glyph++, n++) {
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 */
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 */
536 if (letter == '\t') {
540 /* initialize each struct gfx_char_s */
545 glyph->index = FT_Get_Char_Index(face, letter);
547 /* compute glyph origin */
548 if (use_kerning && previous && glyph->index) {
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;
557 /* load the glyph image (in its native format) */
558 /* for now, we take a monochrome glyph bitmap */
560 FT_Load_Glyph(face, glyph->index,
562 canvas->font_aa_threshold ? canvas->aa_type ==
563 AA_NORMAL ? FT_LOAD_TARGET_NORMAL : canvas->
565 AA_LIGHT ? FT_LOAD_TARGET_LIGHT :
566 FT_LOAD_TARGET_MONO : FT_LOAD_TARGET_MONO);
568 RRDPRINTF("couldn't load glyph: %c\n", letter)
571 error = FT_Get_Glyph(slot, &glyph->image);
573 RRDPRINTF("couldn't get glyph %c from slot %d\n", letter,
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 */
580 /* we are in gridfitting mode so the calculations happen in 1/64 pixles */
582 tabwidth * 64.0 * (float) (1 +
584 (tabwidth * 64.0))) -
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;
597 FT_Vector_Transform(&vec, &string->transform);
598 error = FT_Glyph_Transform(glyph->image, &string->transform, &vec);
600 RRDPRINTF("couldn't transform glyph id %d\n", letter)
604 /* convert to a bitmap - destroy native image */
606 FT_Glyph_To_Bitmap(&glyph->image,
608 canvas->font_aa_threshold ? canvas->aa_type ==
609 AA_NORMAL ? FT_RENDER_MODE_NORMAL : canvas->
611 AA_LIGHT ? FT_RENDER_MODE_LIGHT :
612 FT_RENDER_MODE_MONO : FT_RENDER_MODE_MONO, 0,
615 RRDPRINTF("couldn't convert glyph id %d to bitmap\n", letter)
619 /* increment number of glyphs */
620 previous = glyph->index;
621 string->num_glyphs++;
624 /* printf ("number of glyphs = %d\n", string->num_glyphs);*/
625 compute_string_bbox(string);
626 /* the last character was a tab */
628 string->width = ft_pen.x;
630 string->width = string->bbox.xMax - string->bbox.xMin;
632 string->height = string->bbox.yMax - string->bbox.yMin;
637 static int gfx_save_png(
642 long bytes_per_pixel);
644 /* render grafics into png image */
647 gfx_canvas_t *canvas,
650 gfx_color_t background,
656 gfx_node_t *node = canvas->firstnode;
659 art_u8 red = background >> 24, green = (background >> 16) & 0xff;
660 art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
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;
672 for (i = 0; i < pys_width * pys_height; i++) {
673 *(buffp++) = background;
675 FT_Init_FreeType(&library);
677 switch (node->type) {
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);
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,
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 );
702 svp = art_svp_rewind_uncrossed(svpt,ART_WIND_RULE_NONZERO);
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,
717 art_u8 fcolor[4], falpha;
721 FT_Vector vec; /* 26.6 */
723 float pen_x = 0.0, pen_y = 0.0;
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);
734 rrd_set_error("failed to load %s", node->filename);
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));
747 pen_x = node->x * canvas->zoom;
748 pen_y = node->y * canvas->zoom;
751 gfx_string_create(canvas, face, node->text, node->angle,
752 node->tabwidth, node->size);
755 switch (node->halign) {
757 vec.x = -string->bbox.xMax;
760 vec.x = abs(string->bbox.xMax) >= abs(string->bbox.xMin) ?
761 -string->bbox.xMax / 2 : -string->bbox.xMin / 2;
764 vec.x = -string->bbox.xMin;
771 switch (node->valign) {
773 vec.y = string->bbox.yMax;
776 vec.y = abs(string->bbox.yMax) >= abs(string->bbox.yMin) ?
777 string->bbox.yMax / 2 : string->bbox.yMin / 2;
788 glyph = string->glyphs;
789 for (n = 0; n < string->num_glyphs; n++, glyph++) {
794 /* long buf_x,comp_n; */
795 /* make copy to transform */
797 RRDPRINTF("no image\n")
800 error = FT_Glyph_Copy(glyph->image, &image);
802 RRDPRINTF("couldn't copy image\n")
808 FT_Vector_Transform(&vec, &string->transform);
810 bit = (FT_BitmapGlyph) image;
811 gr = bit->bitmap.num_grays - 1;
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;
821 for (ix=0;ix < bit->bitmap.width;ix++){
822 *(letter + (ix*bytes_per_pixel+3)) = *(bit->bitmap.buffer + iy * bit->bitmap.width + ix);
824 art_rgba_rgba_composite(buffer + buf_y + buf_x ,letter,comp_n);
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)
836 for (ix = 0; ix < bit->bitmap.width; ix++) {
838 ix + (pen_x + 0.5) + (double) bit->left;
841 if (buf_x < 0 || buf_x >= (long) pys_width)
843 buf_x *= bytes_per_pixel;
845 *(bit->bitmap.buffer +
846 iy * bit->bitmap.pitch + ix);
847 if (font_alpha > 0) {
849 (art_u8) ((double) font_alpha / gr *
851 art_rgba_rgba_composite(buffer + buf_y +
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)
865 for (ix = 0; ix < bit->bitmap.width; ix++) {
867 ix + (pen_x + 0.5) + (double) bit->left;
869 if (buf_x < 0 || buf_x >= (long) pys_width)
871 buf_x *= bytes_per_pixel;
875 (bit->bitmap.buffer +
876 iy * bit->bitmap.pitch + ix / 8) >> (7 -
880 art_rgba_rgba_composite(buffer + buf_y +
887 rrd_set_error("unknown freetype pixel mode: %d",
888 bit->bitmap.pixel_mode);
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;
897 for (ix=0;ix < bit->bitmap.width;ix++){
898 long buf_x = ix + (pen_x + 0.5) + (double)bit->left ;
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));
913 FT_Done_Glyph(image);
915 gfx_string_destroy(string);
920 gfx_save_png(buffer, fp, pys_width, pys_height, bytes_per_pixel);
922 FT_Done_FreeType(library);
926 /* free memory used by nodes this will also remove memory required for
927 associated paths and svcs ... but not for text strings */
929 gfx_canvas_t *canvas)
931 gfx_node_t *next, *node = canvas->firstnode;
935 art_free(node->path);
937 free(node->filename);
945 static int gfx_save_png(
950 long bytes_per_pixel)
952 png_structp png_ptr = NULL;
953 png_infop info_ptr = NULL;
955 png_bytep *row_pointers;
956 int rowstride = width * bytes_per_pixel;
963 png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
964 if (png_ptr == NULL) {
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);
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);
984 png_init_io(png_ptr, fp);
985 png_set_IHDR(png_ptr, info_ptr, width, height,
986 8, PNG_COLOR_TYPE_RGB_ALPHA,
988 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
990 text[0].key = "Software";
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); */
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);
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 {
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 */
1042 static void pdf_dump_calc(
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);
1073 #define PDF_DD(x) if (g->debug) x;
1078 static void pdf_rotate(
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;
1086 (stderr, " rotate(%f, %f) -> %f, %f\n", p->x, p->y, x2, y2))
1092 static void pdf_calc(
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");
1105 g->y = page_height - node->y;
1107 g->r = 2 * M_PI * node->angle / 360.0;
1108 g->cos_r = cos(g->r);
1109 g->sin_r = sin(g->r);
1115 g->ascender = afm_get_ascender(node->filename, node->size);
1116 g->descender = afm_get_descender(node->filename, node->size);
1118 afm_get_text_width(0, node->filename, node->size, node->tabwidth,
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;
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.
1143 switch (node->halign) {
1145 g->tdx = -g->maxp.x;
1148 g->tdx = -(g->maxp.x + g->minp.x) / 2;
1151 g->tdx = -g->minp.x;
1157 switch (node->valign) {
1159 g->tdy = -g->maxp.y;
1162 g->tdy = -(g->maxp.y + g->minp.y) / 2;
1165 g->tdy = -g->minp.y;
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))
1182 /* ------- SVG -------
1184 http://www.w3.org/TR/SVG/
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 {
1191 double dash_adjust, dash_len, dash_offset;
1192 double adjusted_on, adjusted_off;
1196 static void svg_print_indent(
1201 for (i = svg_indent - svg_single_line; i > 0; i--) {
1207 static void svg_start_tag(
1211 svg_print_indent(fp);
1217 static void svg_close_tag_single_line(
1224 static void svg_close_tag(
1228 if (!svg_single_line)
1232 static void svg_end_tag(
1236 /* name is NULL if closing empty-node tag */
1238 if (svg_single_line)
1241 svg_print_indent(fp);
1251 static void svg_close_tag_empty_node(
1254 svg_end_tag(fp, NULL);
1257 static void svg_write_text(
1261 #ifdef HAVE_MBSTOWCS
1263 wchar_t *p, *cstr, ch;
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);
1275 unsigned char *p = text;
1276 unsigned char *cstr;
1284 ch = afm_fix_osx_charset(ch); /* unsafe macro */
1287 #ifdef HAVE_MBSTOWCS
1301 fputs(""", fp);
1305 #ifdef HAVE_MBSTOWCS
1306 if (p <= cstr + 1 || !*p || *p == 32)
1307 fputs(" ", fp); /* non-breaking space in unicode */
1311 } else if (ch < 32 || ch >= 127)
1312 fprintf(fp, "&#%d;", (int) ch);
1314 putc((char) ch, fp);
1319 static void svg_format_number(
1324 /* omit decimals if integer to reduce filesize */
1327 snprintf(buf, bufsize, "%.2f", d);
1328 p = buf; /* doesn't trust snprintf return value */
1335 *p = '\0'; /* zap trailing zeros */
1339 *p = '\0'; /* zap trailing dot */
1344 static void svg_write_number(
1350 svg_format_number(buf, sizeof(buf), d);
1354 static int svg_color_is_black(
1357 /* gfx_color_t is RRGGBBAA */
1358 return c == 0x000000FF;
1361 static void svg_write_color(
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)));
1377 fprintf(fp, "#%06lX", rrggbb);
1380 if (opacity != 0xFF) {
1381 fprintf(fp, " opacity=\"");
1382 svg_write_number(fp, opacity / 255.0);
1387 static void svg_get_dash(
1394 if (node->dash_on <= 0 || node->dash_off <= 0) {
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;
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;
1418 static int svg_dash_equal(
1422 if (a->dash_enable != b->dash_enable)
1424 if (a->adjusted_on != b->adjusted_on)
1426 if (a->adjusted_off != b->adjusted_off)
1428 /* rest of properties will be the same when on+off are */
1432 static void svg_common_path_attributes(
1438 svg_get_dash(node, &dash_info);
1439 fputs(" stroke-width=\"", fp);
1440 svg_write_number(fp, node->size);
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);
1450 fputs(" stroke-dasharray=\"", fp);
1451 svg_write_number(fp, dash_info.adjusted_on);
1453 svg_write_number(fp, dash_info.adjusted_off);
1458 static int svg_is_int_step(
1462 double diff = fabs(a - b);
1464 return floor(diff) == diff;
1467 static int svg_path_straight_segment(
1478 if (!svg_is_int_step(lastA, currentA)) {
1480 svg_write_number(fp, currentA);
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 */
1498 svg_write_number(fp, currentA - lastA);
1502 static void svg_path(
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");
1515 svg_common_path_attributes(fp, node);
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 */
1528 svg_write_number(fp, x);
1530 svg_write_number(fp, y);
1533 /* try optimize filesize by using minimal lineto commands */
1534 /* without introducing rounding errors. */
1536 if (svg_path_straight_segment
1537 (fp, lastY, y, x, node, i, 0, 'V', 'v'))
1539 } else if (y == lastY) {
1540 if (svg_path_straight_segment
1541 (fp, lastX, x, y, node, i, 1, 'H', 'h'))
1545 svg_write_number(fp, x);
1547 svg_write_number(fp, y);
1551 break; /* unsupported */
1558 if (node->closed_path)
1561 svg_close_tag_empty_node(fp);
1564 static void svg_multi_path(
1568 /* optimize for multiple paths with the same color, penwidth, etc. */
1570 gfx_node_t *node = *nodeR;
1571 gfx_node_t *next = node->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)
1584 svg_path(fp, node, 0);
1587 svg_start_tag(fp, "g");
1588 svg_common_path_attributes(fp, node);
1590 while (num && node) {
1591 svg_path(fp, node, 1);
1597 svg_end_tag(fp, "g");
1600 static void svg_area(
1605 double startX = 0, startY = 0;
1607 svg_start_tag(fp, "polygon");
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 */
1619 svg_write_number(fp, x);
1621 svg_write_number(fp, y);
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 */
1632 svg_write_number(fp, x);
1634 svg_write_number(fp, y);
1637 break; /* unsupported */
1643 svg_close_tag_empty_node(fp);
1646 static void svg_text(
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
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);
1667 svg_start_tag(fp, "text");
1670 svg_write_number(fp, g.tmx);
1671 fputs("\" y=\"", fp);
1672 svg_write_number(fp, page_height - g.tmy);
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);
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");
1692 gfx_canvas_t *canvas,
1695 gfx_color_t background,
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. */
1703 if (node->type == GFX_TEXT && node->filename) {
1704 svg_default_font = afm_get_font_name(node->filename);
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"
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",
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);
1730 svg_write_number(fp, -LINEOFFSET);
1732 svg_write_number(fp, width - LINEOFFSET);
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);
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;
1747 switch (node->type) {
1749 svg_multi_path(fp, &node);
1759 svg_end_tag(fp, "svg");
1763 /* ------- EPS -------
1764 EPS and Postscript references:
1765 http://partners.adobe.com/asn/developer/technotes/postscript.html
1768 typedef struct eps_font {
1769 const char *ps_font;
1771 struct eps_font *next;
1774 typedef struct eps_state {
1776 gfx_canvas_t *canvas;
1777 art_u32 page_width, page_height;
1778 eps_font *font_list;
1784 int linecap, linejoin;
1788 static void eps_set_color(
1792 #if USE_EPS_FAKE_ALPHA
1795 /* gfx_color_t is RRGGBBAA */
1796 if (state->color == color)
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)
1803 #define eps_color_calc(x) (int)( (x) & 255)
1805 /* gfx_color_t is RRGGBBAA */
1806 if (state->color == color)
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;
1814 static int eps_add_font(
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);
1823 for (ef = state->font_list; ef; ef = ef->next) {
1824 if (!strcmp(ps_font, ef->ps_font))
1827 ef = malloc(sizeof(eps_font));
1829 rrd_set_error("malloc for eps_font");
1832 ef->next = state->font_list;
1833 ef->ps_font = ps_font;
1834 state->font_list = ef;
1838 static void eps_list_fonts(
1840 const char *dscName)
1843 int lineLen = strlen(dscName);
1845 if (!state->font_list)
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);
1856 fputs(" ", state->fp);
1859 fputs(ef->ps_font, state->fp);
1862 fputs("\n", state->fp);
1865 static void eps_define_fonts(
1870 if (!state->font_list)
1872 for (ef = state->font_list; ef; ef = ef->next) {
1873 /* PostScript¨ LANGUAGE REFERENCE third edition
1877 "/%s findfont dup length dict begin\n"
1878 "{ 1 index /FID ne {def} {pop pop} ifelse } forall\n"
1879 "/Encoding ISOLatin1Encoding def\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);
1887 static int eps_prologue(
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)
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"
1915 "/CP {closepath} bd\n"
1916 "/WS {setlinewidth stroke} 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);
1928 static void eps_clear_dash(
1931 if (!state->has_dash)
1933 state->has_dash = 0;
1934 fputs("[1 0] 0 setdash\n", state->fp);
1937 static void eps_write_linearea(
1942 FILE *fp = state->fp;
1944 int clearDashIfAny = 1;
1946 eps_set_color(state, node->color);
1947 if (node->type == GFX_LINE) {
1950 if (state->linecap != 1) {
1951 fputs("1 setlinecap\n", fp);
1954 if (state->linejoin != 1) {
1955 fputs("1 setlinejoin\n", fp);
1956 state->linejoin = 1;
1958 svg_get_dash(node, &dash_info);
1959 if (dash_info.dash_enable) {
1961 state->has_dash = 1;
1963 svg_write_number(fp, dash_info.adjusted_on);
1965 svg_write_number(fp, dash_info.adjusted_off);
1967 svg_write_number(fp, dash_info.dash_offset);
1968 fputs(" setdash\n", fp);
1972 eps_clear_dash(state);
1973 for (i = 0; i < node->points; i++) {
1974 ArtVpath *vec = node->path + i;
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);
1985 switch (vec->code) {
1986 case ART_MOVETO_OPEN: /* fall-through */
1988 svg_write_number(fp, x);
1990 svg_write_number(fp, y);
1992 fputs(useOffset ? "M\n" : "m\n", fp);
1995 svg_write_number(fp, x);
1997 svg_write_number(fp, y);
1999 fputs(useOffset ? "L\n" : "l\n", fp);
2002 break; /* unsupported */
2007 if (node->type == GFX_LINE) {
2008 if (node->closed_path)
2010 if (node->size != state->line_width) {
2011 state->line_width = node->size;
2012 svg_write_number(fp, state->line_width);
2022 static void eps_write_text(
2026 FILE *fp = state->fp;
2027 const char *ps_font = afm_get_font_postscript_name(node->filename);
2031 #ifdef HAVE_MBSTOWCS
2033 wchar_t *p, *cstr, ch;
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);
2045 const unsigned char *p = node->text;
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);
2067 ch = afm_fix_osx_charset(ch); /* unsafe macro */
2068 if (++lineLen > 70) {
2069 fputs("\\\n", fp); /* backslash and \n */
2092 } else if (ch >= 126 || ch < 32) {
2093 fprintf(fp, "\\%03o", (unsigned int) ch);
2101 #ifdef HAVE_MBSTOWCS
2105 /* can't use svg_write_number as 2 decimals is far from enough to avoid
2107 fprintf(fp, ") [%f %f %f %f %f %f] T2\n",
2108 g.ma, g.mb, g.mc, g.md, g.tmx, g.tmy);
2111 svg_write_number(fp, g.tmx);
2113 svg_write_number(fp, g.tmy);
2118 static int eps_write_content(
2123 fputs("%\n", state->fp);
2124 for (node = state->canvas->firstnode; node; node = node->next) {
2125 switch (node->type) {
2128 eps_write_linearea(state, node);
2131 eps_write_text(state, node);
2139 gfx_canvas_t *canvas,
2142 gfx_color_t background,
2145 struct eps_state state;
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;
2156 state.linejoin = -1;
2158 state.line_width = 1;
2159 if (eps_prologue(&state) == -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)
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;
2177 /* ------- PDF -------
2178 PDF references page:
2179 http://partners.adobe.com/public/developer/pdf/index_reference.html
2182 typedef struct pdf_buffer {
2183 int id, is_obj, is_dict, is_stream, pdf_file_pos;
2185 int alloc_size, current_size;
2186 struct pdf_buffer *previous_buffer, *next_buffer;
2187 struct pdf_state *state;
2190 typedef struct pdf_font {
2191 const char *ps_font;
2193 struct pdf_font *next;
2196 typedef struct pdf_state {
2198 gfx_canvas_t *canvas;
2199 art_u32 page_width, page_height;
2200 pdf_font *font_list;
2201 pdf_buffer *first_buffer, *last_buffer;
2205 gfx_color_t stroke_color, fill_color;
2210 int linecap, linejoin;
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;
2219 static void pdf_init_buffer(
2223 int initial_size = 32;
2227 buf->alloc_size = 0;
2228 buf->current_size = 0;
2229 buf->data = (char *) malloc(initial_size);
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;
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;
2247 static void pdf_put(
2254 if (buf->alloc_size < buf->current_size + len) {
2255 int new_size = buf->alloc_size;
2258 while (new_size < buf->current_size + len)
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;
2266 memcpy(new_buf, buf->data, buf->current_size);
2268 buf->data = new_buf;
2269 buf->alloc_size = new_size;
2271 memcpy(buf->data + buf->current_size, text, len);
2272 buf->current_size += len;
2275 static void pdf_put_char(
2279 if (buf->alloc_size >= buf->current_size + 1) {
2280 buf->data[buf->current_size++] = c;
2285 pdf_put(buf, tmp, 1);
2289 static void pdf_puts(
2293 pdf_put(buf, text, strlen(text));
2296 static void pdf_indent(
2299 pdf_puts(buf, "\t");
2302 static void pdf_putsi(
2307 pdf_puts(buf, text);
2310 static void pdf_putint(
2316 sprintf(tmp, "%d", i);
2320 static void pdf_putnumber(
2326 svg_format_number(tmp, sizeof(tmp), d);
2330 static void pdf_put_string_contents_wide(
2332 const afm_char * text)
2334 const afm_char *p = text;
2339 ch = afm_fix_osx_charset(ch); /* unsafe macro */
2344 pdf_puts(buf, "\\(");
2347 pdf_puts(buf, "\\)");
2350 pdf_puts(buf, "\\\\");
2353 pdf_puts(buf, "\\n");
2356 pdf_puts(buf, "\\r");
2359 pdf_puts(buf, "\\t");
2363 pdf_put_char(buf, '?');
2364 } else if (ch > 125 || ch < 32) {
2365 pdf_put_char(buf, ch);
2369 snprintf(tmp, sizeof(tmp), "\\%03o", (int) ch);
2377 static void pdf_put_string_contents(
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);
2391 fprintf(stderr, "Decoding utf8 for '%s'\n", text);
2395 fprintf(stderr, "sz wc = %d\n", sizeof(wchar_t));
2397 fprintf(stderr, " %d = %c versus %d = %c\n", *p, (char) *p,
2398 255 & (int) *pp, *pp);
2406 pdf_put_string_contents_wide(buf, text);
2410 static void pdf_init_object(
2414 pdf_init_buffer(state, buf);
2415 buf->id = ++state->last_obj_id;
2420 static void pdf_init_dict(
2424 pdf_init_object(state, buf);
2428 static void pdf_set_color(
2431 gfx_color_t *current_color,
2434 #if USE_PDF_FAKE_ALPHA
2437 /* gfx_color_t is RRGGBBAA */
2438 if (*current_color == color)
2440 #if USE_PDF_FAKE_ALPHA
2441 a1 = (color & 255) / 255.0;
2443 #define pdf_color_calc(x) ( ((x) & 255) / 255.0 * a1 + a2)
2445 #define pdf_color_calc(x) ( ((x) & 255) / 255.0)
2447 pdf_putnumber(buf, pdf_color_calc(color >> 24));
2449 pdf_putnumber(buf, pdf_color_calc(color >> 16));
2451 pdf_putnumber(buf, pdf_color_calc(color >> 8));
2454 pdf_puts(buf, "\n");
2455 *current_color = color;
2458 static void pdf_set_stroke_color(
2462 pdf_set_color(buf, color, &buf->state->stroke_color, "RG");
2465 static void pdf_set_fill_color(
2469 pdf_set_color(buf, color, &buf->state->fill_color, "rg");
2472 static pdf_font *pdf_find_font(
2476 const char *ps_font = afm_get_font_postscript_name(node->filename);
2479 for (ef = state->font_list; ef; ef = ef->next) {
2480 if (!strcmp(ps_font, ef->ps_font))
2486 static void pdf_add_font(
2490 pdf_font *ef = pdf_find_font(state, node);
2494 ef = malloc(sizeof(pdf_font));
2496 rrd_set_error("malloc for pdf_font");
2497 state->has_failed = 1;
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;
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");
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 */
2525 static void pdf_create_fonts(
2530 for (node = state->canvas->firstnode; node; node = node->next) {
2531 if (node->type == GFX_TEXT)
2532 pdf_add_font(state, node);
2536 static void pdf_write_linearea(
2541 pdf_buffer *s = &state->graph_stream;
2543 if (node->type == GFX_LINE) {
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) {
2551 pdf_putnumber(s, dash_info.adjusted_on);
2553 pdf_putnumber(s, dash_info.adjusted_off);
2555 pdf_putnumber(s, dash_info.dash_offset);
2556 pdf_puts(s, " d\n");
2558 pdf_puts(s, "[] 0 d\n");
2561 pdf_set_stroke_color(s, node->color);
2562 if (state->linecap != 1) {
2563 pdf_puts(s, "1 j\n");
2566 if (state->linejoin != 1) {
2567 pdf_puts(s, "1 J\n");
2568 state->linejoin = 1;
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");
2576 pdf_set_fill_color(s, node->color);
2578 for (i = 0; i < node->points; i++) {
2579 ArtVpath *vec = node->path + i;
2581 double y = state->page_height - vec->y;
2583 if (node->type == GFX_AREA) {
2584 x += LINEOFFSET; /* adjust for libart handling of areas */
2587 switch (vec->code) {
2588 case ART_MOVETO_OPEN: /* fall-through */
2590 pdf_putnumber(s, x);
2592 pdf_putnumber(s, y);
2593 pdf_puts(s, " m\n");
2596 pdf_putnumber(s, x);
2598 pdf_putnumber(s, y);
2599 pdf_puts(s, " l\n");
2602 break; /* unsupported */
2607 if (node->type == GFX_LINE) {
2608 pdf_puts(s, node->closed_path ? "s\n" : "S\n");
2615 static void pdf_write_matrix(
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);
2628 pdf_putnumber(s, useTM ? g->tmy : g->my);
2630 /* can't use svg_write_number as 2 decimals is far from enough to avoid
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);
2639 static void pdf_write_text(
2646 pdf_buffer *s = &state->graph_stream;
2647 pdf_font *font = pdf_find_font(state, node);
2650 rrd_set_error("font disappeared");
2651 state->has_failed = 1;
2654 pdf_calc(state->page_height, node, &g);
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);
2664 pdf_putnumber(s, g.sizep.y);
2665 pdf_puts(s, " re\n");
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;
2676 pdf_putint(s, font->obj.id);
2678 pdf_putnumber(s, node->size);
2679 pdf_puts(s, " Tf\n");
2681 pdf_write_matrix(state, node, &g, 1);
2682 pdf_puts(s, " Tm\n");
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");
2690 static void pdf_write_content(
2694 int last_was_text = 0, next_is_text;
2696 for (node = state->canvas->firstnode; node; node = node->next) {
2697 switch (node->type) {
2700 pdf_write_linearea(state, node);
2703 next_is_text = node->next && node->next->type == GFX_TEXT;
2704 pdf_write_text(state, node, last_was_text, next_is_text);
2707 last_was_text = node->type == GFX_TEXT;
2711 static void pdf_init_document(
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)
2723 /* make stream last object in file */
2724 pdf_init_object(state, &state->graph_stream);
2725 state->graph_stream.is_stream = 1;
2728 static void pdf_setup_document(
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 */
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");
2740 pdf_putsi(&state->info_obj, "/Creator (");
2741 pdf_put_string_contents(&state->info_obj, creator);
2742 pdf_puts(&state->info_obj, ")\n");
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");
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");
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");
2772 static void pdf_write_string_to_file(
2776 fputs(text, state->fp);
2777 state->pdf_file_pos += strlen(text);
2780 static void pdf_write_buf_to_file(
2786 buf->pdf_file_pos = state->pdf_file_pos;
2788 snprintf(tmp, sizeof(tmp), "%d 0 obj\n", buf->id);
2789 pdf_write_string_to_file(state, tmp);
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");
2798 fwrite(buf->data, 1, buf->current_size, state->fp);
2799 state->pdf_file_pos += buf->current_size;
2801 pdf_write_string_to_file(state, "endstream\n");
2803 pdf_write_string_to_file(state, ">>\n");
2805 pdf_write_string_to_file(state, "endobj\n");
2808 static void pdf_write_to_file(
2811 pdf_buffer *buf = state->first_buffer;
2814 state->pdf_file_pos = 0;
2815 pdf_write_buf_to_file(state, &state->pdf_header);
2818 pdf_write_buf_to_file(state, buf);
2819 buf = buf->next_buffer;
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) {
2828 fprintf(state->fp, "%010d %05d n\x20\n", buf->pdf_file_pos, 0);
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);
2841 static void pdf_free_resources(
2844 pdf_buffer *buf = state->first_buffer;
2849 buf->alloc_size = buf->current_size = 0;
2850 buf = buf->next_buffer;
2852 while (state->font_list) {
2853 pdf_font *next = state->font_list->next;
2855 free(state->font_list);
2856 state->font_list = next;
2861 gfx_canvas_t *canvas,
2864 gfx_color_t UNUSED(background),
2867 struct pdf_state state;
2869 memset(&state, 0, sizeof(pdf_state));
2871 state.canvas = canvas;
2872 state.page_width = width;
2873 state.page_height = height;
2875 state.font_size = -1;
2876 state.font_list = NULL;
2878 state.linejoin = -1;
2879 pdf_init_document(&state);
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);
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;