diff options
Diffstat (limited to 'Build/source/texk/web2c/mplibdir/svgout.w')
-rw-r--r-- | Build/source/texk/web2c/mplibdir/svgout.w | 1343 |
1 files changed, 1343 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mplibdir/svgout.w b/Build/source/texk/web2c/mplibdir/svgout.w new file mode 100644 index 00000000000..94801da5205 --- /dev/null +++ b/Build/source/texk/web2c/mplibdir/svgout.w @@ -0,0 +1,1343 @@ +% Copyright 2008-2009 Taco Hoekwater. +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU Lesser General Public License as published by +% the Free Software Foundation, either version 3 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 Lesser General Public License for more details. +% +% You should have received a copy of the GNU Lesser General Public License +% along with this program. If not, see <http://www.gnu.org/licenses/>. +% +% TeX is a trademark of the American Mathematical Society. +% METAFONT is a trademark of Addison-Wesley Publishing Company. +% PostScript is a trademark of Adobe Systems Incorporated. + +% Here is TeX material that gets inserted after \input webmac + +\font\tenlogo=logo10 % font used for the METAFONT logo +\font\logos=logosl10 +\def\MF{{\tenlogo META}\-{\tenlogo FONT}} +\def\MP{{\tenlogo META}\-{\tenlogo POST}} +\def\<#1>{$\langle#1\rangle$} +\def\section{\mathhexbox278} +\def\[#1]{} % from pascal web +\def\(#1){} % this is used to make section names sort themselves better +\def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@> + +\def\title{MetaPost SVG output} +\def\topofcontents{\hsize 5.5in + \vglue -30pt plus 1fil minus 1.5in + \def\?##1]{\hbox to 1in{\hfil##1.\ }} + } +\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in} +\pdfoutput=1 +\pageno=3 + +@ +@d zero_t ((math_data *)mp->math)->zero_t +@d number_zero(A) (((math_data *)(mp->math))->equal)(A,zero_t) +@d number_greater(A,B) (((math_data *)(mp->math))->greater)(A,B) +@d number_positive(A) number_greater(A, zero_t) +@d number_to_scaled(A) (((math_data *)(mp->math))->to_scaled)(A) +@d round_unscaled(A) (((math_data *)(mp->math))->round_unscaled)(A) +@d true 1 +@d false 0 +@d null_font 0 +@d null 0 +@d unity 1.0 +@d incr(A) (A)=(A)+1 /* increase a variable by unity */ +@d decr(A) (A)=(A)-1 /* decrease a variable by unity */ +@d negate(A) (A)=-(A) /* change the sign of a variable */ + +@c +#include <w2c/config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "mplib.h" +#include "mplibps.h" /* external header */ +#include "mplibsvg.h" /* external header */ +#include "mpmp.h" /* internal header */ +#include "mppsout.h" /* internal header */ +#include "mpsvgout.h" /* internal header */ +#include "mpmath.h" /* internal header */ +@h +@<Types in the outer block@> +@<Declarations@> + +@ There is a small bit of code from the backend that bleads through +to the frontend because I do not know how to set up the includes +properly. That is |typedef struct svgout_data_struct * svgout_data|. + +@ @(mpsvgout.h@>= +#ifndef MPSVGOUT_H +#define MPSVGOUT_H 1 +#include "mplib.h" +#include "mpmp.h" +#include "mplibps.h" +typedef struct svgout_data_struct { + @<Globals@> +} svgout_data_struct ; +@<Exported function headers@> +#endif + +@ @<Exported function headers@>= +void mp_svg_backend_initialize (MP mp) ; +void mp_svg_backend_free (MP mp) ; + +@ @c +void mp_svg_backend_initialize (MP mp) { + mp->svg = mp_xmalloc(mp,1,sizeof(svgout_data_struct)); + @<Set initial values@>; +} +void mp_svg_backend_free (MP mp) { + mp_xfree(mp->svg->buf); + mp_xfree(mp->svg); + mp->svg = NULL; +} + +@ Writing to SVG files + +This variable holds the number of characters on the current SVG file +line. It could also be a boolean because right now the only interesting +thing it does is keep track of whether or not we are start-of-line. + +@<Globals@>= +size_t file_offset; + +@ @<Set initial values@>= +mp->svg->file_offset = 0; + +@ Print a newline. + +@c +static void mp_svg_print_ln (MP mp) { + (mp->write_ascii_file)(mp,mp->output_file,"\n"); + mp->svg->file_offset=0; +} + +@ Print a single character. + +@c +static void mp_svg_print_char (MP mp, int s) { + char ss[2]; + ss[0]=(char)s; ss[1]=0; + (mp->write_ascii_file)(mp,mp->output_file,(char *)ss); + mp->svg->file_offset ++; +} + +@ Print a string. + +In PostScript, this used to be done in terms of |mp_svg_print_char|, +but that is very expensive (in other words: slow). It should be ok +to print whole strings here because line length of an XML file should +not be an issue to any respectable XML processing tool. + +@c +static void mp_svg_print (MP mp, const char *ss) { + (mp->write_ascii_file)(mp,mp->output_file,ss); + mp->svg->file_offset += strlen(ss); +} + + +@ The procedure |print_nl| is like |print|, but it makes sure that the +string appears at the beginning of a new line. + +@c +static void mp_svg_print_nl (MP mp, const char *s) { + if ( mp->svg->file_offset>0 ) + mp_svg_print_ln(mp); + mp_svg_print(mp, s); +} + + +@ Many of the printing routines use a print buffer to store partial +strings in before feeding the attribute value to |mp_svg_attribute|. + +@<Globals...@>= +char *buf; +unsigned loc; +unsigned bufsize; + +@ Start with a modest size of 256. the buffer will grow automatically +when needed. + +@<Set initial values@>= +mp->svg->loc = 0; +mp->svg->bufsize = 256; +mp->svg->buf = mp_xmalloc(mp,mp->svg->bufsize,1); +memset(mp->svg->buf,0,256); + + +@ How to append a character or a string of characters to +the end of the buffer. + +@d append_char(A) do { + if (mp->svg->loc==(mp->svg->bufsize-1)) { + char *buffer; + unsigned l; + l = (unsigned)(mp->svg->bufsize+(mp->svg->bufsize>>4)); + if (l>(0x3FFFFFF)) { + mp_confusion(mp,"svg buffer size"); + } + buffer = mp_xmalloc(mp,l,1); + memset (buffer,0,l); + memcpy(buffer,mp->svg->buf,(size_t)mp->svg->bufsize); + mp_xfree(mp->svg->buf); + mp->svg->buf = buffer ; + mp->svg->bufsize = l; + } + mp->svg->buf[mp->svg->loc++] = (A); +} while (0) + +@d append_string(A) do { + const char *ss = (A); + while (*ss != '\0') { append_char(*ss); ss++ ;} +} while (0) + +@ This function resets the buffer in preparation of the next string. +The |memset| is an easy way to make sure that the old string is +forgotten completely and that the new string will be zero-terminated. + +@c +static void mp_svg_reset_buf(MP mp) { + mp->svg->loc = 0; + memset (mp->svg->buf,0,mp->svg->bufsize); +} + +@ Printing the buffer is a matter of printing its string, then +it is reset. + +@c +static void mp_svg_print_buf (MP mp) { + mp_svg_print(mp, (char *)mp->svg->buf); + mp_svg_reset_buf(mp); +} + +@ The following procedure, which stores the decimal representation of +a given integer |n| in the buffer, has been written carefully so that +it works properly if |n=0| or if |(-n)| would cause overflow. + +@c +static void mp_svg_store_int (MP mp, integer n) { + unsigned char dig[23]; /* digits in a number, for rounding */ + integer m; /* used to negate |n| in possibly dangerous cases */ + int k = 0; /* index to current digit; we assume that $|n|<10^{23}$ */ + if ( n<0 ) { + append_char('-'); + if ( n>-100000000 ) { + negate(n); + } else { + m=-1-n; n=m / 10; m=(m % 10)+1; k=1; + if ( m<10 ) { + dig[0]=(unsigned char)m; + } else { + dig[0]=0; incr(n); + } + } + } + do { + dig[k]=(unsigned char)(n % 10); n=n / 10; incr(k); + } while (n!=0); + /* print the digits */ + while ( k-->0 ){ + append_char((char)('0'+dig[k])); + } +} + +@ \MP\ also makes use of a trivial procedure to output two digits. The +following subroutine is usually called with a parameter in the range |0<=n<=99|, +but the assignments makes sure that only the two least significant digits +are printed, just in case. + +@c +static void mp_svg_store_dd (MP mp,integer n) { + char nn=(char)abs(n) % 100; + append_char((char)('0'+(nn / 10))); + append_char((char)('0'+(nn % 10))); +} + +@ Conversely, here is a procedure analogous to |mp_svg_store_int|. +A decimal point is printed only if the value is not an integer. If +there is more than one way to print the result with the optimum +number of digits following the decimal point, the closest possible +value is given. + +The invariant relation in the \&{do while} loop is that a sequence of +decimal digits yet to be printed will yield the original number if and only if +they form a fraction~$f$ in the range $s-\delta\L10\cdot2^{16}f<s$. +We can stop if and only if $f=0$ satisfies this condition; the loop will +terminate before $s$ can possibly become zero. + +@c +static void mp_svg_store_double (MP mp, double s) { + char *value, *c; + value = mp_xmalloc(mp,1,32); + mp_snprintf(value,32,"%f", s); + c = value; + while (*c) { + append_char(*c); + c++; + } + free(value); +} + + +@ Output XML tags. + +In order to create a nicely indented output file, the current tag +nesting level needs to be remembered. + +@<Globals...@>= +int level; + +@ @<Set initial values@>= +mp->svg->level = 0; + +@ Output an XML start tag. + +Because start tags may carry attributes, this happens in two steps. +The close function is trivial of course, but it looks nicer in the source. + +@d mp_svg_starttag(A,B) { mp_svg_open_starttag (A,B); mp_svg_close_starttag(A); } + +@c +static void mp_svg_open_starttag (MP mp, const char *s) { + int l = mp->svg->level * 2; + mp_svg_print_ln(mp); + while (l-->0) { + append_char(' '); + } + append_char('<'); + append_string(s); + mp_svg_print_buf(mp); + mp->svg->level++; +} +static void mp_svg_close_starttag (MP mp) { + mp_svg_print_char(mp,'>'); +} + +@ Output an XML end tag. + +If the |indent| is true, then the end tag will appear on the next line +of the SVG file, correctly indented for the current XML nesting +level. If it is false, the end tag will appear immediatelu after the +preceding output. + +@c +static void mp_svg_endtag (MP mp, const char *s, boolean indent) { + mp->svg->level--; + if (indent) { + int l = mp->svg->level * 2; + mp_svg_print_ln(mp); + while (l-->0) { + append_char(' '); + } + } + append_string("</"); + append_string(s); + append_char('>'); + mp_svg_print_buf(mp); +} + +@ Attribute. Can't play with the buffer here becase it is likely +that that is the |v| argument. + +@c +static void mp_svg_attribute (MP mp, const char *s, const char *v) { + mp_svg_print_char(mp, ' '); + mp_svg_print(mp, s); + mp_svg_print(mp,"=\""); + mp_svg_print(mp, v); + mp_svg_print_char(mp,'"'); +} + +@ This is a test to filter out characters that are illegal in XML. + +@<Character |k| is illegal in SVG output@>= + (k<=0x8)||(k==0xB)||(k==0xC)||(k>=0xE && k<=0x1F)|| + (k>=0x7F && k<=0x84)||(k>=0x86 && k<=0x9F) + + +@ This is test is used to switch between direct representation of characters +and character references. Just in case the input string is UTF-8, allow everything +except the characters that have to be quoted for XML well-formedness. + +@<Character |k| is not allowed in SVG output@>= + (k=='&')||(k=='>')||(k=='<') + +@ We often need to print a pair of coordinates. + +Because of bugs in svg rendering software, it is necessary to +change the point coordinates so that there are all in the "positive" +quadrant of the SVG field. This means an shift and a vertical flip. + +The two correction values are calculated by the function that writes +the initial |<svg>| tag, and are stored in two globals: + +@<Globals@>= +integer dx; +integer dy; + +@ @c +void mp_svg_pair_out (MP mp,double x, double y) { + mp_svg_store_double(mp, (x+mp->svg->dx)); + append_char(' '); + mp_svg_store_double(mp, (-(y+mp->svg->dy))); +} + +@ @<Declarations@>= +void mp_svg_font_pair_out (MP mp,double x, double y) ; + +@ @c +void mp_svg_font_pair_out (MP mp,double x, double y) { + mp_svg_store_double(mp, (x)); + append_char(' '); + mp_svg_store_double(mp, -(y)); +} + +@ When stroking a path with an elliptical pen, it is necessary to distort +the path such that a circular pen can be used to stroke the path. The path +itself is wrapped in another transformation to restore the points to their +correct location (but now with a modified pen stroke). + +Because all the points in the path need fixing, it makes sense to +have a specific helper to write such distorted pairs of coordinates out. + +@<Declarations@>= +void mp_svg_trans_pair_out (MP mp, mp_pen_info *pen, double x, double y) ; + +@ @c +void mp_svg_trans_pair_out (MP mp, mp_pen_info *pen, double x, double y) { + double sx,sy, rx,ry, px, py, retval, divider; + sx = (pen->sx); + sy = (pen->sy); + rx = (pen->rx); + ry = (pen->ry); + px = ((x+mp->svg->dx)); + py = ((-(y+mp->svg->dy))); + divider = (sx*sy - rx*ry); + retval = (sy*px-ry*py)/divider; + mp_svg_store_double(mp, (retval)); + append_char(' '); + retval = (sx*py-rx*px)/divider; + mp_svg_store_double(mp, (retval)); +} + + + +@ @<Declarations@>= +static void mp_svg_pair_out (MP mp,double x, double y) ; + +@ +@<Declarations@>= +static void mp_svg_print_initial_comment(MP mp,mp_edge_object *hh); + +@ @c +void mp_svg_print_initial_comment(MP mp,mp_edge_object *hh) { + double tx, ty; + @<Print the MetaPost version and time @>; + mp_svg_open_starttag(mp,"svg"); + mp_svg_attribute(mp,"version", "1.1"); + mp_svg_attribute(mp,"xmlns", "http://www.w3.org/2000/svg"); + mp_svg_attribute(mp, "xmlns:xlink", "http://www.w3.org/1999/xlink"); + if ( hh->minx>hh->maxx) { + tx = 0; + ty = 0; + mp->svg->dx = 0; + mp->svg->dy = 0; + } else { + tx = (hh->minx<0 ? -hh->minx : 0) + hh->maxx; + ty = (hh->miny<0 ? -hh->miny : 0) + hh->maxy; + mp->svg->dx = (hh->minx<0 ? -hh->minx : 0); + mp->svg->dy = (hh->miny<0 ? -hh->miny : 0) - ty; + } + mp_svg_store_double(mp, tx); + mp_svg_attribute(mp,"width", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_store_double(mp, ty); + mp_svg_attribute(mp,"height", mp->svg->buf); + mp_svg_reset_buf(mp); + append_string("0 0 "); mp_svg_store_double(mp,tx); + append_char(' '); mp_svg_store_double(mp,ty); + mp_svg_attribute(mp,"viewBox", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_close_starttag(mp); + mp_svg_print_nl(mp,"<!-- Original BoundingBox: "); + mp_svg_store_double(mp, hh->minx); append_char(' '); + mp_svg_store_double(mp, hh->miny); append_char(' '); + mp_svg_store_double(mp, hh->maxx); append_char(' '); + mp_svg_store_double(mp, hh->maxy); + mp_svg_print_buf(mp); + mp_svg_print(mp," -->"); +} + +@ @<Print the MetaPost version and time @>= +{ + char *s; + int tt; /* scaled */ + mp_svg_print_nl(mp, "<!-- Created by MetaPost "); + s = mp_metapost_version(); + mp_svg_print(mp, s); + mp_xfree(s); + mp_svg_print(mp, " on "); + mp_svg_store_int(mp, round_unscaled(internal_value(mp_year))); + append_char('.'); + mp_svg_store_dd(mp, round_unscaled(internal_value(mp_month))); + append_char('.'); + mp_svg_store_dd(mp, round_unscaled(internal_value(mp_day))); + append_char(':'); + tt=round_unscaled(internal_value(mp_time)); + mp_svg_store_dd(mp, tt / 60); + mp_svg_store_dd(mp, tt % 60); + mp_svg_print_buf(mp); + mp_svg_print(mp, " -->"); +} + + +@ Outputting a color specification. + +@d set_color_objects(pq) + object_color_model = pq->color_model; + object_color_a = pq->color.a_val; + object_color_b = pq->color.b_val; + object_color_c = pq->color.c_val; + object_color_d = pq->color.d_val; + +@c +static void mp_svg_color_out (MP mp, mp_graphic_object *p) { + int object_color_model; + double object_color_a, object_color_b, object_color_c, object_color_d ; + if (gr_type(p) == mp_fill_code) { + mp_fill_object *pq = (mp_fill_object *)p; + set_color_objects(pq); + } else if (gr_type(p) == mp_stroked_code) { + mp_stroked_object *pq = (mp_stroked_object *)p; + set_color_objects(pq); + } else { + mp_text_object *pq = (mp_text_object *)p; + set_color_objects(pq); + } + if ( object_color_model==mp_no_model ) { + append_string("black"); + } else { + if ( object_color_model==mp_grey_model ) { + object_color_b = object_color_a; + object_color_c = object_color_a; + } else if ( object_color_model==mp_cmyk_model) { + int c,m,y,k; + c = object_color_a; + m = object_color_b; + y = object_color_c; + k = object_color_d; + object_color_a = unity - (c+k>unity ? unity : c+k); + object_color_b = unity - (m+k>unity ? unity : m+k); + object_color_c = unity - (y+k>unity ? unity : y+k); + } + append_string("rgb("); + mp_svg_store_double(mp, (object_color_a * 100)); + append_char('%'); + append_char(','); + mp_svg_store_double(mp, (object_color_b * 100)); + append_char('%'); + append_char(','); + mp_svg_store_double(mp, (object_color_c * 100)); + append_char('%'); + append_char(')'); + } +} + +@ @<Declarations@>= +static void mp_svg_color_out (MP mp, mp_graphic_object *p); + +@ This is the information that comes from a pen + +@<Types...@>= +typedef struct mp_pen_info { + double tx, ty; + double sx, rx, ry, sy; + double ww; +} mp_pen_info; + + +@ (Re)discover the characteristics of an elliptical pen + +@<Declarations@>= +mp_pen_info *mp_svg_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p); + +@ The next two constants come from the original web source. +Together with the two helper functions, they will tell whether +the |x| or the |y| direction of the path is the most important + +@d aspect_bound (10/65536.0) +@d aspect_default 1 + +@c +static double coord_range_x (mp_gr_knot h, double dz) { + double z; + double zlo = 0, zhi = 0; + mp_gr_knot f = h; + while (h != NULL) { + z = gr_x_coord(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_right_x(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_left_x(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + h = gr_next_knot(h); + if (h==f) + break; + } + return (zhi - zlo <= dz ? aspect_bound : aspect_default); +} +static double coord_range_y (mp_gr_knot h, double dz) { + double z; + double zlo = 0, zhi = 0; + mp_gr_knot f = h; + while (h != NULL) { + z = gr_y_coord(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_right_y(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_left_y(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + h = gr_next_knot(h); + if (h==f) + break; + } + return (zhi - zlo <= dz ? aspect_bound : aspect_default); +} + +@ +@c +mp_pen_info *mp_svg_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p) { + double wx, wy; /* temporary pen widths, in either direction */ + struct mp_pen_info *pen; /* return structure */ + if (p == NULL) + return NULL; + pen = mp_xmalloc(mp, 1, sizeof(mp_pen_info)); + pen->rx = unity; + pen->ry = unity; + pen->ww = unity; + if ((gr_right_x(p) == gr_x_coord(p)) + && + (gr_left_y(p) == gr_y_coord(p))) { + wx = fabs(gr_left_x(p) - gr_x_coord(p)); + wy = fabs(gr_right_y(p) - gr_y_coord(p)); + } else { + double a,b; + a = gr_left_x(p)-gr_x_coord(p); + b = gr_right_x(p)-gr_x_coord(p); + wx = sqrt(a*a + b*b); + a = gr_left_y(p)-gr_y_coord(p); + b = gr_right_y(p)-gr_y_coord(p); + wy = sqrt(a*a + b*b); + } + if ((wy/coord_range_x(pp, wx)) >= (wx/coord_range_y(pp, wy))) + pen->ww = wy; + else + pen->ww = wx; + pen->tx = gr_x_coord(p); + pen->ty = gr_y_coord(p); + pen->sx = gr_left_x(p) - pen->tx; + pen->rx = gr_left_y(p) - pen->ty; + pen->ry = gr_right_x(p) - pen->tx; + pen->sy = gr_right_y(p) - pen->ty; + if (pen->ww != unity) { + if (pen->ww == 0) { + pen->sx = unity; + pen->sy = unity; + } else { + /* this negation is needed because the svg coordinate system differs + from postscript's. */ + pen->rx = -(pen->rx / pen->ww); + pen->ry = -(pen->ry / pen->ww); + pen->sx = pen->sx / pen->ww; + pen->sy = pen->sy / pen->ww; + } + } + return pen; +} + +@ Two types of straight lines come up often in \MP\ paths: +cubics with zero initial and final velocity as created by |make_path| or +|make_envelope|, and cubics with control points uniformly spaced on a line +as created by |make_choices|. + +@<Declarations@>= +static boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) ; + + +@ +@d bend_tolerance (131/65536.0) /* allow rounding error of $2\cdot10^{-3}$ */ + +@c +boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) { + double d; /* a temporary value */ + if ( gr_right_x(p)==gr_x_coord(p) ) + if ( gr_right_y(p)==gr_y_coord(p) ) + if ( gr_left_x(q)==gr_x_coord(q) ) + if ( gr_left_y(q)==gr_y_coord(q) ) + return false; + d=gr_left_x(q)-gr_right_x(p); + if ( fabs(gr_right_x(p)-gr_x_coord(p)-d)<=bend_tolerance ) + if ( fabs(gr_x_coord(q)-gr_left_x(q)-d)<=bend_tolerance ) { + d=gr_left_y(q)-gr_right_y(p); + if ( fabs(gr_right_y(p)-gr_y_coord(p)-d)<=bend_tolerance ) + if ( fabs(gr_y_coord(q)-gr_left_y(q)-d)<=bend_tolerance ) + return false; + } + return true; +} + + +@ @c +static void mp_svg_path_out (MP mp, mp_gr_knot h) { + mp_gr_knot p, q; /* for scanning the path */ + append_char('M'); + mp_svg_pair_out(mp, gr_x_coord(h),gr_y_coord(h)); + p=h; + do { + if ( gr_right_type(p)==mp_endpoint ) { + if ( p==h ) { + append_string("l0 0"); + } + return; + } + q=gr_next_knot(p); + if (mp_is_curved(p, q)){ + append_char('C'); + mp_svg_pair_out(mp, gr_right_x(p),gr_right_y(p)); + append_char(','); + mp_svg_pair_out(mp, gr_left_x(q),gr_left_y(q)); + append_char(','); + mp_svg_pair_out(mp, gr_x_coord(q),gr_y_coord(q)); + } else if ( q!=h ){ + append_char('L'); + mp_svg_pair_out(mp, gr_x_coord(q),gr_y_coord(q)); + } + p=q; + } while (p!=h); + append_char('Z'); + append_char(0); +} + +@ @c +static void mp_svg_path_trans_out (MP mp, mp_gr_knot h, mp_pen_info *pen) { + mp_gr_knot p, q; /* for scanning the path */ + append_char('M'); + mp_svg_trans_pair_out(mp, pen, gr_x_coord(h),gr_y_coord(h)); + p=h; + do { + if ( gr_right_type(p)==mp_endpoint ) { + if ( p==h ) { + append_string("l0 0"); + } + return; + } + q=gr_next_knot(p); + if (mp_is_curved(p, q)){ + append_char('C'); + mp_svg_trans_pair_out(mp, pen, gr_right_x(p),gr_right_y(p)); + append_char(','); + mp_svg_trans_pair_out(mp, pen,gr_left_x(q),gr_left_y(q)); + append_char(','); + mp_svg_trans_pair_out(mp, pen,gr_x_coord(q),gr_y_coord(q)); + } else if ( q!=h ){ + append_char('L'); + mp_svg_trans_pair_out(mp, pen,gr_x_coord(q),gr_y_coord(q)); + } + p=q; + } while (p!=h); + append_char('Z'); + append_char(0); +} + + +@ @c +static void mp_svg_font_path_out (MP mp, mp_gr_knot h) { + mp_gr_knot p, q; /* for scanning the path */ + append_char('M'); + mp_svg_font_pair_out(mp, gr_x_coord(h),gr_y_coord(h)); + p=h; + do { + if ( gr_right_type(p)==mp_endpoint ) { + if ( p==h ) { + append_char('l'); + mp_svg_font_pair_out(mp, 0, 0); + } + return; + } + q=gr_next_knot(p); + if (mp_is_curved(p, q)){ + append_char('C'); + mp_svg_font_pair_out(mp, gr_right_x(p),gr_right_y(p)); + append_char(','); + mp_svg_font_pair_out(mp, gr_left_x(q),gr_left_y(q)); + append_char(','); + mp_svg_font_pair_out(mp, gr_x_coord(q),gr_y_coord(q)); + } else if ( q!=h ){ + append_char('L'); + mp_svg_font_pair_out(mp, gr_x_coord(q),gr_y_coord(q)); + } + p=q; + } while (p!=h); + append_char(0); +} + +@ If |prologues:=3|, any glyphs in labels will be converted into paths. + +@d do_mark(A,B) do { + if (mp_chars == NULL) { + mp_chars = mp_xmalloc(mp, mp->font_max+1, sizeof(int *)); + memset(mp_chars, 0, ((mp->font_max+1) * sizeof(int *))); + } + if (mp_chars[(A)] == NULL) { + int *glfs = mp_xmalloc(mp, 256, sizeof(int)); + memset(glfs, 0, (256 * sizeof(int))); + mp_chars[(A)] = glfs; + } + mp_chars[(A)][(int)(B)] = 1; +} while (0) + +@<Declarations@>= +void mp_svg_print_glyph_defs (MP mp, mp_edge_object *h); + +@ @c +void mp_svg_print_glyph_defs (MP mp, mp_edge_object *h) { + mp_graphic_object *p; /* object index */ + int k; /* general purpose index */ + size_t l; /* a string length */ + int **mp_chars = NULL; /* a twodimensional array of used glyphs */ + mp_ps_font *f = NULL; + mp_edge_object *ch; + p = h->body; + while ( p!=NULL ) { + if ((gr_type(p) == mp_text_code) && + (gr_font_n(p)!=null_font) && + ((l = gr_text_l(p))>0) ) { + unsigned char *s = (unsigned char *)gr_text_p(p); + while (l-->0) { + do_mark(gr_font_n(p), *s); + s++; + } + } + p=gr_link(p); + } + if (mp_chars != NULL) { + mp_svg_starttag(mp,"defs"); + for (k=0;k<=(int)mp->font_max;k++) { + if (mp_chars[k] != NULL ) { + double scale; /* the next gives rounding errors */ + double ds,dx,sk; + ds =(mp->font_dsize[k]+8) / 16; + scale = (1/1000.0) * (ds); + ds = (scale); + dx = ds; + sk = 0; + for (l=0;l<256;l++) { + if (mp_chars[k][l] == 1) { + if (f == NULL) { + f = mp_ps_font_parse(mp, k); + if (f == NULL) continue; + if (f->extend != 0) { + dx = (((double)f->extend / 1000.0) * scale); + } + if (f->slant != 0) { + sk = (((double)f->slant / 1000.0) * 90); + } + } + mp_svg_open_starttag(mp,"g"); + append_string("scale("); + mp_svg_store_double(mp,dx/65536); + append_char(','); + mp_svg_store_double(mp,ds/65536); + append_char(')'); + if (sk!=0) { + append_string(" skewX("); + mp_svg_store_double(mp,-sk); + append_char(')'); + } + mp_svg_attribute(mp, "transform", mp->svg->buf); + mp_svg_reset_buf(mp); + + append_string("GLYPH"); + append_string(mp->font_name[k]); + append_char('_'); + mp_svg_store_int(mp, (int)l); + mp_svg_attribute(mp, "id", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_close_starttag(mp); + if (f != NULL) { + ch = mp_ps_font_charstring(mp,f,(int)l); + if (ch != NULL) { + p = ch->body; + mp_svg_open_starttag(mp,"path"); + mp_svg_attribute(mp, "style", "fill-rule: evenodd;"); + while (p!=NULL) { + if (mp->svg->loc>0) mp->svg->loc--; /* drop a '\\0' */ + mp_svg_font_path_out(mp, gr_path_p((mp_fill_object *)p)); + p=p->next; + } + mp_svg_attribute(mp, "d", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_close_starttag(mp); + mp_svg_endtag(mp,"path",false); + } + mp_gr_toss_objects(ch); + } + mp_svg_endtag(mp,"g",true); + } + } + if (f!=NULL) { mp_ps_font_free(mp, f); f = NULL; } + } + } + mp_svg_endtag(mp,"defs", true); + + /* cleanup */ + for (k=0;k<(int)mp->font_max;k++) { + mp_xfree(mp_chars[k]); + } + mp_xfree(mp_chars); + } +} + + +@ Now for outputting the actual graphic objects. + +@<Declarations@>= +static void mp_svg_text_out (MP mp, mp_text_object *p, int prologues) ; + +@ @c +void mp_svg_text_out (MP mp, mp_text_object *p, int prologues) { + /* -Wunused: char *fname; */ + unsigned char *s; + int k; /* a character */ + size_t l; /* string length */ + boolean transformed ; + double ds; /* design size and scale factor for a text node */ + /* clang: never read: |fname = mp->font_ps_name[gr_font_n(p)];| */ + s = (unsigned char *)gr_text_p(p); + l = gr_text_l(p); + transformed=(gr_txx_val(p)!=unity)||(gr_tyy_val(p)!=unity)|| + (gr_txy_val(p)!=0)||(gr_tyx_val(p)!=0); + mp_svg_open_starttag(mp, "g"); + if ( transformed ) { + append_string("matrix("); + mp_svg_store_double(mp,gr_txx_val(p)); append_char(','); + mp_svg_store_double(mp,-gr_tyx_val(p)); append_char(','); + mp_svg_store_double(mp,-gr_txy_val(p)); append_char(','); + mp_svg_store_double(mp,gr_tyy_val(p)); append_char(','); + } else { + append_string("translate("); + } + mp_svg_pair_out(mp,gr_tx_val(p),gr_ty_val(p)); + append_char(')'); + + mp_svg_attribute(mp, "transform", mp->svg->buf); + mp_svg_reset_buf(mp); + + append_string("fill: "); + mp_svg_color_out(mp,(mp_graphic_object *)p); + append_char(';'); + mp_svg_attribute(mp, "style", mp->svg->buf); + mp_svg_reset_buf(mp); + + mp_svg_close_starttag(mp); + + if (prologues == 3 ) { + + double charwd; + double wd = 0.0; /* this is in PS design units */ + while (l-->0) { + k=(int)*s++; + mp_svg_open_starttag(mp, "use"); + append_string("#GLYPH"); + append_string(mp->font_name[gr_font_n(p)]); + append_char('_'); + mp_svg_store_int(mp,k); + mp_svg_attribute(mp,"xlink:href", mp->svg->buf); + mp_svg_reset_buf(mp); + charwd = ((wd/100)); + if (charwd!=0) { + mp_svg_store_double(mp,charwd); + mp_svg_attribute(mp,"x", mp->svg->buf); + mp_svg_reset_buf(mp); + } + wd += mp_get_char_dimension (mp, mp->font_name[gr_font_n(p)], k, 'w'); + mp_svg_close_starttag(mp); + mp_svg_endtag(mp, "use", false); + } + } else { + mp_svg_open_starttag(mp, "text"); + ds=(mp->font_dsize[gr_font_n(p)]+8) / 16 / 65536.0; + mp_svg_store_double(mp,ds); + mp_svg_attribute(mp, "font-size", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_close_starttag(mp); + + while (l-->0) { + k=(int)*s++; + if (@<Character |k| is illegal in SVG output@>) { + char S[100]; + mp_snprintf(S,99,"The character %d cannot be output in SVG " + "unless prologues:=3;",k); + mp_warn(mp,S); + } else if ( (@<Character |k| is not allowed in SVG output@>) ) { + append_string("&#"); + mp_svg_store_int(mp,k); + append_char(';'); + } else { + append_char((char)k); + } + } + mp_svg_print_buf(mp); + mp_svg_endtag(mp, "text", false); + } + mp_svg_endtag(mp, "g", true); +} + +@ When stroking a path with an elliptical pen, it is necessary to transform +the coordinate system so that a unit circular pen will have the desired shape. +To keep this transformation local, we enclose it in a $$\&{<g>}\ldots\&{</g>}$$ +block. Any translation component must be applied to the path being stroked +while the rest of the transformation must apply only to the pen. +If |fill_also=true|, the path is to be filled as well as stroked so we must +insert commands to do this after giving the path. + +@<Declarations@>= +static void mp_svg_stroke_out (MP mp, mp_graphic_object *h, + mp_pen_info *pen, boolean fill_also) ; + + +@ @c +void mp_svg_stroke_out (MP mp, mp_graphic_object *h, + mp_pen_info *pen, boolean fill_also) { + boolean transformed = false; + if (pen != NULL) { + transformed = true; + if ((pen->sx==unity) && + (pen->rx==0) && + (pen->ry==0) && + (pen->sy==unity) && + (pen->tx==0) && + (pen->ty==0)) { + transformed = false; + } + } + if (transformed) { + mp_svg_open_starttag(mp, "g"); + append_string("matrix("); + mp_svg_store_double(mp,pen->sx); append_char(','); + mp_svg_store_double(mp,pen->rx); append_char(','); + mp_svg_store_double(mp,pen->ry); append_char(','); + mp_svg_store_double(mp,pen->sy); append_char(','); + mp_svg_store_double(mp,pen->tx); append_char(','); + mp_svg_store_double(mp,pen->ty); + append_char(')'); + mp_svg_attribute(mp, "transform", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_close_starttag(mp); + } + mp_svg_open_starttag(mp, "path"); + + if (false) { + if (transformed) + mp_svg_path_trans_out(mp, gr_path_p((mp_fill_object *)h), pen); + else + mp_svg_path_out(mp, gr_path_p((mp_fill_object *)h)); + mp_svg_attribute(mp, "d", mp->svg->buf); + mp_svg_reset_buf(mp); + append_string("fill: "); + mp_svg_color_out(mp,h); + append_string("; stroke: none;"); + mp_svg_attribute(mp, "style", mp->svg->buf); + mp_svg_reset_buf(mp); + } else { + if (transformed) + mp_svg_path_trans_out(mp, gr_path_p((mp_stroked_object *)h), pen); + else + mp_svg_path_out(mp, gr_path_p((mp_stroked_object *)h)); + mp_svg_attribute(mp, "d", mp->svg->buf); + mp_svg_reset_buf(mp); + append_string("stroke:"); + mp_svg_color_out(mp,h); + append_string("; stroke-width: "); + if (pen != NULL) { + mp_svg_store_double(mp, pen->ww); + } else { + append_char('0'); + } + append_char(';'); + if (gr_lcap_val(h)!=0) { + append_string("stroke-linecap: "); + switch (gr_lcap_val(h)) { + case 1: append_string("round"); break; + case 2: append_string("square"); break; + default: append_string("butt"); break; + } + append_char(';'); + } + if (gr_type(h)!=mp_fill_code) { + mp_dash_object *hh; + hh =gr_dash_p(h); + if (hh != NULL && hh->array != NULL) { + int i; + append_string("stroke-dasharray: "); + /* svg doesn't accept offsets */ + for (i=0; *(hh->array+i) != -1;i++) { + mp_svg_store_double(mp, *(hh->array+i)); + append_char(' ') ; + } + append_char(';'); + } + + if (gr_ljoin_val((mp_stroked_object *)h)!=0) { + append_string ("stroke-linejoin: "); + switch (gr_ljoin_val((mp_stroked_object *)h)) { + case 1: append_string("round"); break; + case 2: append_string("bevel"); break; + default: append_string("miter"); break; + } + append_char(';'); + } + + if (gr_miterlim_val((mp_stroked_object *)h) != 4*unity) { + append_string("stroke-miterlimit: "); + mp_svg_store_double(mp, gr_miterlim_val((mp_stroked_object *)h)); + append_char(';'); + } + } + + append_string("fill: "); + if (fill_also) { + mp_svg_color_out(mp,h); + } else { + append_string("none"); + } + append_char(';'); + mp_svg_attribute(mp, "style", mp->svg->buf); + mp_svg_reset_buf(mp); + } + mp_svg_close_starttag(mp); + mp_svg_endtag(mp, "path", false); + if (transformed) { + mp_svg_endtag(mp, "g", true); + } +} + +@ Here is a simple routine that just fills a cycle. + +@<Declarations@>= +static void mp_svg_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h); + +@ @c +void mp_svg_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h) { + mp_svg_open_starttag(mp, "path"); + mp_svg_path_out(mp, p); + mp_svg_attribute(mp, "d", mp->svg->buf); + mp_svg_reset_buf(mp); + append_string("fill: "); + mp_svg_color_out(mp,h); + append_string(";stroke: none;"); + mp_svg_attribute(mp, "style", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_close_starttag(mp); /* path */ + mp_svg_endtag(mp, "path", false); +} + +@ Clipping paths use IDs, so an extra global is needed: + +@<Globals...@>= +int clipid; + +@ +@<Set initial values@>= +mp->svg->clipid = 0; + +@ @<Declarations@>= +static void mp_svg_clip_out (MP mp, mp_clip_object *p); + +@ @c +void mp_svg_clip_out (MP mp, mp_clip_object *p) { + mp->svg->clipid++; + mp_svg_starttag(mp, "g"); + mp_svg_starttag(mp, "defs"); + mp_svg_open_starttag(mp, "clipPath"); + + append_string("CLIP"); + mp_svg_store_int(mp, mp->svg->clipid); + mp_svg_attribute(mp, "id", mp->svg->buf); + mp_svg_reset_buf(mp); + + mp_svg_close_starttag(mp); + mp_svg_open_starttag(mp, "path"); + mp_svg_path_out(mp, gr_path_p(p)); + mp_svg_attribute(mp,"d", mp->svg->buf); + mp_svg_reset_buf(mp); + mp_svg_attribute(mp, "style", "fill: black; stroke: none;"); + mp_svg_close_starttag(mp); /* path */ + mp_svg_endtag(mp, "path", false); + mp_svg_endtag(mp, "clipPath", true); + mp_svg_endtag(mp, "defs", true); + mp_svg_open_starttag(mp, "g"); + + append_string("url(#CLIP"); + mp_svg_store_int(mp, mp->svg->clipid); + append_string(");"); + mp_svg_attribute(mp, "clip-path", mp->svg->buf); + mp_svg_reset_buf(mp); + + mp_svg_close_starttag(mp); +} + + + +@ The main output function + +@d gr_has_scripts(A) (gr_type((A))<mp_start_clip_code) +@d pen_is_elliptical(A) ((A)==gr_next_knot((A))) + +@<Exported function ...@>= +int mp_svg_gr_ship_out (mp_edge_object *hh, int prologues, int standalone) ; + +@ @c +int mp_svg_gr_ship_out (mp_edge_object *hh, int qprologues, int standalone) { + mp_graphic_object *p; + mp_pen_info *pen = NULL; + MP mp = hh->parent; + if (standalone) { + mp->jump_buf = malloc(sizeof(jmp_buf)); + if (mp->jump_buf == NULL || setjmp(*(mp->jump_buf))) + return 0; + } + if (mp->history >= mp_fatal_error_stop ) return 1; + mp_open_output_file(mp); + if ( (qprologues>=1) && (mp->last_ps_fnum==0) && mp->last_fnum>0) + mp_read_psname_table(mp); + /* The next seems counterintuitive, but calls from |mp_svg_ship_out| + * set standalone to true, and because embedded use is likely, it is + * better not to output the XML declaration in that case. + */ + if (!standalone) + mp_svg_print(mp, "<?xml version=\"1.0\"?>"); + mp_svg_print_initial_comment(mp, hh); + if (qprologues == 3) { + mp_svg_print_glyph_defs(mp, hh); + } + p = hh->body; + while ( p!=NULL ) { + if ( gr_has_scripts(p) ) { + @<Write |pre_script| of |p|@>; + } + switch (gr_type(p)) { + case mp_fill_code: + { + mp_fill_object *ph = (mp_fill_object *)p; + if ( gr_pen_p(ph)==NULL ) { + mp_svg_fill_out(mp, gr_path_p(ph), p); + } else if ( pen_is_elliptical(gr_pen_p(ph)) ) { + pen = mp_svg_pen_info(mp, gr_path_p(ph), gr_pen_p(ph)); + mp_svg_stroke_out(mp, p, pen, true); + mp_xfree(pen); + } else { + mp_svg_fill_out(mp, gr_path_p(ph), p); + mp_svg_fill_out(mp, gr_htap_p(ph), p); + } + } + break; + case mp_stroked_code: + { + mp_stroked_object *ph = (mp_stroked_object *)p; + if ( pen_is_elliptical(gr_pen_p(ph))) { + pen = mp_svg_pen_info(mp, gr_path_p(ph), gr_pen_p(ph)); + mp_svg_stroke_out(mp, p, pen, false); + mp_xfree(pen); + } else { + mp_svg_fill_out(mp, gr_path_p(ph), p); + } + } + break; + case mp_text_code: + if ( (gr_font_n(p)!=null_font) && (gr_text_l(p)>0) ) { + mp_svg_text_out(mp, (mp_text_object *)p, qprologues); + } + break; + case mp_start_clip_code: + mp_svg_clip_out(mp, (mp_clip_object *)p); + break; + case mp_stop_clip_code: + mp_svg_endtag(mp, "g", true); + mp_svg_endtag(mp, "g", true); + break; + case mp_start_bounds_code: + case mp_stop_bounds_code: + break; + case mp_special_code: + { + mp_special_object *ps = (mp_special_object *)p; + mp_svg_print_nl (mp, gr_pre_script(ps)); + mp_svg_print_ln (mp); + } + break; + } /* all cases are enumerated */ + if ( gr_has_scripts(p) ) { + @<Write |post_script| of |p|@>; + } + p=gr_link(p); + } + mp_svg_endtag(mp, "svg", true); + mp_svg_print_ln(mp); + (mp->close_file)(mp,mp->output_file); + return 1; +} + +@ @(mplibsvg.h@>= +#ifndef MPLIBSVG_H +#define MPLIBSVG_H 1 +#include "mplibps.h" +int mp_svg_ship_out (mp_edge_object *hh, int prologues) ; +#endif + +@ @c +int mp_svg_ship_out (mp_edge_object *hh, int prologues) { + return mp_svg_gr_ship_out (hh, prologues, (int)true); +} + +@ +@d do_write_prescript(a,b) { + if ( (gr_pre_script((b *)a))!=NULL ) { + mp_svg_print_nl (mp, gr_pre_script((b *)a)); + mp_svg_print_ln(mp); + } +} + +@<Write |pre_script| of |p|@>= +{ + if (gr_type(p)==mp_fill_code) { do_write_prescript(p,mp_fill_object); } + else if (gr_type(p)==mp_stroked_code) { do_write_prescript(p,mp_stroked_object); } + else if (gr_type(p)==mp_text_code) { do_write_prescript(p,mp_text_object); } +} + + +@ +@d do_write_postscript(a,b) { + if ( (gr_post_script((b *)a))!=NULL ) { + mp_svg_print_nl (mp, gr_post_script((b *)a)); + mp_svg_print_ln(mp); + } +} + +@<Write |post_script| of |p|@>= +{ + if (gr_type(p)==mp_fill_code) { do_write_postscript(p,mp_fill_object); } + else if (gr_type(p)==mp_stroked_code) { do_write_postscript(p,mp_stroked_object); } + else if (gr_type(p)==mp_text_code) { do_write_postscript(p,mp_text_object); } +} + |