-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;
- node->points -= 2;
- art_vpath_add_point (&(node->path),
- &(node->points),
- &(node->points_max),
- ART_LINETO,
- x,y);
- art_vpath_add_point (&(node->path),
- &(node->points),
- &(node->points_max),
- ART_LINETO,
- X0,Y0);
- art_vpath_add_point (&(node->path),
- &(node->points),
- &(node->points_max),
- ART_END,
- 0,0);
- } else if (node->type == GFX_LINE) {
- node->points -= 1;
- art_vpath_add_point (&(node->path),
- &(node->points),
- &(node->points_max),
- ART_LINETO,
- x+LINEOFFSET,y+LINEOFFSET);
- art_vpath_add_point (&(node->path),
- &(node->points),
- &(node->points_max),
- ART_END,
- 0,0);
-
- } else {
- /* can only add point to areas and lines */
- return 1;
- }
- 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,
- double x, double y, gfx_color_t color,
- char* font, double size,
- double tabwidth, double angle,
- enum gfx_h_align_en h_align,
- enum gfx_v_align_en v_align,
- char* text){
- gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
- if (angle != 0.0){
- /* currently we only support 0 and 270 */
- angle = 270.0;
- }
-
- node->text = strdup(text);
- node->size = size;
- 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
- green is along x-axis, red is downward y-axis */
- if (1) {
- double a = 2 * M_PI * -node->angle / 360.0;
- double cos_a = cos(a);
- double sin_a = sin(a);
- double len = 3;
- gfx_new_line(canvas,
- x, y,
- x + len * cos_a, y - len * sin_a,
- 0.2, 0x00FF0000);
- gfx_new_line(canvas,
- x, y,
- x + len * sin_a, y + len * cos_a,
- 0.2, 0xFF000000);
- }
-#endif
- return node;
-}
-
-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);
- case IF_PDF:
- return gfx_render_pdf (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:
- case IF_PDF:
- 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;
- FT_UInt previous=0;
- FT_UInt glyph_index=0;
- FT_Bool use_kerning;
- int error;
- FT_Face face;
- FT_Library library=NULL;
- double text_width=0;
- FT_Init_FreeType( &library );
- error = FT_New_Face( library, font, 0, &face );
- if ( error ) return -1;
- error = FT_Set_Char_Size(face, size*64,size*64, 100,100);
- if ( error ) return -1;
-
- use_kerning = FT_HAS_KERNING(face);
- slot = face->glyph;
- for(;*text;text++) {
- previous = glyph_index;
- glyph_index = FT_Get_Char_Index( face, *text);
-
- if (use_kerning && previous && glyph_index){
- FT_Vector delta;
- FT_Get_Kerning( face, previous, glyph_index,
- 0, &delta );
- text_width += (double)delta.x / 64.0;
-
- }
- error = FT_Load_Glyph( face, glyph_index, 0 );
- if ( error ) {
- FT_Done_FreeType(library);
- return -1;
- }
- if (! previous) {
- text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
- }
- text_width += (double)slot->metrics.horiAdvance / 64.0;
- }
- text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
- text_width += (double)slot->metrics.width / 64.0; /* add just char width */
- text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
- FT_Done_FreeType(library);
- 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,
- gfx_color_t background, FILE *fp){
-
-
- FT_Library library;
- 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 * 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);
- art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
- FT_Init_FreeType( &library );
- while(node){
- switch (node->type) {
- case GFX_LINE:
- case GFX_AREA: {
- ArtVpath *vec;
- double dst[6];
- ArtSVP *svp;
- 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*canvas->zoom,1,1);
- } else {
- svp = art_svp_from_vpath ( vec );
- }
- art_free(vec);
- art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
- node->color, buffer, rowstride, NULL);
- art_free(svp);
- break;
- }
- case GFX_TEXT: {
- int error;
- float text_width=0.0, text_height = 0.0;
- unsigned char *text;
- art_u8 fcolor[3],falpha;
- FT_Face face;
- FT_GlyphSlot slot;
- FT_UInt previous=0;
- FT_UInt glyph_index=0;
- FT_Bool use_kerning;
-
- float pen_x = 0.0 , pen_y = 0.0;
- /* double x,y; */
- long ix,iy,iz;
-
- fcolor[0] = node->color >> 24;
- fcolor[1] = (node->color >> 16) & 0xff;
- fcolor[2] = (node->color >> 8) & 0xff;
- falpha = node->color & 0xff;
- error = FT_New_Face( library,
- (char *)node->filename,
- 0,
- &face );
- if ( error ) break;
- use_kerning = FT_HAS_KERNING(face);
-
- error = FT_Set_Char_Size(face, /* handle to face object */
- (long)(node->size*64),
- (long)(node->size*64),
- (long)(100*canvas->zoom),
- (long)(100*canvas->zoom));
- if ( error ) break;
- pen_x = node->x * canvas->zoom;
- pen_y = node->y * canvas->zoom;
- slot = face->glyph;
-
- for(text=(unsigned char *)node->text;*text;text++) {
- previous = glyph_index;
- glyph_index = FT_Get_Char_Index( face, *text);
-
- if (use_kerning && previous && glyph_index){
- FT_Vector delta;
- FT_Get_Kerning( face, previous, glyph_index,
- 0, &delta );
- text_width += (double)delta.x / 64.0;
-
- }
- error = FT_Load_Glyph( face, glyph_index, 0 );
- if ( error ) break;
- if (previous == 0){
- pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */
- text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
- }
- if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
- text_height = (double)slot->metrics.horiBearingY / 64.0;
- }
- text_width += (double)slot->metrics.horiAdvance / 64.0;
- }
- text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
- text_width += (double)slot->metrics.width / 64.0; /* add just char width */
- text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
-
- switch(node->halign){
- 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;
- for(text=(unsigned char *)node->text;*text;text++) {
- int gr;
- previous = glyph_index;
- glyph_index = FT_Get_Char_Index( face, *text);
-
- if (use_kerning && previous && glyph_index){
- FT_Vector delta;
- FT_Get_Kerning( face, previous, glyph_index,
- 0, &delta );
- pen_x += (double)delta.x / 64.0;
-
- }
- error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
- if ( error ) break;
- gr = slot->bitmap.num_grays -1;
- for (iy=0; iy < slot->bitmap.rows; iy++){
- long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
- if (buf_y < 0 || buf_y >= pys_height) continue;
- buf_y *= rowstride;
- for (ix=0;ix < slot->bitmap.width;ix++){
- long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
- art_u8 font_alpha;
-
- if (buf_x < 0 || buf_x >= pys_width) continue;
- buf_x *= bytes_per_pixel ;
- font_alpha = *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
- font_alpha = (art_u8)((double)font_alpha / gr * falpha);
- for (iz = 0; iz < 3; iz++){
- art_u8 *orig = buffer + buf_y + buf_x + iz;
- *orig = (art_u8)((double)*orig / gr * ( gr - font_alpha) +
- (double)fcolor[iz] / gr * (font_alpha));
- }
- }
- }
- pen_x += (double)slot->metrics.horiAdvance / 64.0;
- }
- }
- }
- node = node->next;
- }
- gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
- art_free(buffer);
- FT_Done_FreeType( library );
- return 0;
-}
-
-/* free memory used by nodes this will also remove memory required for
- associated paths and svcs ... but not for text strings */
-int
-gfx_destroy (gfx_canvas_t *canvas){
- gfx_node_t *next,*node = canvas->firstnode;
- while(node){
- next = node->next;
- art_free(node->path);
- art_free(node->svp);
- free(node->text);
- free(node->filename);
- art_free(node);
- node = next;
- }
- return 0;
-}
-
-static int gfx_save_png (art_u8 *buffer, FILE *fp, long width, long height, long bytes_per_pixel){
- png_structp png_ptr = NULL;
- png_infop info_ptr = NULL;
- int i;
- png_bytep *row_pointers;
- int rowstride = width * bytes_per_pixel;
- png_text text[2];
-
- if (fp == NULL)
- return (1);
-
- png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
- if (png_ptr == NULL)
- {
- return (1);
- }
- row_pointers = (png_bytepp)png_malloc(png_ptr,
- height*sizeof(png_bytep));
-
- info_ptr = png_create_info_struct(png_ptr);
-
- if (info_ptr == NULL)
- {
- png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
- return (1);
- }
-
- if (setjmp(png_jmpbuf(png_ptr)))
- {
- /* If we get here, we had a problem writing the file */
- png_destroy_write_struct(&png_ptr, &info_ptr);
- return (1);
- }
-
- png_init_io(png_ptr, fp);
- png_set_IHDR (png_ptr, info_ptr,width, height,
- 8, PNG_COLOR_TYPE_RGB,
- PNG_INTERLACE_NONE,
- PNG_COMPRESSION_TYPE_DEFAULT,
- PNG_FILTER_TYPE_DEFAULT);
-
- text[0].key = "Software";
- text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
- text[0].compression = PNG_TEXT_COMPRESSION_NONE;
- png_set_text (png_ptr, info_ptr, text, 1);
-
- /* Write header data */
- png_write_info (png_ptr, info_ptr);
-
- for (i = 0; i < height; i++)
- row_pointers[i] = (png_bytep) (buffer + i*rowstride);
-
- png_write_image(png_ptr, row_pointers);
- png_write_end(png_ptr, info_ptr);
- 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";
-typedef struct svg_dash
-{
- int dash_enable;
- double dash_adjust, dash_len, dash_offset;
- double adjusted_on, adjusted_off;
-} svg_dash;
-
-
-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("</", fp);
- fputs(name, fp);
- } else {
- putc('/', fp);
- }
- svg_close_tag(fp);
-}
-
-static void svg_close_tag_empty_node(FILE *fp)
-{
- svg_end_tag(fp, NULL);
-}
-
-static void svg_write_text(FILE *fp, const char *text)
-{
- const unsigned char *p, *start, *last;
- unsigned int ch;
- p = (const unsigned char*)text;
- if (!p)
- return;
- /* trim leading spaces */
- while (*p == ' ')
- p++;
- start = p;
- /* trim trailing spaces */
- last = p - 1;
- while ((ch = *p) != 0) {
- if (ch != ' ')
- last = p;
- p++;
- }
- /* encode trimmed text */
- p = start;
- while (p <= last) {
- ch = *p++;
- ch = afm_host2unicode(ch); /* unsafe macro */
- switch (ch) {
- case '&': fputs("&", fp); break;
- case '<': fputs("<", fp); break;
- case '>': fputs(">", fp); break;
- case '"': fputs(""", fp); break;
- default:
- if (ch >= 127)
- fprintf(fp, "&#%d;", ch);
- else
- putc(ch, fp);
- }
- }
-}
-
-static void svg_format_number(char *buf, int bufsize, double d)
-{
- /* omit decimals if integer to reduce filesize */
- char *p;
- snprintf(buf, bufsize, "%.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;
- }
-}
-
-static void svg_write_number(FILE *fp, double d)
-{
- char buf[60];
- svg_format_number(buf, sizeof(buf), d);
- 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_get_dash(gfx_node_t *node, svg_dash *d)
-{
- double offset;
- int mult;
- if (node->dash_on <= 0 || node->dash_off <= 0) {
- d->dash_enable = 0;
- return;
- }
- d->dash_enable = 1;
- d->dash_len = node->dash_on + node->dash_off;
- /* dash on/off adjustment due to round caps */
- d->dash_adjust = 0.8 * node->size;
- d->adjusted_on = node->dash_on - d->dash_adjust;
- if (d->adjusted_on < 0.01)
- d->adjusted_on = 0.01;
- d->adjusted_off = d->dash_len - d->adjusted_on;
- /* dash offset calc */
- if (node->path[0].x == node->path[1].x) /* only good for horz/vert lines */
- offset = node->path[0].y;
- else
- offset = node->path[0].x;
- mult = (int)fabs(offset / d->dash_len);
- d->dash_offset = offset - mult * d->dash_len;
- if (node->path[0].x < node->path[1].x || node->path[0].y < node->path[1].y)
- d->dash_offset = d->dash_len - d->dash_offset;
-}
-
-static int svg_dash_equal(svg_dash *a, svg_dash *b)
-{
- if (a->dash_enable != b->dash_enable)
- return 0;
- if (a->adjusted_on != b->adjusted_on)
- return 0;
- if (a->adjusted_off != b->adjusted_off)
- return 0;
- /* rest of properties will be the same when on+off are */
- return 1;
-}
-
-static void svg_common_path_attributes(FILE *fp, gfx_node_t *node)