% $Id: svgout.w 657 2008-10-08 09:48:49Z taco $
%
% 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 .
%
% 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 true 1
@d false 0
@d null_font 0
@d null 0
@d unity 0200000 /* $2^{16}$, represents 1.00000 */
@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
#include
#include
#include
#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
@
@
@ 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@>=
typedef struct svgout_data_struct {
@
} svgout_data_struct ;
@
@ @=
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));
@;
}
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.
@=
size_t file_offset;
@ @=
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|.
@=
char *buf;
unsigned loc;
unsigned bufsize;
@ Start with a modest size of 256. the buffer will grow automatically
when needed.
@=
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}funity )
s=s+0100000-(delta / 2); /* round the final digit */
append_char((char)('0'+(s / unity)));
s=10*(s % unity);
delta=delta*10;
} while (s>delta);
}
}
@ Output XML tags.
In order to create a nicely indented output file, the current tag
nesting level needs to be remembered.
@=
int level;
@ @=
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.
@=
(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.
@=
(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 |