/*
* Grace - GRaphing, Advanced Computation and Exploration of data
*
* Home page: http://plasma-gate.weizmann.ac.il/Grace/
*
* Copyright (c) 1996-2002 Grace Development Team
*
* Maintained by Evgeny Stambulchik
*
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* Driver for the Scalable Vector Graphics Format from W3C
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "defines.h"
#include "utils.h"
#include "cmath.h"
#include "draw.h"
#include "graphs.h"
#include "device.h"
#include "devlist.h"
#include "patterns.h"
#include "svgdrv.h"
extern FILE *prstream;
static Device_entry dev_svg = {
DEVICE_FILE,
"SVG",
svginitgraphics,
NULL,
NULL,
"svg",
TRUE,
FALSE,
{DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT, 72.0},
NULL
};
typedef struct {
double side;
int *pattern_defined;
int *pattern_empty;
int *pattern_full;
int *colorfilter_defined;
int group_is_open;
double line_width;
Pen pen;
int fillrule;
int linecap;
int linejoin;
int linestyle;
int draw;
int fill;
} Svg_data;
static int init_svg_data(void)
{
Svg_data *data;
int i;
data = (Svg_data *) get_curdevice_data();
if (data == NULL) {
/* we need to perform the allocations */
data = (Svg_data *) xrealloc(NULL, sizeof(Svg_data));
if (data == NULL) {
return RETURN_FAILURE;
}
data->pattern_defined = NULL;
data->pattern_empty = NULL;
data->pattern_full = NULL;
data->colorfilter_defined = NULL;
set_curdevice_data((void *) data);
}
data->side = MIN2(page_width_pp, page_height_pp);
data->pattern_defined =
xrealloc(data->pattern_defined, number_of_patterns()*SIZEOF_INT);
data->pattern_empty =
xrealloc(data->pattern_empty, number_of_patterns()*SIZEOF_INT);
data->pattern_full =
xrealloc(data->pattern_full, number_of_patterns()*SIZEOF_INT);
for (i = 0; i < number_of_patterns(); i++) {
data->pattern_defined[i] = FALSE;
data->pattern_empty[i] = FALSE;
data->pattern_full[i] = FALSE;
}
svg_updatecmap();
data->group_is_open = FALSE;
data->line_width = 0.0;
data->pen.color = 0;
data->pen.pattern = 0;
data->fillrule = 0;
data->linecap = 0;
data->linejoin = 0;
data->linestyle = 0;
data->draw = FALSE;
data->fill = FALSE;
return RETURN_SUCCESS;
}
/*
* SVG conventions :
* Y coordinates increase downwards
* angles increase clockwise
*/
/*
* scale coordinates, using a SVG-viewer to do this gives rounding-problems
*/
static double scaleval (double val)
{
Svg_data *data;
data = get_curdevice_data();
return val*data->side;
}
int register_svg_drv(void)
{
return register_device(dev_svg);
}
void svg_updatecmap(void)
{
int i;
Svg_data *data;
data = (Svg_data *) get_curdevice_data();
if (data == NULL) {
return;
} else {
data->colorfilter_defined =
xrealloc(data->colorfilter_defined, number_of_colors()*SIZEOF_INT);
for (i = 0; i < number_of_colors(); i++) {
data->colorfilter_defined[i] = FALSE;
}
}
}
static void define_pattern(int i, int c, Svg_data *data)
{
int j, k, l;
fRGB *frgb;
double bg_red, bg_green, bg_blue;
if (data->pattern_defined[i] == TRUE && c < number_of_colors() && data->colorfilter_defined[c] == TRUE) {
return;
}
if (data->pattern_defined[i] != TRUE) {
/* testing if the pattern is either empty or full */
data->pattern_empty[i] = TRUE;
data->pattern_full[i] = TRUE;
for (j = 0; j < 32; j++) {
if (pat_bits[i][j] != 0x00) {
data->pattern_empty[i] = FALSE;
}
if (pat_bits[i][j] != 0xff) {
data->pattern_full[i] = FALSE;
}
}
}
if (data->pattern_empty[i] != TRUE && data->pattern_full[i] != TRUE) {
fprintf(prstream, " <defs>\n");
/* test if the pattern is already defined. */
if (data->pattern_defined[i] != TRUE) {
/* this is an horrible hack ! */
/* we define pixels as squares in vector graphics */
/* first fill the whole pattern */
fprintf(prstream,
" <pattern id=\"pattern%d\" viewBox=\"0 0 16 16\""
" width=\"%d\" height=\"%d\" patternUnits=\"userSpaceOnUse\">\n",
i, 16, 16);
fprintf(prstream," <rect fill=\"#FFFFFF\" x=\"0\" y=\"0\""
" width=\"16\" height=\"16\"/>\n");
for (j = 0; j < 256; j++) {
k = j/16;
l = j%16;
if ((pat_bits[i][j/8] >> (j%8)) & 0x01) {
/* the bit is set */
fprintf(prstream,
" <rect x=\"%d\" y=\"%d\""
" width=\"1\" height=\"1\"/>\n",
l, 15-k);
}
}
fprintf(prstream, " </pattern>\n");
data->pattern_defined[i] = TRUE;
}
/* test if the needed colorfilter is already defined. */
/* color-patterns can be drawn with black patterns and then
applying a colorfilter to change white to the background-color
and black to the patterncolor. */
if (c < number_of_colors() && data->colorfilter_defined[c] != TRUE) {
frgb = get_frgb(getbgcolor());
bg_red=frgb->red;
bg_green=frgb->green;
bg_blue=frgb->blue;
frgb = get_frgb(c);
fprintf(prstream, " <filter id=\"tocolor%d\" filterUnits=\"objectBoundingBox\"\n", c);
fprintf(prstream, " color-interpolation-filters=\"sRGB\" x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n");
fprintf(prstream, " <feComponentTransfer>\n");
fprintf(prstream, " <feFuncR type=\"discrete\" tableValues=\"%.6f %.6f\"/>\n",
frgb->red, bg_red);
fprintf(prstream, " <feFuncG type=\"discrete\" tableValues=\"%.6f %.6f\"/>\n",
frgb->green, bg_green);
fprintf(prstream, " <feFuncB type=\"discrete\" tableValues=\"%.6f %.6f\"/>\n",
frgb->blue, bg_blue);
fprintf(prstream, " </feComponentTransfer>\n");
fprintf(prstream, " </filter>\n");
data->colorfilter_defined[c] = TRUE;
}
fprintf(prstream, " </defs>\n");
}
}
/*
* escape special characters
*/
static char *escape_specials(unsigned char *s, int len)
{
static char *es = NULL;
int i, elen = 0;
elen = 0;
for (i = 0; i < len; i++) {
if (s[i] == '&') {
elen += 4;
} else if (s[i] == '<' || s[i] == '>') {
elen += 3;
}
elen++;
}
es = xrealloc(es, (elen + 1)*SIZEOF_CHAR);
elen = 0;
for (i = 0; i < len; i++) {
if (s[i] == '&') {
es[elen++] = '&';
es[elen++] = 'a';
es[elen++] = 'm';
es[elen++] = 'p';
es[elen++] = ';';
} else if (s[i] == '<') {
es[elen++] = '&';
es[elen++] = 'l';
es[elen++] = 't';
es[elen++] = ';';
} else if (s[i] == '>') {
es[elen++] = '&';
es[elen++] = 'g';
es[elen++] = 't';
es[elen++] = ';';
} else {
es[elen++] = (char) s[i];
}
}
es[elen] = '\0';
return (es);
}
int svginitgraphics(void)
{
/* device-dependent routines */
devupdatecmap = svg_updatecmap;
devdrawpixel = svg_drawpixel;
devdrawpolyline = svg_drawpolyline;
devfillpolygon = svg_fillpolygon;
devdrawarc = svg_drawarc;
devfillarc = svg_fillarc;
devputpixmap = svg_putpixmap;
devputtext = svg_puttext;
devleavegraphics = svg_leavegraphics;
if (init_svg_data() != RETURN_SUCCESS) {
return RETURN_FAILURE;
}
fprintf(prstream, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n");
fprintf(prstream, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"");
fprintf(prstream, " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
fprintf(prstream, "<!-- generated by %s -->\n", bi_version_string());
/* Let do the SVG-Viewer the conversion of coordinates. */
fprintf(prstream, "<svg xml:space=\"preserve\" ");
fprintf(prstream, "width=\"%.4fin\" height=\"%.4fin\" viewBox=\"%.4f %.4f %.4f %.4f\">\n",
page_width_in, page_height_in,
0.0, 0.0, page_width_pp, page_height_pp);
fprintf(prstream, " <g transform=\"translate(0,%.4f) scale(1,-1)\">\n",
page_height_pp);
/* project description */
if (get_project_description() != NULL) {
fprintf(prstream, " <desc>%s</desc>\n", get_project_description());
}
return RETURN_SUCCESS;
}
static void svg_group_props (int draw, int fill)
{
int i, needs_group;
double lw;
Pen pen;
int fillrule, linecap, linejoin, linestyle;
RGB *prgb;
int red, green, blue;
Svg_data *data;
data = (Svg_data *) get_curdevice_data();
/* do we need to redefine a group with new properties ? */
needs_group = (data->group_is_open == TRUE) ? FALSE : TRUE;
lw = scaleval(getlinewidth());
fillrule = getfillrule();
linecap = getlinecap();
linejoin = getlinejoin();
linestyle = getlinestyle();
if (fabs(lw - data->line_width) >= 1.0e-6*(1.0 + fabs(data->line_width))) {
needs_group = TRUE;
}
pen = getpen();
if ((pen.color != data->pen.color) || (pen.pattern != data->pen.pattern)) {
needs_group = TRUE;
}
if (fillrule != data->fillrule) {
needs_group = TRUE;
}
if (linecap != data->linecap) {
needs_group = TRUE;
}
if (linejoin != data->linejoin) {
needs_group = TRUE;
}
if (linestyle != data->linestyle) {
needs_group = TRUE;
}
if ((draw != data->draw) || (fill != data->fill)) {
needs_group = TRUE;
}
if (needs_group == TRUE) {
/* we need to write the characteristics of the group */
if (data->group_is_open == TRUE) {
/* first, we should close the preceding group */
fprintf(prstream, " </g>\n");
data->group_is_open = FALSE;
}
define_pattern(pen.pattern, pen.color, data);
prgb = get_rgb(pen.color);
if (prgb != NULL) {
red = prgb->red >> (GRACE_BPP - 8);
green = prgb->green >> (GRACE_BPP - 8);
blue = prgb->blue >> (GRACE_BPP - 8);
} else {
red = 0;
green = 0;
blue = 0;
}
if (fill && data->pattern_empty[pen.pattern] != TRUE) {
if (data->pattern_full[pen.pattern] == TRUE) {
fprintf(prstream, " <g style=\"fill:#%2.2X%2.2X%2.2X",
red, green, blue);
} else {
fprintf(prstream, " <g style=\"filter:url(#tocolor%d); ", pen.color);
fprintf(prstream, "fill:url(#pattern%d)", pen.pattern);
}
if (getfillrule() == FILLRULE_WINDING) {
fprintf(prstream, "; fill-rule:nonzero");
} else {
fprintf(prstream, "; fill-rule:evenodd");
}
} else {
fprintf(prstream, " <g style=\"fill:none");
}
if (draw) {
fprintf(prstream, "; stroke:#%2.2X%2.2X%2.2X", red, green, blue);
fprintf(prstream, "; stroke-width:%8.4f", lw);
switch (linecap) {
case LINECAP_BUTT :
fprintf(prstream, "; stroke-linecap:butt");
break;
case LINECAP_ROUND :
fprintf(prstream, "; stroke-linecap:round");
break;
case LINECAP_PROJ :
fprintf(prstream, "; stroke-linecap:square");
break;
default :
fprintf(prstream, "; stroke-linecap:inherit");
break;
}
switch (linejoin) {
case LINEJOIN_MITER :
fprintf(prstream, "; stroke-linejoin:miter");
break;
case LINEJOIN_ROUND :
fprintf(prstream, "; stroke-linejoin:round");
break;
case LINEJOIN_BEVEL :
fprintf(prstream, "; stroke-linejoin:bevel");
break;
default :
fprintf(prstream, "; stroke-linejoin:inherit");
break;
}
if (linestyle <= 1) {
fprintf(prstream, "; stroke-dasharray:none");
} else {
fprintf(prstream, "; stroke-dasharray:");
for (i = 0; i < dash_array_length[linestyle]; i++) {
fprintf(prstream,
" %d", (int) rint(lw*dash_array[linestyle][i]));
}
}
}
fprintf(prstream, "\">\n");
data->group_is_open = TRUE;
data->line_width = lw;
data->pen = pen;
data->fillrule = fillrule;
data->linecap = linecap;
data->linejoin = linejoin;
data->linestyle = linestyle;
data->draw = draw;
data->fill = fill;
}
}
void svg_drawpixel(VPoint vp)
{
svg_group_props(FALSE, TRUE);
fprintf(prstream,
" <rect x=\"%.4f\" y=\"%.4f\" width=\"%.4f\" height=\"%.4f\"/>\n",
scaleval(vp.x), scaleval(vp.y),
scaleval(1.0), scaleval(1.0));
}
void svg_drawpolyline(VPoint *vps, int n, int mode)
{
int i;
if (n <= 0) {
return;
}
svg_group_props(TRUE, FALSE);
fprintf(prstream, " <path d=\"M%.4f,%.4f",
scaleval(vps[0].x), scaleval(vps[0].y));
for (i = 1; i < n; i++) {
if (i%10 == 0) {
fprintf(prstream, "\n ");
}
fprintf(prstream, "L%.4f,%.4f", scaleval(vps[i].x), scaleval(vps[i].y));
}
if (mode == POLYLINE_CLOSED) {
fprintf(prstream, "z\"/>\n");
} else {
fprintf(prstream, "\"/>\n");
}
}
void svg_fillpolygon(VPoint *vps, int nc)
{
int i;
if (nc <= 0) {
return;
}
svg_group_props(FALSE, TRUE);
fprintf(prstream, " <path d=\"M%.4f,%.4f",
scaleval(vps[0].x), scaleval(vps[0].y));
for (i = 1; i < nc; i++) {
if (i%10 == 0) {
fprintf(prstream, "\n ");
}
fprintf(prstream, "L%.4f,%.4f", scaleval(vps[i].x), scaleval(vps[i].y));
}
fprintf(prstream, "z\"/>\n");
}
void svg_drawarc(VPoint vp1, VPoint vp2, int a1, int a2)
{
VPoint center;
double rx, ry;
if (a1 == a2) {
return;
}
center.x = 0.5*(vp1.x + vp2.x);
center.y = 0.5*(vp1.y + vp2.y);
rx = 0.5*fabs(vp2.x - vp1.x);
ry = 0.5*fabs(vp2.y - vp1.y);
svg_group_props(TRUE, FALSE);
if ((a1 - a2)%360 == 0) {
fprintf(prstream,
" <ellipse rx=\"%.4f\" ry=\"%.4f\" cx=\"%.4f\" cy=\"%.4f\"/>\n",
scaleval(rx), scaleval(ry),
scaleval(center.x), scaleval(center.y));
} else {
VPoint start, end;
start.x = center.x + rx*cos((M_PI/180.0)*a1);
start.y = center.y + ry*sin((M_PI/180.0)*a1);
end.x = center.x + rx*cos((M_PI/180.0)*a2);
end.y = center.y + ry*sin((M_PI/180.0)*a2);
fprintf(prstream,
" <path d=\"M%.4f, %.4fA%.4f, %.4f %d %d %d %.4f, %.4f\"/>\n",
scaleval(start.x), scaleval(start.y),
scaleval(rx), scaleval(ry),
0,
(abs(a2 - a1) > 180) ? 1 : 0,
(a2 > a1) ? 1 : 0,
scaleval(end.x), scaleval(end.y));
}
}
void svg_fillarc(VPoint vp1, VPoint vp2, int a1, int a2, int mode)
{
VPoint center;
double rx, ry;
if (a1 == a2) {
return;
}
center.x = 0.5*(vp1.x + vp2.x);
center.y = 0.5*(vp1.y + vp2.y);
rx = 0.5*fabs(vp2.x - vp1.x);
ry = 0.5*fabs(vp2.y - vp1.y);
svg_group_props(FALSE, TRUE);
if ((a1 - a2)%360 == 0) {
fprintf(prstream,
" <ellipse rx=\"%.4f\" ry=\"%.4f\" cx=\"%.4f\" cy=\"%.4f\"/>\n",
scaleval(rx), scaleval(ry),
scaleval(center.x), scaleval(center.y));
} else {
VPoint start, end;
start.x = center.x + rx*cos((M_PI/180.0)*a1);
start.y = center.y + ry*sin((M_PI/180.0)*a1);
end.x = center.x + rx*cos((M_PI/180.0)*a2);
end.y = center.y + ry*sin((M_PI/180.0)*a2);
if (mode == ARCFILL_CHORD) {
fprintf(prstream,
" <path d=\"M%.4f, %.4fA%.4f, %.4f %d %d %d %.4f, %.4fz\"/>\n",
scaleval(start.x), scaleval(start.y),
scaleval(rx), scaleval(ry),
0,
(abs(a2 - a1) > 180) ? 1 : 0,
(a2 > a1) ? 1 : 0,
scaleval(end.x), scaleval(end.y));
} else {
fprintf(prstream,
" <path d=\"M%.4f,%.4fL%.4f,%.4fA%.4f,%.4f %d %d %d %.4f,%.4fz\"/>\n",
scaleval(center.x), scaleval(center.y),
scaleval(start.x), scaleval(start.y),
scaleval(rx), scaleval(ry),
0,
(abs(a2 - a1) > 180) ? 1 : 0,
(a2 > a1) ? 1 : 0,
scaleval(end.x), scaleval(end.y));
}
}
}
void svg_putpixmap(VPoint vp, int width, int height, char *databits,
int pixmap_bpp, int bitmap_pad, int pixmap_type)
{
/* not implemented yet */
}
void svg_puttext(VPoint vp, char *s, int len, int font,
TextMatrix *tm, int underline, int overline, int kerning)
{
char *fontalias, *fontfullname, *fontweight;
char *dash, *family, *familyff;
double fsize = scaleval(1);
svg_group_props(FALSE, TRUE);
fprintf(prstream, " <text ");
fontalias = get_fontalias(font);
fontfullname = get_fontfullname(font);
family = NULL;
if ((dash = strchr(fontalias, '-')) == NULL) {
family = copy_string(family, fontalias);
} else {
family = xrealloc(family, dash - fontalias + 1);
strncpy(family, fontalias, dash - fontalias);
family[dash - fontalias] = '\0';
}
fprintf(prstream, " style=\"font-family:'%s'", family);
familyff=get_fontfamilyname(font);
if (strcmp(family,familyff) != 0){
fprintf(prstream, ",'%s'",familyff);
}
copy_string(family, NULL);
if (get_italic_angle(font) != 0) {
if ((strstr(fontfullname, "Obliqued") != NULL) ||
(strstr(fontfullname, "Oblique") != NULL) ||
(strstr(fontfullname, "Upright") != NULL) ||
(strstr(fontfullname, "Kursiv") != NULL) ||
(strstr(fontfullname, "Cursive") != NULL) ||
(strstr(fontfullname, "Slanted") != NULL) ||
(strstr(fontfullname, "Inclined") != NULL)) {
fprintf(prstream, "; font-style:oblique");
} else {
fprintf(prstream, "; font-style:italic");
}
} else {
fprintf(prstream, "; font-style:normal");
}
fontweight=get_fontweight(font);
if ((strstr(fontweight, "UltraLight") != NULL) ||
(strstr(fontweight, "ExtraLight") != NULL)) {
fprintf(prstream, "; font-weight:100");
} else if ((strstr(fontweight, "SemiLight") != NULL) ||
(strstr(fontweight, "Thin") != NULL)) {
fprintf(prstream, "; font-weight:200");
} else if (strstr(fontweight, "Light") != NULL) {
fprintf(prstream, "; font-weight:300");
} else if (strstr(fontweight, "Book") != NULL) {
fprintf(prstream, "; font-weight:500");
} else if (strstr(fontweight, "miBold") != NULL) {
fprintf(prstream, "; font-weight:600");
} else if ((strstr(fontweight, "ExtraBold") != NULL) ||
(strstr(fontweight, "Heavy") != NULL) ||
(strstr(fontweight, "UltraBold") != NULL)) {
fprintf(prstream, "; font-weight:800");
} else if (strstr(fontweight, "Bold") != NULL) {
fprintf(prstream, "; font-weight:bold");
} else if ((strstr(fontweight, "ExtraBlack") != NULL) ||
(strstr(fontweight, "Ultra") != NULL)) {
fprintf(prstream, "; font-weight:900");
} else if (strstr(fontweight, "Black") != NULL) {
fprintf(prstream, "; font-weight:800");
} else {
fprintf(prstream, "; font-weight:normal");
}
if ((strstr(fontfullname, "UltraCompressed") != NULL) ||
(strstr(fontfullname, "UltraCondensed") != NULL)) {
fprintf(prstream, "; font-stretch:ultra-condensed");
} else if ((strstr(fontfullname, "ExtraCompressed") != NULL) ||
(strstr(fontfullname, "ExtraCondensed") != NULL)) {
fprintf(prstream, "; font-stretch:extra-condensed");
} else if ((strstr(fontfullname, "SemiCondensed") != NULL) ||
(strstr(fontfullname, "Narrow") != NULL)) {
fprintf(prstream, "; font-stretch:semi-condensed");
} else if (strstr(fontfullname, "Condensed") != NULL) {
fprintf(prstream, "; font-stretch:condensed");
} else if ((strstr(fontfullname, "Wide") != NULL) ||
(strstr(fontfullname, "Poster") != NULL) ||
(strstr(fontfullname, "SemiExpanded") != NULL)) {
fprintf(prstream, "; font-stretch:semi-expanded");
} else if ((strstr(fontfullname, "ExtraExpanded") != NULL) ||
(strstr(fontfullname, "ExtraExtended") != NULL)) {
fprintf(prstream, "; font-stretch:extra-expanded");
} else if ((strstr(fontfullname, "UltraExpanded") != NULL) ||
(strstr(fontfullname, "UltraExtended") != NULL)) {
fprintf(prstream, "; font-stretch:ultra-expanded");
} else if ((strstr(fontfullname, "Expanded") != NULL) ||
(strstr(fontfullname, "Extended") != NULL)) {
fprintf(prstream, "; font-stretch:expanded");
}
fprintf(prstream, "; font-size:%.4f", fsize);
if (underline == TRUE) {
if (overline == TRUE) {
fprintf(prstream, "; text-decoration:underline|overline");
} else {
fprintf(prstream, "; text-decoration:underline");
}
} else {
if (overline == TRUE) {
fprintf(prstream, "; text-decoration:overline");
}
}
fprintf(prstream, "\" transform=\"matrix(%.4f,%.4f,%.4f,%.4f,%.4f,%.4f)\">",
tm->cxx, tm->cyx,
-tm->cxy, -tm->cyy,
scaleval(vp.x), scaleval(vp.y));
fprintf(prstream, escape_specials((unsigned char *) s, len));
fprintf(prstream, "</text>\n");
}
void svg_leavegraphics(void)
{
Svg_data *data;
data = (Svg_data *) get_curdevice_data();
if (data->group_is_open == TRUE) {
fprintf(prstream, " </g>\n");
data->group_is_open = FALSE;
}
fprintf(prstream, " </g>\n");
fprintf(prstream, "</svg>\n");
}
syntax highlighted by Code2HTML, v. 0.9.1