X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=src%2Frrd_gfx.c;h=4ddd584e5d8423a55a435bb5e5ca3249f9caa40b;hb=dd7a80457423c23c6d6d9cc05b46ced2a3ec2dc6;hp=405291b345f3303a7c91790885c4ea07b81f6179;hpb=01399dbbb2b67d6fbe8d22eac1a1482f8efee461;p=rrdtool.git diff --git a/src/rrd_gfx.c b/src/rrd_gfx.c index 405291b..4ddd584 100644 --- a/src/rrd_gfx.c +++ b/src/rrd_gfx.c @@ -11,13 +11,13 @@ #else # define DPRINT(x) #endif - +#include "rrd_tool.h" #include #include #include FT_FREETYPE_H -#include #include "rrd_gfx.h" +#include "rrd_afm.h" /* lines are better drawn on the pixle than between pixles */ #define LINEOFFSET 0.5 @@ -32,11 +32,13 @@ gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){ node->path = NULL; /* path */ node->points = 0; node->points_max =0; + node->closed_path = 0; node->svp = NULL; /* svp */ node->filename = NULL; /* font or image filename */ node->text = NULL; node->x = 0.0; node->y = 0.0; /* position */ + node->angle = 0; node->halign = GFX_H_NULL; /* text alignement */ node->valign = GFX_V_NULL; /* text alignement */ node->tabwidth = 0.0; @@ -55,14 +57,25 @@ gfx_canvas_t *gfx_new_canvas (void) { gfx_canvas_t *canvas = art_new(gfx_canvas_t,1); canvas->firstnode = NULL; canvas->lastnode = NULL; + canvas->imgformat = IF_PNG; /* we default to PNG output */ + canvas->interlaced = 0; + canvas->zoom = 1.0; return canvas; } /* create a new line */ gfx_node_t *gfx_new_line(gfx_canvas_t *canvas, - double x0, double y0, - double x1, double y1, + double X0, double Y0, + double X1, double Y1, double width, gfx_color_t color){ + return gfx_new_dashed_line(canvas, X0, Y0, X1, Y1, width, color, 0, 0); +} + +gfx_node_t *gfx_new_dashed_line(gfx_canvas_t *canvas, + double X0, double Y0, + double X1, double Y1, + double width, gfx_color_t color, + double dash_on, double dash_off){ gfx_node_t *node; ArtVpath *vec; @@ -70,23 +83,25 @@ gfx_node_t *gfx_new_line(gfx_canvas_t *canvas, if (node == NULL) return NULL; vec = art_new(ArtVpath, 3); if (vec == NULL) return NULL; - vec[0].code = ART_MOVETO_OPEN; vec[0].x=x0+LINEOFFSET; vec[0].y=y0+LINEOFFSET; - vec[1].code = ART_LINETO; vec[1].x=x1+LINEOFFSET; vec[1].y=y1+LINEOFFSET; + vec[0].code = ART_MOVETO_OPEN; vec[0].x=X0+LINEOFFSET; vec[0].y=Y0+LINEOFFSET; + vec[1].code = ART_LINETO; vec[1].x=X1+LINEOFFSET; vec[1].y=Y1+LINEOFFSET; vec[2].code = ART_END; node->points = 3; node->points_max = 3; node->color = color; node->size = width; + node->dash_on = dash_on; + node->dash_off = dash_off; node->path = vec; return node; } /* create a new area */ gfx_node_t *gfx_new_area (gfx_canvas_t *canvas, - double x0, double y0, - double x1, double y1, - double x2, double y2, + double X0, double Y0, + double X1, double Y1, + double X2, double Y2, gfx_color_t color) { gfx_node_t *node; @@ -95,10 +110,10 @@ gfx_node_t *gfx_new_area (gfx_canvas_t *canvas, if (node == NULL) return NULL; vec = art_new(ArtVpath, 5); if (vec == NULL) return NULL; - vec[0].code = ART_MOVETO; vec[0].x=x0; vec[0].y=y0; - vec[1].code = ART_LINETO; vec[1].x=x1; vec[1].y=y1; - vec[2].code = ART_LINETO; vec[2].x=x2; vec[2].y=y2; - vec[3].code = ART_LINETO; vec[3].x=x0; vec[3].y=y0; + vec[0].code = ART_MOVETO; vec[0].x=X0; vec[0].y=Y0; + vec[1].code = ART_LINETO; vec[1].x=X1; vec[1].y=Y1; + vec[2].code = ART_LINETO; vec[2].x=X2; vec[2].y=Y2; + vec[3].code = ART_LINETO; vec[3].x=X0; vec[3].y=Y0; vec[4].code = ART_END; node->points = 5; @@ -114,8 +129,8 @@ int gfx_add_point (gfx_node_t *node, double x, double y){ if (node == NULL) return 1; if (node->type == GFX_AREA) { - double x0 = node->path[0].x; - double y0 = node->path[0].y; + double X0 = node->path[0].x; + double Y0 = node->path[0].y; node->points -= 2; art_vpath_add_point (&(node->path), &(node->points), @@ -126,7 +141,7 @@ int gfx_add_point (gfx_node_t *node, &(node->points), &(node->points_max), ART_LINETO, - x0,y0); + X0,Y0); art_vpath_add_point (&(node->path), &(node->points), &(node->points_max), @@ -152,7 +167,11 @@ int gfx_add_point (gfx_node_t *node, return 0; } - +void gfx_close_path (gfx_node_t *node) { + node->closed_path = 1; + if (node->path[0].code == ART_MOVETO_OPEN) + node->path[0].code = ART_MOVETO; +} /* create a text node */ gfx_node_t *gfx_new_text (gfx_canvas_t *canvas, @@ -173,14 +192,50 @@ gfx_node_t *gfx_new_text (gfx_canvas_t *canvas, node->filename = strdup(font); node->x = x; node->y = y; + node->angle = angle; node->color = color; node->tabwidth = tabwidth; node->halign = h_align; node->valign = v_align; +#if 0 + /* debugging: show text anchor */ + gfx_new_line(canvas, x - 3, y + 0, x, y, 0.2, 0x00FF0000); + gfx_new_line(canvas, x - 0, y + 3, x, y, 0.2, 0x00FF0000); +#endif return node; } -double gfx_get_text_width ( double start, char* font, double size, +int gfx_render(gfx_canvas_t *canvas, + art_u32 width, art_u32 height, + gfx_color_t background, FILE *fp){ + switch (canvas->imgformat) { + case IF_PNG: + return gfx_render_png (canvas, width, height, background, fp); + case IF_SVG: + return gfx_render_svg (canvas, width, height, background, fp); + case IF_EPS: + return gfx_render_eps (canvas, width, height, background, fp); + default: + return -1; + } +} + +double gfx_get_text_width ( gfx_canvas_t *canvas, + double start, char* font, double size, + double tabwidth, char* text){ + switch (canvas->imgformat) { + case IF_PNG: + return gfx_get_text_width_libart (canvas, start, font, size, tabwidth, text); + case IF_SVG: /* fall through */ + case IF_EPS: + return afm_get_text_width(start, font, size, tabwidth, text); + default: + return size * strlen(text); + } +} + +double gfx_get_text_width_libart ( gfx_canvas_t *canvas, + double start, char* font, double size, double tabwidth, char* text){ FT_GlyphSlot slot; @@ -227,15 +282,34 @@ double gfx_get_text_width ( double start, char* font, double size, return text_width; } +static void gfx_libart_close_path(gfx_canvas_t *canvas, + gfx_node_t *node, ArtVpath **vec) +{ + /* libart must have end==start for closed paths, + even if using ART_MOVETO and not ART_MOVETO_OPEN + so add extra point which is the same as the starting point */ + int points_max = node->points; /* scaled array has exact size */ + int points = node->points - 1; + art_vpath_add_point (vec, &points, &points_max, ART_LINETO, + (**vec).x, (**vec).y); + art_vpath_add_point (vec, &points, &points_max, ART_END, 0, 0); +} - +static void gfx_round_scaled_coordinates(gfx_canvas_t *canvas, + gfx_node_t *node, ArtVpath *vec) +{ + while (vec->code != ART_END) { + vec->x = floor(vec->x - LINEOFFSET + 0.5) + LINEOFFSET; + vec->y = floor(vec->y - LINEOFFSET + 0.5) + LINEOFFSET; + vec++; + } +} static int gfx_save_png (art_u8 *buffer, FILE *fp, long width, long height, long bytes_per_pixel); /* render grafics into png image */ int gfx_render_png (gfx_canvas_t *canvas, art_u32 width, art_u32 height, - double zoom, gfx_color_t background, FILE *fp){ @@ -243,8 +317,8 @@ int gfx_render_png (gfx_canvas_t *canvas, gfx_node_t *node = canvas->firstnode; art_u8 red = background >> 24, green = (background >> 16) & 0xff; art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff ); - unsigned long pys_width = width * zoom; - unsigned long pys_height = height * zoom; + unsigned long pys_width = width * canvas->zoom; + unsigned long pys_height = height * canvas->zoom; const int bytes_per_pixel = 3; unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */ art_u8 *buffer = art_new (art_u8, rowstride*pys_height); @@ -257,12 +331,15 @@ int gfx_render_png (gfx_canvas_t *canvas, ArtVpath *vec; double dst[6]; ArtSVP *svp; - art_affine_scale(dst,zoom,zoom); + art_affine_scale(dst,canvas->zoom,canvas->zoom); vec = art_vpath_affine_transform(node->path,dst); + if (node->closed_path) + gfx_libart_close_path(canvas, node, &vec); + gfx_round_scaled_coordinates(canvas, node, vec); if(node->type == GFX_LINE){ svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, - node->size*zoom,1,1); + node->size*canvas->zoom,1,1); } else { svp = art_svp_from_vpath ( vec ); } @@ -301,11 +378,11 @@ int gfx_render_png (gfx_canvas_t *canvas, error = FT_Set_Char_Size(face, /* handle to face object */ (long)(node->size*64), (long)(node->size*64), - (long)(100*zoom), - (long)(100*zoom)); + (long)(100*canvas->zoom), + (long)(100*canvas->zoom)); if ( error ) break; - pen_x = node->x * zoom; - pen_y = node->y * zoom; + pen_x = node->x * canvas->zoom; + pen_y = node->y * canvas->zoom; slot = face->glyph; for(text=(unsigned char *)node->text;*text;text++) { @@ -338,12 +415,14 @@ int gfx_render_png (gfx_canvas_t *canvas, case GFX_H_RIGHT: pen_x -= text_width; break; case GFX_H_CENTER: pen_x -= text_width / 2.0; break; case GFX_H_LEFT: break; + case GFX_H_NULL: break; } switch(node->valign){ case GFX_V_TOP: pen_y += text_height; break; case GFX_V_CENTER: pen_y += text_height / 2.0; break; case GFX_V_BOTTOM: break; + case GFX_V_NULL: break; } glyph_index=0; @@ -467,3 +546,770 @@ static int gfx_save_png (art_u8 *buffer, FILE *fp, long width, long height, lon png_destroy_write_struct(&png_ptr, &info_ptr); return 1; } + + +/* ------- SVG ------- + SVG reference: + http://www.w3.org/TR/SVG/ +*/ +static int svg_indent = 0; +static int svg_single_line = 0; +static const char *svg_default_font = "Helvetica"; + +static void svg_print_indent(FILE *fp) +{ + int i; + for (i = svg_indent - svg_single_line; i > 0; i--) { + putc(' ', fp); + putc(' ', fp); + } +} + +static void svg_start_tag(FILE *fp, const char *name) +{ + svg_print_indent(fp); + putc('<', fp); + fputs(name, fp); + svg_indent++; +} + +static void svg_close_tag_single_line(FILE *fp) +{ + svg_single_line++; + putc('>', fp); +} + +static void svg_close_tag(FILE *fp) +{ + putc('>', fp); + if (!svg_single_line) + putc('\n', fp); +} + +static void svg_end_tag(FILE *fp, const char *name) +{ + /* name is NULL if closing empty-node tag */ + svg_indent--; + if (svg_single_line) + svg_single_line--; + else if (name) + svg_print_indent(fp); + if (name != NULL) { + fputs("': fputs(">", fp); break; + case '"': fputs(""", fp); break; + default: + if (ch >= 127) + fprintf(fp, "&#%d;", ch); + else + putc(ch, fp); + } + } +} + +static void svg_write_number(FILE *fp, double d) +{ + /* omit decimals if integer to reduce filesize */ + char buf[60], *p; + snprintf(buf, sizeof(buf), "%.2f", d); + p = buf; /* doesn't trust snprintf return value */ + while (*p) + p++; + while (--p > buf) { + char ch = *p; + if (ch == '0') { + *p = '\0'; /* zap trailing zeros */ + continue; + } + if (ch == '.') + *p = '\0'; /* zap trailing dot */ + break; + } + fputs(buf, fp); +} + +static int svg_color_is_black(int c) +{ + /* gfx_color_t is RRGGBBAA */ + return c == 0x000000FF; +} + +static void svg_write_color(FILE *fp, gfx_color_t c, const char *attr) +{ + /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB and #RGB like html */ + gfx_color_t rrggbb = (int)((c >> 8) & 0xFFFFFF); + gfx_color_t opacity = c & 0xFF; + fprintf(fp, " %s=\"", attr); + if ((rrggbb & 0x0F0F0F) == ((rrggbb >> 4) & 0x0F0F0F)) { + /* css2 short form, #rgb is #rrggbb, not #r0g0b0 */ + fprintf(fp, "#%03lX", + ( ((rrggbb >> 8) & 0xF00) + | ((rrggbb >> 4) & 0x0F0) + | ( rrggbb & 0x00F))); + } else { + fprintf(fp, "#%06lX", rrggbb); + } + fputs("\"", fp); + if (opacity != 0xFF) { + fprintf(fp, " stroke-opacity=\""); + svg_write_number(fp, opacity / 255.0); + fputs("\"", fp); + } +} + +static void svg_common_path_attributes(FILE *fp, gfx_node_t *node) +{ + fputs(" stroke-width=\"", fp); + svg_write_number(fp, node->size); + fputs("\"", fp); + svg_write_color(fp, node->color, "stroke"); + fputs(" fill=\"none\"", fp); + if (node->dash_on > 0 && node->dash_off > 0) { + fputs(" stroke-dasharray=\"", fp); + svg_write_number(fp, node->dash_on); + fputs(",", fp); + svg_write_number(fp, node->dash_off); + fputs("\"", fp); + } +} + +static int svg_is_int_step(double a, double b) +{ + double diff = fabs(a - b); + return floor(diff) == diff; +} + +static int svg_path_straight_segment(FILE *fp, + double lastA, double currentA, double currentB, + gfx_node_t *node, + int segment_idx, int isx, char absChar, char relChar) +{ + if (!svg_is_int_step(lastA, currentA)) { + putc(absChar, fp); + svg_write_number(fp, currentA); + return 0; + } + if (segment_idx < node->points - 1) { + ArtVpath *vec = node->path + segment_idx + 1; + if (vec->code == ART_LINETO) { + double nextA = (isx ? vec->x : vec->y) - LINEOFFSET; + double nextB = (isx ? vec->y : vec->x) - LINEOFFSET; + if (nextB == currentB + && ((currentA >= lastA) == (nextA >= currentA)) + && svg_is_int_step(currentA, nextA)) { + return 1; /* skip to next as it is a straight line */ + } + } + } + putc(relChar, fp); + svg_write_number(fp, currentA - lastA); + return 0; +} + +static void svg_path(FILE *fp, gfx_node_t *node, int multi) +{ + int i; + double lastX = 0, lastY = 0; + /* for straight lines tags take less space than + tags because of the efficient packing + in the 'd' attribute */ + svg_start_tag(fp, "path"); + if (!multi) + svg_common_path_attributes(fp, node); + fputs(" d=\"", fp); + /* specification of the 'd' attribute: */ + /* http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation */ + for (i = 0; i < node->points; i++) { + ArtVpath *vec = node->path + i; + double x = vec->x - LINEOFFSET; + double y = vec->y - LINEOFFSET; + switch (vec->code) { + case ART_MOVETO_OPEN: /* fall-through */ + case ART_MOVETO: + putc('M', fp); + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + break; + case ART_LINETO: + /* try optimize filesize by using minimal lineto commands */ + /* without introducing rounding errors. */ + if (x == lastX) { + if (svg_path_straight_segment(fp, lastY, y, x, node, i, 0, 'V', 'v')) + continue; + } else if (y == lastY) { + if (svg_path_straight_segment(fp, lastX, x, y, node, i, 1, 'H', 'h')) + continue; + } else { + putc('L', fp); + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + } + break; + case ART_CURVETO: break; /* unsupported */ + case ART_END: break; /* nop */ + } + lastX = x; + lastY = y; + } + if (node->closed_path) + fputs(" Z", fp); + fputs("\"", fp); + svg_close_tag_empty_node(fp); +} + +static void svg_multi_path(FILE *fp, gfx_node_t **nodeR) +{ + /* optimize for multiple paths with the same color, penwidth, etc. */ + int num = 1; + gfx_node_t *node = *nodeR; + gfx_node_t *next = node->next; + while (next) { + if (next->type != node->type + || next->size != node->size + || next->color != node->color + || next->dash_on != node->dash_on + || next->dash_off != node->dash_off) + break; + next = next->next; + num++; + } + if (num == 1) { + svg_path(fp, node, 0); + return; + } + svg_start_tag(fp, "g"); + svg_common_path_attributes(fp, node); + svg_close_tag(fp); + while (num && node) { + svg_path(fp, node, 1); + if (!--num) + break; + node = node->next; + *nodeR = node; + } + svg_end_tag(fp, "g"); +} + +static void svg_area(FILE *fp, gfx_node_t *node) +{ + int i; + double startX = 0, startY = 0; + svg_start_tag(fp, "polygon"); + fputs(" ", fp); + svg_write_color(fp, node->color, "fill"); + fputs(" points=\"", fp); + for (i = 0; i < node->points; i++) { + ArtVpath *vec = node->path + i; + double x = vec->x - LINEOFFSET; + double y = vec->y - LINEOFFSET; + switch (vec->code) { + case ART_MOVETO_OPEN: /* fall-through */ + case ART_MOVETO: + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + startX = x; + startY = y; + break; + case ART_LINETO: + if (i == node->points - 2 + && node->path[i + 1].code == ART_END + && fabs(x - startX) < 0.001 && fabs(y - startY) < 0.001) { + break; /* poly area always closed, no need for last point */ + } + putc(' ', fp); + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + break; + case ART_CURVETO: break; /* unsupported */ + case ART_END: break; /* nop */ + } + } + fputs("\"", fp); + svg_close_tag_empty_node(fp); +} + +static void svg_text(FILE *fp, gfx_node_t *node) +{ + double x = node->x - LINEOFFSET; + double y = node->y - LINEOFFSET; + if (node->angle != 0) { + svg_start_tag(fp, "g"); + fputs(" transform=\"translate(", fp); + svg_write_number(fp, x); + fputs(",", fp); + svg_write_number(fp, y); + fputs(") rotate(", fp); + svg_write_number(fp, node->angle); + fputs(")\"", fp); + x = y = 0; + svg_close_tag(fp); + } + switch (node->valign) { + case GFX_V_TOP: y += node->size; break; + case GFX_V_CENTER: y += node->size / 2; break; + case GFX_V_BOTTOM: break; + case GFX_V_NULL: break; + } + svg_start_tag(fp, "text"); + fputs(" x=\"", fp); + svg_write_number(fp, x); + fputs("\" y=\"", fp); + svg_write_number(fp, y); + if (strcmp(node->filename, svg_default_font)) + fprintf(fp, " font-family=\"%s\"", node->filename); + fputs("\" font-size=\"", fp); + svg_write_number(fp, node->size); + fputs("\"", fp); + if (!svg_color_is_black(node->color)) + svg_write_color(fp, node->color, "fill"); + switch (node->halign) { + case GFX_H_RIGHT: fputs(" text-anchor=\"end\"", fp); break; + case GFX_H_CENTER: fputs(" text-anchor=\"middle\"", fp); break; + case GFX_H_LEFT: break; + case GFX_H_NULL: break; + } + svg_close_tag_single_line(fp); + /* support for node->tabwidth missing */ + svg_write_text(fp, node->text); + svg_end_tag(fp, "text"); + if (node->angle != 0) + svg_end_tag(fp, "g"); +} + +int gfx_render_svg (gfx_canvas_t *canvas, + art_u32 width, art_u32 height, + gfx_color_t background, FILE *fp){ + gfx_node_t *node = canvas->firstnode; + fputs( +"\n" +"\n" +"\n", fp); + svg_start_tag(fp, "svg"); + fputs(" width=\"", fp); + svg_write_number(fp, width * canvas->zoom); + fputs("\" height=\"", fp); + svg_write_number(fp, height * canvas->zoom); + fputs("\" x=\"0\" y=\"0\" viewBox=\"", fp); + svg_write_number(fp, -LINEOFFSET); + fputs(" ", fp); + svg_write_number(fp, -LINEOFFSET); + fputs(" ", fp); + svg_write_number(fp, width - LINEOFFSET); + fputs(" ", fp); + svg_write_number(fp, height - LINEOFFSET); + fputs("\" preserveAspectRatio=\"xMidYMid\"", fp); + fprintf(fp, " font-family=\"%s\"", svg_default_font); /* default font */ + fputs(" stroke-linecap=\"round\" stroke-linejoin=\"round\"", fp); + svg_close_tag(fp); + svg_start_tag(fp, "rect"); + fprintf(fp, " x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", width, height); + svg_write_color(fp, background, "fill"); + svg_close_tag_empty_node(fp); + while (node) { + switch (node->type) { + case GFX_LINE: + svg_multi_path(fp, &node); + break; + case GFX_AREA: + svg_area(fp, node); + break; + case GFX_TEXT: + svg_text(fp, node); + } + node = node->next; + } + svg_end_tag(fp, "svg"); + return 0; +} + +/* ------- EPS ------- + EPS and Postscript references: + http://partners.adobe.com/asn/developer/technotes/postscript.html +*/ + +typedef struct eps_font +{ + const char *ps_font; + int id; + struct eps_font *next; +} eps_font; + +typedef struct eps_state +{ + FILE *fp; + gfx_canvas_t *canvas; + art_u32 page_width, page_height; + eps_font *font_list; + /*--*/ + gfx_color_t color; + const char *font; + double font_size; + double line_width; + int linecap, linejoin; +} eps_state; + +static void eps_set_color(eps_state *state, gfx_color_t color) +{ + /* gfx_color_t is RRGGBBAA */ + if (state->color == color) + return; + fprintf(state->fp, "%d %d %d Rgb\n", + (int)((color >> 24) & 255), + (int)((color >> 16) & 255), + (int)((color >> 8) & 255)); + state->color = color; +} + +static int eps_add_font(eps_state *state, gfx_node_t *node) +{ + /* The fonts list could be postponed to the end using + (atend), but let's be nice and have them in the header. */ + const char *ps_font = afm_get_font_postscript_name(node->filename); + eps_font *ef; + for (ef = state->font_list; ef; ef = ef->next) { + if (!strcmp(ps_font, ef->ps_font)) + return 0; + } + ef = malloc(sizeof(eps_font)); + if (ef == NULL) { + rrd_set_error("malloc for eps_font"); + return -1; + } + ef->next = state->font_list; + ef->ps_font = ps_font; + state->font_list = ef; + return 0; +} + +static void eps_list_fonts(eps_state *state, const char *dscName) +{ + eps_font *ef; + int lineLen = strlen(dscName); + if (!state->font_list) + return; + fputs(dscName, state->fp); + for (ef = state->font_list; ef; ef = ef->next) { + int nameLen = strlen(ef->ps_font); + if (lineLen + nameLen > 100 && lineLen) { + fputs("\n", state->fp); + fputs("%%- \n", state->fp); + lineLen = 5; + } else { + fputs(" ", state->fp); + lineLen++; + } + fputs(ef->ps_font, state->fp); + lineLen += nameLen; + } + fputs("\n", state->fp); +} + +static void eps_define_fonts(eps_state *state) +{ + eps_font *ef; + if (!state->font_list) + return; + for (ef = state->font_list; ef; ef = ef->next) { + /* PostScript¨ LANGUAGE REFERENCE third edition + page 349 */ + fprintf(state->fp, + "%%\n" + "/%s findfont dup length dict begin\n" + "{ 1 index /FID ne {def} {pop pop} ifelse } forall\n" + "/Encoding ISOLatin1Encoding def\n" + "currentdict end\n" + "/%s-ISOLatin1 exch definefont pop\n" + "/SetFont-%s { /%s-ISOLatin1 findfont exch scalefont setfont } bd\n", + ef->ps_font, ef->ps_font, ef->ps_font, ef->ps_font); + } +} + +static int eps_prologue(eps_state *state) +{ + gfx_node_t *node; + fputs( + "%!PS-Adobe-3.0 EPSF-3.0\n" + "%%Creator: RRDtool 1.1.x, Tobias Oetiker, http://tobi.oetiker.ch\n" + /* can't like weird chars here */ + "%%Title: (RRDTool output)\n" + "%%DocumentData: Clean7Bit\n" + "", state->fp); + fprintf(state->fp, "%%%%BoundingBox: 0 0 %d %d\n", + state->page_width, state->page_height); + for (node = state->canvas->firstnode; node; node = node->next) { + if (node->type == GFX_TEXT && eps_add_font(state, node) == -1) + return -1; + } + eps_list_fonts(state, "%%DocumentFonts:"); + eps_list_fonts(state, "%%DocumentNeededFonts:"); + fputs( + "%%EndComments\n" + "%%BeginProlog\n" + "%%EndProlog\n" /* must have, or BoundingBox is ignored */ + "/bd { bind def } bind def\n" + "", state->fp); + fprintf(state->fp, "/X { %.2f add } bd\n", LINEOFFSET); + fputs( + "/X2 {X exch X exch} bd\n" + "/M {X2 moveto} bd\n" + "/L {X2 lineto} bd\n" + "/m {moveto} bd\n" + "/l {lineto} bd\n" + "/S {stroke} bd\n" + "/CP {closepath} bd\n" + "/WS {setlinewidth stroke} bd\n" + "/F {fill} bd\n" + "/TaL { } bd\n" + "/TaC {dup stringwidth pop neg 2 div 0 rmoveto } bd\n" + "/TaR {dup stringwidth pop neg 0 rmoveto } bd\n" + "/TL {moveto TaL show} bd\n" + "/TC {moveto TaC show} bd\n" + "/TR {moveto TaR show} bd\n" + "/Rgb { 255.0 div 3 1 roll\n" + " 255.0 div 3 1 roll \n" + " 255.0 div 3 1 roll setrgbcolor } bd\n" + "", state->fp); + eps_define_fonts(state); + return 0; +} + +static void eps_write_linearea(eps_state *state, gfx_node_t *node) +{ + int i; + FILE *fp = state->fp; + int useOffset = 0; + eps_set_color(state, node->color); + if (node->type == GFX_LINE) { + if (state->linecap != 1) { + fputs("1 setlinecap\n", fp); + state->linecap = 1; + } + if (state->linejoin != 1) { + fputs("1 setlinejoin\n", fp); + state->linejoin = 1; + } + } + for (i = 0; i < node->points; i++) { + ArtVpath *vec = node->path + i; + double x = vec->x; + double y = state->page_height - vec->y; + if (vec->code == ART_MOVETO_OPEN || vec->code == ART_MOVETO) + useOffset = (fabs(x - floor(x) - 0.5) < 0.01 && fabs(y - floor(y) - 0.5) < 0.01); + if (useOffset) { + x -= LINEOFFSET; + y -= LINEOFFSET; + } + switch (vec->code) { + case ART_MOVETO_OPEN: /* fall-through */ + case ART_MOVETO: + svg_write_number(fp, x); + fputc(' ', fp); + svg_write_number(fp, y); + fputc(' ', fp); + fputs(useOffset ? "M\n" : "m\n", fp); + break; + case ART_LINETO: + svg_write_number(fp, x); + fputc(' ', fp); + svg_write_number(fp, y); + fputc(' ', fp); + fputs(useOffset ? "L\n" : "l\n", fp); + break; + case ART_CURVETO: break; /* unsupported */ + case ART_END: break; /* nop */ + } + } + if (node->type == GFX_LINE) { + if (node->closed_path) + fputs("CP ", fp); + if (node->size != state->line_width) { + state->line_width = node->size; + svg_write_number(fp, state->line_width); + fputs(" WS\n", fp); + } else { + fputs("S\n", fp); + } + } else { + fputs("F\n", fp); + } +} + +static void eps_write_text(eps_state *state, gfx_node_t *node) +{ + FILE *fp = state->fp; + const unsigned char *p; + const char *ps_font = afm_get_font_postscript_name(node->filename); + char align = 'L'; + double x = node->x; + double y = state->page_height - node->y, ydelta = 0; + int lineLen = 0; + eps_set_color(state, node->color); + if (strcmp(ps_font, state->font) || node->size != state->font_size) { + state->font = ps_font; + state->font_size = node->size; + svg_write_number(fp, state->font_size); + fprintf(fp, " SetFont-%s\n", state->font); + } + fputs("(", fp); + lineLen = 20; + for (p = (const unsigned char*)node->text; *p; p++) { + if (lineLen > 70) { + fputs("\\\n", fp); /* backslash and \n */ + lineLen = 0; + } + switch (*p) { + case '(': + case ')': + case '\\': + case '\n': + case '\r': + case '\t': + fputc('\\', fp); + lineLen++; + /* fall-through */ + default: + if (*p >= 126) + fprintf(fp, "\\%03o", *p); + else + fputc(*p, fp); + lineLen++; + } + } + fputs(") ", fp); + switch(node->valign){ + case GFX_V_TOP: ydelta = -node->size; break; + case GFX_V_CENTER: ydelta = -node->size / 3.0; break; + case GFX_V_BOTTOM: break; + case GFX_V_NULL: break; + } + if (node->angle == 0) + y += ydelta; + switch (node->halign) { + case GFX_H_RIGHT: align = 'R'; break; + case GFX_H_CENTER: align = 'C'; break; + case GFX_H_LEFT: align= 'L'; break; + case GFX_H_NULL: align= 'L'; break; + } + if (node->angle != 0) { + fputs("\n", fp); + fputs(" gsave ", fp); + svg_write_number(fp, x); + fputc(' ', fp); + svg_write_number(fp, y); + fputs(" translate ", fp); + svg_write_number(fp, -node->angle); + fputs(" rotate 0 ", fp); + svg_write_number(fp, ydelta); + fputs(" moveto ", fp); + fprintf(fp, "Ta%c", align); + fputs(" show grestore\n", fp); + } else { + svg_write_number(fp, x); + fputc(' ', fp); + svg_write_number(fp, y); + fputs(" T", fp); + fputc(align, fp); + fputc('\n', fp); + } +} + +static int eps_write_content(eps_state *state) +{ + gfx_node_t *node; + fputs("%\n", state->fp); + for (node = state->canvas->firstnode; node; node = node->next) { + switch (node->type) { + case GFX_LINE: + case GFX_AREA: + eps_write_linearea(state, node); + break; + case GFX_TEXT: + eps_write_text(state, node); + break; + } + } + return 0; +} + +int gfx_render_eps (gfx_canvas_t *canvas, + art_u32 width, art_u32 height, + gfx_color_t background, FILE *fp){ + struct eps_state state; + state.fp = fp; + state.canvas = canvas; + state.page_width = width; + state.page_height = height; + state.font = "no-default-font"; + state.font_size = -1; + state.color = 0; /* black */ + state.font_list = NULL; + state.linecap = -1; + state.linejoin = -1; + if (eps_prologue(&state) == -1) + return -1; + eps_set_color(&state, background); + fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n", + height, width, height, width); + if (eps_write_content(&state) == -1) + return 0; + fputs("showpage\n", fp); + fputs("%%EOF\n", fp); + while (state.font_list) { + eps_font *next = state.font_list->next; + free(state.font_list); + state.font_list = next; + } + return 0; +}