beta-0.89.2
[luatex.git] / source / texk / web2c / mplibdir / svgout.w
blob6ee2c71173990175141c7105988cdd82f3d5c67f
1 % Copyright 2008-2009 Taco Hoekwater.
3 % This program is free software: you can redistribute it and/or modify
4 % it under the terms of the GNU Lesser General Public License as published by
5 % the Free Software Foundation, either version 3 of the License, or
6 % (at your option) any later version.
8 % This program is distributed in the hope that it will be useful,
9 % but WITHOUT ANY WARRANTY; without even the implied warranty of
10 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 % GNU Lesser General Public License for more details.
13 % You should have received a copy of the GNU Lesser General Public License
14 % along with this program. If not, see <http://www.gnu.org/licenses/>.
16 % TeX is a trademark of the American Mathematical Society.
17 % METAFONT is a trademark of Addison-Wesley Publishing Company.
18 % PostScript is a trademark of Adobe Systems Incorporated.
20 % Here is TeX material that gets inserted after \input webmac
22 \font\tenlogo=logo10 % font used for the METAFONT logo
23 \font\logos=logosl10
24 \def\MF{{\tenlogo META}\-{\tenlogo FONT}}
25 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
26 \def\<#1>{$\langle#1\rangle$}
27 \def\section{\mathhexbox278}
28 \def\[#1]{} % from pascal web
29 \def\(#1){} % this is used to make section names sort themselves better
30 \def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@>
32 \def\title{MetaPost SVG output}
33 \def\topofcontents{\hsize 5.5in
34 \vglue -30pt plus 1fil minus 1.5in
35 \def\?##1]{\hbox to 1in{\hfil##1.\ }}
37 \def\botofcontents{\vskip 0pt plus 1fil minus 1.5in}
38 \pdfoutput=1
39 \pageno=3
42 @d zero_t ((math_data *)mp->math)->zero_t
43 @d number_zero(A) (((math_data *)(mp->math))->equal)(A,zero_t)
44 @d number_greater(A,B) (((math_data *)(mp->math))->greater)(A,B)
45 @d number_positive(A) number_greater(A, zero_t)
46 @d number_to_scaled(A) (((math_data *)(mp->math))->to_scaled)(A)
47 @d round_unscaled(A) (((math_data *)(mp->math))->round_unscaled)(A)
48 @d true 1
49 @d false 0
50 @d null_font 0
51 @d null 0
52 @d unity 1.0
53 @d incr(A) (A)=(A)+1 /* increase a variable by unity */
54 @d decr(A) (A)=(A)-1 /* decrease a variable by unity */
55 @d negate(A) (A)=-(A) /* change the sign of a variable */
58 #include <w2c/config.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <math.h>
63 #include "mplib.h"
64 #include "mplibps.h" /* external header */
65 #include "mplibsvg.h" /* external header */
66 #include "mpmp.h" /* internal header */
67 #include "mppsout.h" /* internal header */
68 #include "mpsvgout.h" /* internal header */
69 #include "mpmath.h" /* internal header */
71 @<Types in the outer block@>
72 @<Declarations@>
74 @ There is a small bit of code from the backend that bleads through
75 to the frontend because I do not know how to set up the includes
76 properly. That is |typedef struct svgout_data_struct * svgout_data|.
78 @ @(mpsvgout.h@>=
79 #ifndef MPSVGOUT_H
80 #define MPSVGOUT_H 1
81 #include "mplib.h"
82 #include "mpmp.h"
83 #include "mplibps.h"
84 typedef struct svgout_data_struct {
85 @<Globals@>
86 } svgout_data_struct ;
87 @<Exported function headers@>
88 #endif
90 @ @<Exported function headers@>=
91 void mp_svg_backend_initialize (MP mp) ;
92 void mp_svg_backend_free (MP mp) ;
94 @ @c
95 void mp_svg_backend_initialize (MP mp) {
96 mp->svg = mp_xmalloc(mp,1,sizeof(svgout_data_struct));
97 @<Set initial values@>;
99 void mp_svg_backend_free (MP mp) {
100 mp_xfree(mp->svg->buf);
101 mp_xfree(mp->svg);
102 mp->svg = NULL;
105 @ Writing to SVG files
107 This variable holds the number of characters on the current SVG file
108 line. It could also be a boolean because right now the only interesting
109 thing it does is keep track of whether or not we are start-of-line.
111 @<Globals@>=
112 size_t file_offset;
114 @ @<Set initial values@>=
115 mp->svg->file_offset = 0;
117 @ Print a newline.
120 static void mp_svg_print_ln (MP mp) {
121 (mp->write_ascii_file)(mp,mp->output_file,"\n");
122 mp->svg->file_offset=0;
125 @ Print a single character.
128 static void mp_svg_print_char (MP mp, int s) {
129 char ss[2];
130 ss[0]=(char)s; ss[1]=0;
131 (mp->write_ascii_file)(mp,mp->output_file,(char *)ss);
132 mp->svg->file_offset ++;
135 @ Print a string.
137 In PostScript, this used to be done in terms of |mp_svg_print_char|,
138 but that is very expensive (in other words: slow). It should be ok
139 to print whole strings here because line length of an XML file should
140 not be an issue to any respectable XML processing tool.
143 static void mp_svg_print (MP mp, const char *ss) {
144 (mp->write_ascii_file)(mp,mp->output_file,ss);
145 mp->svg->file_offset += strlen(ss);
149 @ The procedure |print_nl| is like |print|, but it makes sure that the
150 string appears at the beginning of a new line.
153 static void mp_svg_print_nl (MP mp, const char *s) {
154 if ( mp->svg->file_offset>0 )
155 mp_svg_print_ln(mp);
156 mp_svg_print(mp, s);
160 @ Many of the printing routines use a print buffer to store partial
161 strings in before feeding the attribute value to |mp_svg_attribute|.
163 @<Globals...@>=
164 char *buf;
165 unsigned loc;
166 unsigned bufsize;
168 @ Start with a modest size of 256. the buffer will grow automatically
169 when needed.
171 @<Set initial values@>=
172 mp->svg->loc = 0;
173 mp->svg->bufsize = 256;
174 mp->svg->buf = mp_xmalloc(mp,mp->svg->bufsize,1);
175 memset(mp->svg->buf,0,256);
178 @ How to append a character or a string of characters to
179 the end of the buffer.
181 @d append_char(A) do {
182 if (mp->svg->loc==(mp->svg->bufsize-1)) {
183 char *buffer;
184 unsigned l;
185 l = (unsigned)(mp->svg->bufsize+(mp->svg->bufsize>>4));
186 if (l>(0x3FFFFFF)) {
187 mp_confusion(mp,"svg buffer size");
189 buffer = mp_xmalloc(mp,l,1);
190 memset (buffer,0,l);
191 memcpy(buffer,mp->svg->buf,(size_t)mp->svg->bufsize);
192 mp_xfree(mp->svg->buf);
193 mp->svg->buf = buffer ;
194 mp->svg->bufsize = l;
196 mp->svg->buf[mp->svg->loc++] = (A);
197 } while (0)
199 @d append_string(A) do {
200 const char *ss = (A);
201 while (*ss != '\0') { append_char(*ss); ss++ ;}
202 } while (0)
204 @ This function resets the buffer in preparation of the next string.
205 The |memset| is an easy way to make sure that the old string is
206 forgotten completely and that the new string will be zero-terminated.
209 static void mp_svg_reset_buf(MP mp) {
210 mp->svg->loc = 0;
211 memset (mp->svg->buf,0,mp->svg->bufsize);
214 @ Printing the buffer is a matter of printing its string, then
215 it is reset.
218 static void mp_svg_print_buf (MP mp) {
219 mp_svg_print(mp, (char *)mp->svg->buf);
220 mp_svg_reset_buf(mp);
223 @ The following procedure, which stores the decimal representation of
224 a given integer |n| in the buffer, has been written carefully so that
225 it works properly if |n=0| or if |(-n)| would cause overflow.
228 static void mp_svg_store_int (MP mp, integer n) {
229 unsigned char dig[23]; /* digits in a number, for rounding */
230 integer m; /* used to negate |n| in possibly dangerous cases */
231 int k = 0; /* index to current digit; we assume that $|n|<10^{23}$ */
232 if ( n<0 ) {
233 append_char('-');
234 if ( n>-100000000 ) {
235 negate(n);
236 } else {
237 m=-1-n; n=m / 10; m=(m % 10)+1; k=1;
238 if ( m<10 ) {
239 dig[0]=(unsigned char)m;
240 } else {
241 dig[0]=0; incr(n);
245 do {
246 dig[k]=(unsigned char)(n % 10); n=n / 10; incr(k);
247 } while (n!=0);
248 /* print the digits */
249 while ( k-->0 ){
250 append_char((char)('0'+dig[k]));
254 @ \MP\ also makes use of a trivial procedure to output two digits. The
255 following subroutine is usually called with a parameter in the range |0<=n<=99|,
256 but the assignments makes sure that only the two least significant digits
257 are printed, just in case.
260 static void mp_svg_store_dd (MP mp,integer n) {
261 char nn=(char)abs(n) % 100;
262 append_char((char)('0'+(nn / 10)));
263 append_char((char)('0'+(nn % 10)));
266 @ Conversely, here is a procedure analogous to |mp_svg_store_int|.
267 A decimal point is printed only if the value is not an integer. If
268 there is more than one way to print the result with the optimum
269 number of digits following the decimal point, the closest possible
270 value is given.
272 The invariant relation in the \&{do while} loop is that a sequence of
273 decimal digits yet to be printed will yield the original number if and only if
274 they form a fraction~$f$ in the range $s-\delta\L10\cdot2^{16}f<s$.
275 We can stop if and only if $f=0$ satisfies this condition; the loop will
276 terminate before $s$ can possibly become zero.
279 static void mp_svg_store_double (MP mp, double s) {
280 char *value, *c;
281 value = mp_xmalloc(mp,1,32);
282 mp_snprintf(value,32,"%f", s);
283 c = value;
284 while (*c) {
285 append_char(*c);
286 c++;
288 free(value);
292 @ Output XML tags.
294 In order to create a nicely indented output file, the current tag
295 nesting level needs to be remembered.
297 @<Globals...@>=
298 int level;
300 @ @<Set initial values@>=
301 mp->svg->level = 0;
303 @ Output an XML start tag.
305 Because start tags may carry attributes, this happens in two steps.
306 The close function is trivial of course, but it looks nicer in the source.
308 @d mp_svg_starttag(A,B) { mp_svg_open_starttag (A,B); mp_svg_close_starttag(A); }
311 static void mp_svg_open_starttag (MP mp, const char *s) {
312 int l = mp->svg->level * 2;
313 mp_svg_print_ln(mp);
314 while (l-->0) {
315 append_char(' ');
317 append_char('<');
318 append_string(s);
319 mp_svg_print_buf(mp);
320 mp->svg->level++;
322 static void mp_svg_close_starttag (MP mp) {
323 mp_svg_print_char(mp,'>');
326 @ Output an XML end tag.
328 If the |indent| is true, then the end tag will appear on the next line
329 of the SVG file, correctly indented for the current XML nesting
330 level. If it is false, the end tag will appear immediatelu after the
331 preceding output.
334 static void mp_svg_endtag (MP mp, const char *s, boolean indent) {
335 mp->svg->level--;
336 if (indent) {
337 int l = mp->svg->level * 2;
338 mp_svg_print_ln(mp);
339 while (l-->0) {
340 append_char(' ');
343 append_string("</");
344 append_string(s);
345 append_char('>');
346 mp_svg_print_buf(mp);
349 @ Attribute. Can't play with the buffer here becase it is likely
350 that that is the |v| argument.
353 static void mp_svg_attribute (MP mp, const char *s, const char *v) {
354 mp_svg_print_char(mp, ' ');
355 mp_svg_print(mp, s);
356 mp_svg_print(mp,"=\"");
357 mp_svg_print(mp, v);
358 mp_svg_print_char(mp,'"');
361 @ This is a test to filter out characters that are illegal in XML.
363 @<Character |k| is illegal in SVG output@>=
364 (k<=0x8)||(k==0xB)||(k==0xC)||(k>=0xE && k<=0x1F)||
365 (k>=0x7F && k<=0x84)||(k>=0x86 && k<=0x9F)
368 @ This is test is used to switch between direct representation of characters
369 and character references. Just in case the input string is UTF-8, allow everything
370 except the characters that have to be quoted for XML well-formedness.
372 @<Character |k| is not allowed in SVG output@>=
373 (k=='&')||(k=='>')||(k=='<')
375 @ We often need to print a pair of coordinates.
377 Because of bugs in svg rendering software, it is necessary to
378 change the point coordinates so that there are all in the "positive"
379 quadrant of the SVG field. This means an shift and a vertical flip.
381 The two correction values are calculated by the function that writes
382 the initial |<svg>| tag, and are stored in two globals:
384 @<Globals@>=
385 integer dx;
386 integer dy;
388 @ @c
389 void mp_svg_pair_out (MP mp,double x, double y) {
390 mp_svg_store_double(mp, (x+mp->svg->dx));
391 append_char(' ');
392 mp_svg_store_double(mp, (-(y+mp->svg->dy)));
395 @ @<Declarations@>=
396 void mp_svg_font_pair_out (MP mp,double x, double y) ;
398 @ @c
399 void mp_svg_font_pair_out (MP mp,double x, double y) {
400 mp_svg_store_double(mp, (x));
401 append_char(' ');
402 mp_svg_store_double(mp, -(y));
405 @ When stroking a path with an elliptical pen, it is necessary to distort
406 the path such that a circular pen can be used to stroke the path. The path
407 itself is wrapped in another transformation to restore the points to their
408 correct location (but now with a modified pen stroke).
410 Because all the points in the path need fixing, it makes sense to
411 have a specific helper to write such distorted pairs of coordinates out.
413 @<Declarations@>=
414 void mp_svg_trans_pair_out (MP mp, mp_pen_info *pen, double x, double y) ;
416 @ @c
417 void mp_svg_trans_pair_out (MP mp, mp_pen_info *pen, double x, double y) {
418 double sx,sy, rx,ry, px, py, retval, divider;
419 sx = (pen->sx);
420 sy = (pen->sy);
421 rx = (pen->rx);
422 ry = (pen->ry);
423 px = ((x+mp->svg->dx));
424 py = ((-(y+mp->svg->dy)));
425 divider = (sx*sy - rx*ry);
426 retval = (sy*px-ry*py)/divider;
427 mp_svg_store_double(mp, (retval));
428 append_char(' ');
429 retval = (sx*py-rx*px)/divider;
430 mp_svg_store_double(mp, (retval));
435 @ @<Declarations@>=
436 static void mp_svg_pair_out (MP mp,double x, double y) ;
439 @<Declarations@>=
440 static void mp_svg_print_initial_comment(MP mp,mp_edge_object *hh);
442 @ @c
443 void mp_svg_print_initial_comment(MP mp,mp_edge_object *hh) {
444 double tx, ty;
445 @<Print the MetaPost version and time @>;
446 mp_svg_open_starttag(mp,"svg");
447 mp_svg_attribute(mp,"version", "1.1");
448 mp_svg_attribute(mp,"xmlns", "http://www.w3.org/2000/svg");
449 mp_svg_attribute(mp, "xmlns:xlink", "http://www.w3.org/1999/xlink");
450 if ( hh->minx>hh->maxx) {
451 tx = 0;
452 ty = 0;
453 mp->svg->dx = 0;
454 mp->svg->dy = 0;
455 } else {
456 tx = (hh->minx<0 ? -hh->minx : 0) + hh->maxx;
457 ty = (hh->miny<0 ? -hh->miny : 0) + hh->maxy;
458 mp->svg->dx = (hh->minx<0 ? -hh->minx : 0);
459 mp->svg->dy = (hh->miny<0 ? -hh->miny : 0) - ty;
461 mp_svg_store_double(mp, tx);
462 mp_svg_attribute(mp,"width", mp->svg->buf);
463 mp_svg_reset_buf(mp);
464 mp_svg_store_double(mp, ty);
465 mp_svg_attribute(mp,"height", mp->svg->buf);
466 mp_svg_reset_buf(mp);
467 append_string("0 0 "); mp_svg_store_double(mp,tx);
468 append_char(' '); mp_svg_store_double(mp,ty);
469 mp_svg_attribute(mp,"viewBox", mp->svg->buf);
470 mp_svg_reset_buf(mp);
471 mp_svg_close_starttag(mp);
472 mp_svg_print_nl(mp,"<!-- Original BoundingBox: ");
473 mp_svg_store_double(mp, hh->minx); append_char(' ');
474 mp_svg_store_double(mp, hh->miny); append_char(' ');
475 mp_svg_store_double(mp, hh->maxx); append_char(' ');
476 mp_svg_store_double(mp, hh->maxy);
477 mp_svg_print_buf(mp);
478 mp_svg_print(mp," -->");
481 @ @<Print the MetaPost version and time @>=
483 char *s;
484 int tt; /* scaled */
485 mp_svg_print_nl(mp, "<!-- Created by MetaPost ");
486 s = mp_metapost_version();
487 mp_svg_print(mp, s);
488 mp_xfree(s);
489 mp_svg_print(mp, " on ");
490 mp_svg_store_int(mp, round_unscaled(internal_value(mp_year)));
491 append_char('.');
492 mp_svg_store_dd(mp, round_unscaled(internal_value(mp_month)));
493 append_char('.');
494 mp_svg_store_dd(mp, round_unscaled(internal_value(mp_day)));
495 append_char(':');
496 tt=round_unscaled(internal_value(mp_time));
497 mp_svg_store_dd(mp, tt / 60);
498 mp_svg_store_dd(mp, tt % 60);
499 mp_svg_print_buf(mp);
500 mp_svg_print(mp, " -->");
504 @ Outputting a color specification.
506 @d set_color_objects(pq)
507 object_color_model = pq->color_model;
508 object_color_a = pq->color.a_val;
509 object_color_b = pq->color.b_val;
510 object_color_c = pq->color.c_val;
511 object_color_d = pq->color.d_val;
514 static void mp_svg_color_out (MP mp, mp_graphic_object *p) {
515 int object_color_model;
516 double object_color_a, object_color_b, object_color_c, object_color_d ;
517 if (gr_type(p) == mp_fill_code) {
518 mp_fill_object *pq = (mp_fill_object *)p;
519 set_color_objects(pq);
520 } else if (gr_type(p) == mp_stroked_code) {
521 mp_stroked_object *pq = (mp_stroked_object *)p;
522 set_color_objects(pq);
523 } else {
524 mp_text_object *pq = (mp_text_object *)p;
525 set_color_objects(pq);
527 if ( object_color_model==mp_no_model ) {
528 append_string("black");
529 } else {
530 if ( object_color_model==mp_grey_model ) {
531 object_color_b = object_color_a;
532 object_color_c = object_color_a;
533 } else if ( object_color_model==mp_cmyk_model) {
534 int c,m,y,k;
535 c = object_color_a;
536 m = object_color_b;
537 y = object_color_c;
538 k = object_color_d;
539 object_color_a = unity - (c+k>unity ? unity : c+k);
540 object_color_b = unity - (m+k>unity ? unity : m+k);
541 object_color_c = unity - (y+k>unity ? unity : y+k);
543 append_string("rgb(");
544 mp_svg_store_double(mp, (object_color_a * 100));
545 append_char('%');
546 append_char(',');
547 mp_svg_store_double(mp, (object_color_b * 100));
548 append_char('%');
549 append_char(',');
550 mp_svg_store_double(mp, (object_color_c * 100));
551 append_char('%');
552 append_char(')');
556 @ @<Declarations@>=
557 static void mp_svg_color_out (MP mp, mp_graphic_object *p);
559 @ This is the information that comes from a pen
561 @<Types...@>=
562 typedef struct mp_pen_info {
563 double tx, ty;
564 double sx, rx, ry, sy;
565 double ww;
566 } mp_pen_info;
569 @ (Re)discover the characteristics of an elliptical pen
571 @<Declarations@>=
572 mp_pen_info *mp_svg_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p);
574 @ The next two constants come from the original web source.
575 Together with the two helper functions, they will tell whether
576 the |x| or the |y| direction of the path is the most important
578 @d aspect_bound (10/65536.0)
579 @d aspect_default 1
582 static double coord_range_x (mp_gr_knot h, double dz) {
583 double z;
584 double zlo = 0, zhi = 0;
585 mp_gr_knot f = h;
586 while (h != NULL) {
587 z = gr_x_coord(h);
588 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
589 z = gr_right_x(h);
590 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
591 z = gr_left_x(h);
592 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
593 h = gr_next_knot(h);
594 if (h==f)
595 break;
597 return (zhi - zlo <= dz ? aspect_bound : aspect_default);
599 static double coord_range_y (mp_gr_knot h, double dz) {
600 double z;
601 double zlo = 0, zhi = 0;
602 mp_gr_knot f = h;
603 while (h != NULL) {
604 z = gr_y_coord(h);
605 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
606 z = gr_right_y(h);
607 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
608 z = gr_left_y(h);
609 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
610 h = gr_next_knot(h);
611 if (h==f)
612 break;
614 return (zhi - zlo <= dz ? aspect_bound : aspect_default);
619 mp_pen_info *mp_svg_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p) {
620 double wx, wy; /* temporary pen widths, in either direction */
621 struct mp_pen_info *pen; /* return structure */
622 if (p == NULL)
623 return NULL;
624 pen = mp_xmalloc(mp, 1, sizeof(mp_pen_info));
625 pen->rx = unity;
626 pen->ry = unity;
627 pen->ww = unity;
628 if ((gr_right_x(p) == gr_x_coord(p))
630 (gr_left_y(p) == gr_y_coord(p))) {
631 wx = fabs(gr_left_x(p) - gr_x_coord(p));
632 wy = fabs(gr_right_y(p) - gr_y_coord(p));
633 } else {
634 double a,b;
635 a = gr_left_x(p)-gr_x_coord(p);
636 b = gr_right_x(p)-gr_x_coord(p);
637 wx = sqrt(a*a + b*b);
638 a = gr_left_y(p)-gr_y_coord(p);
639 b = gr_right_y(p)-gr_y_coord(p);
640 wy = sqrt(a*a + b*b);
642 if ((wy/coord_range_x(pp, wx)) >= (wx/coord_range_y(pp, wy)))
643 pen->ww = wy;
644 else
645 pen->ww = wx;
646 pen->tx = gr_x_coord(p);
647 pen->ty = gr_y_coord(p);
648 pen->sx = gr_left_x(p) - pen->tx;
649 pen->rx = gr_left_y(p) - pen->ty;
650 pen->ry = gr_right_x(p) - pen->tx;
651 pen->sy = gr_right_y(p) - pen->ty;
652 if (pen->ww != unity) {
653 if (pen->ww == 0) {
654 pen->sx = unity;
655 pen->sy = unity;
656 } else {
657 /* this negation is needed because the svg coordinate system differs
658 from postscript's. */
659 pen->rx = -(pen->rx / pen->ww);
660 pen->ry = -(pen->ry / pen->ww);
661 pen->sx = pen->sx / pen->ww;
662 pen->sy = pen->sy / pen->ww;
665 return pen;
668 @ Two types of straight lines come up often in \MP\ paths:
669 cubics with zero initial and final velocity as created by |make_path| or
670 |make_envelope|, and cubics with control points uniformly spaced on a line
671 as created by |make_choices|.
673 @<Declarations@>=
674 static boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) ;
678 @d bend_tolerance (131/65536.0) /* allow rounding error of $2\cdot10^{-3}$ */
681 boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) {
682 double d; /* a temporary value */
683 if ( gr_right_x(p)==gr_x_coord(p) )
684 if ( gr_right_y(p)==gr_y_coord(p) )
685 if ( gr_left_x(q)==gr_x_coord(q) )
686 if ( gr_left_y(q)==gr_y_coord(q) )
687 return false;
688 d=gr_left_x(q)-gr_right_x(p);
689 if ( fabs(gr_right_x(p)-gr_x_coord(p)-d)<=bend_tolerance )
690 if ( fabs(gr_x_coord(q)-gr_left_x(q)-d)<=bend_tolerance ) {
691 d=gr_left_y(q)-gr_right_y(p);
692 if ( fabs(gr_right_y(p)-gr_y_coord(p)-d)<=bend_tolerance )
693 if ( fabs(gr_y_coord(q)-gr_left_y(q)-d)<=bend_tolerance )
694 return false;
696 return true;
700 @ @c
701 static void mp_svg_path_out (MP mp, mp_gr_knot h) {
702 mp_gr_knot p, q; /* for scanning the path */
703 append_char('M');
704 mp_svg_pair_out(mp, gr_x_coord(h),gr_y_coord(h));
705 p=h;
706 do {
707 if ( gr_right_type(p)==mp_endpoint ) {
708 if ( p==h ) {
709 append_string("l0 0");
711 return;
713 q=gr_next_knot(p);
714 if (mp_is_curved(p, q)){
715 append_char('C');
716 mp_svg_pair_out(mp, gr_right_x(p),gr_right_y(p));
717 append_char(',');
718 mp_svg_pair_out(mp, gr_left_x(q),gr_left_y(q));
719 append_char(',');
720 mp_svg_pair_out(mp, gr_x_coord(q),gr_y_coord(q));
721 } else if ( q!=h ){
722 append_char('L');
723 mp_svg_pair_out(mp, gr_x_coord(q),gr_y_coord(q));
725 p=q;
726 } while (p!=h);
727 append_char('Z');
728 append_char(0);
731 @ @c
732 static void mp_svg_path_trans_out (MP mp, mp_gr_knot h, mp_pen_info *pen) {
733 mp_gr_knot p, q; /* for scanning the path */
734 append_char('M');
735 mp_svg_trans_pair_out(mp, pen, gr_x_coord(h),gr_y_coord(h));
736 p=h;
737 do {
738 if ( gr_right_type(p)==mp_endpoint ) {
739 if ( p==h ) {
740 append_string("l0 0");
742 return;
744 q=gr_next_knot(p);
745 if (mp_is_curved(p, q)){
746 append_char('C');
747 mp_svg_trans_pair_out(mp, pen, gr_right_x(p),gr_right_y(p));
748 append_char(',');
749 mp_svg_trans_pair_out(mp, pen,gr_left_x(q),gr_left_y(q));
750 append_char(',');
751 mp_svg_trans_pair_out(mp, pen,gr_x_coord(q),gr_y_coord(q));
752 } else if ( q!=h ){
753 append_char('L');
754 mp_svg_trans_pair_out(mp, pen,gr_x_coord(q),gr_y_coord(q));
756 p=q;
757 } while (p!=h);
758 append_char('Z');
759 append_char(0);
763 @ @c
764 static void mp_svg_font_path_out (MP mp, mp_gr_knot h) {
765 mp_gr_knot p, q; /* for scanning the path */
766 append_char('M');
767 mp_svg_font_pair_out(mp, gr_x_coord(h),gr_y_coord(h));
768 p=h;
769 do {
770 if ( gr_right_type(p)==mp_endpoint ) {
771 if ( p==h ) {
772 append_char('l');
773 mp_svg_font_pair_out(mp, 0, 0);
775 return;
777 q=gr_next_knot(p);
778 if (mp_is_curved(p, q)){
779 append_char('C');
780 mp_svg_font_pair_out(mp, gr_right_x(p),gr_right_y(p));
781 append_char(',');
782 mp_svg_font_pair_out(mp, gr_left_x(q),gr_left_y(q));
783 append_char(',');
784 mp_svg_font_pair_out(mp, gr_x_coord(q),gr_y_coord(q));
785 } else if ( q!=h ){
786 append_char('L');
787 mp_svg_font_pair_out(mp, gr_x_coord(q),gr_y_coord(q));
789 p=q;
790 } while (p!=h);
791 append_char(0);
794 @ If |prologues:=3|, any glyphs in labels will be converted into paths.
796 @d do_mark(A,B) do {
797 if (mp_chars == NULL) {
798 mp_chars = mp_xmalloc(mp, mp->font_max+1, sizeof(int *));
799 memset(mp_chars, 0, ((mp->font_max+1) * sizeof(int *)));
801 if (mp_chars[(A)] == NULL) {
802 int *glfs = mp_xmalloc(mp, 256, sizeof(int));
803 memset(glfs, 0, (256 * sizeof(int)));
804 mp_chars[(A)] = glfs;
806 mp_chars[(A)][(int)(B)] = 1;
807 } while (0)
809 @<Declarations@>=
810 void mp_svg_print_glyph_defs (MP mp, mp_edge_object *h);
812 @ @c
813 void mp_svg_print_glyph_defs (MP mp, mp_edge_object *h) {
814 mp_graphic_object *p; /* object index */
815 int k; /* general purpose index */
816 size_t l; /* a string length */
817 int **mp_chars = NULL; /* a twodimensional array of used glyphs */
818 mp_ps_font *f = NULL;
819 mp_edge_object *ch;
820 p = h->body;
821 while ( p!=NULL ) {
822 if ((gr_type(p) == mp_text_code) &&
823 (gr_font_n(p)!=null_font) &&
824 ((l = gr_text_l(p))>0) ) {
825 unsigned char *s = (unsigned char *)gr_text_p(p);
826 while (l-->0) {
827 do_mark(gr_font_n(p), *s);
828 s++;
831 p=gr_link(p);
833 if (mp_chars != NULL) {
834 mp_svg_starttag(mp,"defs");
835 for (k=0;k<=(int)mp->font_max;k++) {
836 if (mp_chars[k] != NULL ) {
837 double scale; /* the next gives rounding errors */
838 double ds,dx,sk;
839 ds =(mp->font_dsize[k]+8) / 16;
840 scale = (1/1000.0) * (ds);
841 ds = (scale);
842 dx = ds;
843 sk = 0;
844 for (l=0;l<256;l++) {
845 if (mp_chars[k][l] == 1) {
846 if (f == NULL) {
847 f = mp_ps_font_parse(mp, k);
848 if (f == NULL) continue;
849 if (f->extend != 0) {
850 dx = (((double)f->extend / 1000.0) * scale);
852 if (f->slant != 0) {
853 sk = (((double)f->slant / 1000.0) * 90);
856 mp_svg_open_starttag(mp,"g");
857 append_string("scale(");
858 mp_svg_store_double(mp,dx/65536);
859 append_char(',');
860 mp_svg_store_double(mp,ds/65536);
861 append_char(')');
862 if (sk!=0) {
863 append_string(" skewX(");
864 mp_svg_store_double(mp,-sk);
865 append_char(')');
867 mp_svg_attribute(mp, "transform", mp->svg->buf);
868 mp_svg_reset_buf(mp);
870 append_string("GLYPH");
871 append_string(mp->font_name[k]);
872 append_char('_');
873 mp_svg_store_int(mp, (int)l);
874 mp_svg_attribute(mp, "id", mp->svg->buf);
875 mp_svg_reset_buf(mp);
876 mp_svg_close_starttag(mp);
877 if (f != NULL) {
878 ch = mp_ps_font_charstring(mp,f,(int)l);
879 if (ch != NULL) {
880 p = ch->body;
881 mp_svg_open_starttag(mp,"path");
882 mp_svg_attribute(mp, "style", "fill-rule: evenodd;");
883 while (p!=NULL) {
884 if (mp->svg->loc>0) mp->svg->loc--; /* drop a '\0' */
885 mp_svg_font_path_out(mp, gr_path_p((mp_fill_object *)p));
886 p=p->next;
888 mp_svg_attribute(mp, "d", mp->svg->buf);
889 mp_svg_reset_buf(mp);
890 mp_svg_close_starttag(mp);
891 mp_svg_endtag(mp,"path",false);
893 mp_gr_toss_objects(ch);
895 mp_svg_endtag(mp,"g",true);
898 if (f!=NULL) { mp_ps_font_free(mp, f); f = NULL; }
901 mp_svg_endtag(mp,"defs", true);
903 /* cleanup */
904 for (k=0;k<(int)mp->font_max;k++) {
905 mp_xfree(mp_chars[k]);
907 mp_xfree(mp_chars);
912 @ Now for outputting the actual graphic objects.
914 @<Declarations@>=
915 static void mp_svg_text_out (MP mp, mp_text_object *p, int prologues) ;
917 @ @c
918 void mp_svg_text_out (MP mp, mp_text_object *p, int prologues) {
919 /* -Wunused: char *fname; */
920 unsigned char *s;
921 int k; /* a character */
922 size_t l; /* string length */
923 boolean transformed ;
924 double ds; /* design size and scale factor for a text node */
925 /* clang: never read: fname = mp->font_ps_name[gr_font_n(p)]; */
926 s = (unsigned char *)gr_text_p(p);
927 l = gr_text_l(p);
928 transformed=(gr_txx_val(p)!=unity)||(gr_tyy_val(p)!=unity)||
929 (gr_txy_val(p)!=0)||(gr_tyx_val(p)!=0);
930 mp_svg_open_starttag(mp, "g");
931 if ( transformed ) {
932 append_string("matrix(");
933 mp_svg_store_double(mp,gr_txx_val(p)); append_char(',');
934 mp_svg_store_double(mp,-gr_tyx_val(p)); append_char(',');
935 mp_svg_store_double(mp,-gr_txy_val(p)); append_char(',');
936 mp_svg_store_double(mp,gr_tyy_val(p)); append_char(',');
937 } else {
938 append_string("translate(");
940 mp_svg_pair_out(mp,gr_tx_val(p),gr_ty_val(p));
941 append_char(')');
943 mp_svg_attribute(mp, "transform", mp->svg->buf);
944 mp_svg_reset_buf(mp);
946 append_string("fill: ");
947 mp_svg_color_out(mp,(mp_graphic_object *)p);
948 append_char(';');
949 mp_svg_attribute(mp, "style", mp->svg->buf);
950 mp_svg_reset_buf(mp);
952 mp_svg_close_starttag(mp);
954 if (prologues == 3 ) {
956 double charwd;
957 double wd = 0.0; /* this is in PS design units */
958 while (l-->0) {
959 k=(int)*s++;
960 mp_svg_open_starttag(mp, "use");
961 append_string("#GLYPH");
962 append_string(mp->font_name[gr_font_n(p)]);
963 append_char('_');
964 mp_svg_store_int(mp,k);
965 mp_svg_attribute(mp,"xlink:href", mp->svg->buf);
966 mp_svg_reset_buf(mp);
967 charwd = ((wd/100));
968 if (charwd!=0) {
969 mp_svg_store_double(mp,charwd);
970 mp_svg_attribute(mp,"x", mp->svg->buf);
971 mp_svg_reset_buf(mp);
973 wd += mp_get_char_dimension (mp, mp->font_name[gr_font_n(p)], k, 'w');
974 mp_svg_close_starttag(mp);
975 mp_svg_endtag(mp, "use", false);
977 } else {
978 mp_svg_open_starttag(mp, "text");
979 ds=(mp->font_dsize[gr_font_n(p)]+8) / 16 / 65536.0;
980 mp_svg_store_double(mp,ds);
981 mp_svg_attribute(mp, "font-size", mp->svg->buf);
982 mp_svg_reset_buf(mp);
983 mp_svg_close_starttag(mp);
985 while (l-->0) {
986 k=(int)*s++;
987 if (@<Character |k| is illegal in SVG output@>) {
988 char S[100];
989 mp_snprintf(S,99,"The character %d cannot be output in SVG "
990 "unless prologues:=3;",k);
991 mp_warn(mp,S);
992 } else if ( (@<Character |k| is not allowed in SVG output@>) ) {
993 append_string("&#");
994 mp_svg_store_int(mp,k);
995 append_char(';');
996 } else {
997 append_char((char)k);
1000 mp_svg_print_buf(mp);
1001 mp_svg_endtag(mp, "text", false);
1003 mp_svg_endtag(mp, "g", true);
1006 @ When stroking a path with an elliptical pen, it is necessary to transform
1007 the coordinate system so that a unit circular pen will have the desired shape.
1008 To keep this transformation local, we enclose it in a $$\&{<g>}\ldots\&{</g>}$$
1009 block. Any translation component must be applied to the path being stroked
1010 while the rest of the transformation must apply only to the pen.
1011 If |fill_also=true|, the path is to be filled as well as stroked so we must
1012 insert commands to do this after giving the path.
1014 @<Declarations@>=
1015 static void mp_svg_stroke_out (MP mp, mp_graphic_object *h,
1016 mp_pen_info *pen, boolean fill_also) ;
1019 @ @c
1020 void mp_svg_stroke_out (MP mp, mp_graphic_object *h,
1021 mp_pen_info *pen, boolean fill_also) {
1022 boolean transformed = false;
1023 if (pen != NULL) {
1024 transformed = true;
1025 if ((pen->sx==unity) &&
1026 (pen->rx==0) &&
1027 (pen->ry==0) &&
1028 (pen->sy==unity) &&
1029 (pen->tx==0) &&
1030 (pen->ty==0)) {
1031 transformed = false;
1034 if (transformed) {
1035 mp_svg_open_starttag(mp, "g");
1036 append_string("matrix(");
1037 mp_svg_store_double(mp,pen->sx); append_char(',');
1038 mp_svg_store_double(mp,pen->rx); append_char(',');
1039 mp_svg_store_double(mp,pen->ry); append_char(',');
1040 mp_svg_store_double(mp,pen->sy); append_char(',');
1041 mp_svg_store_double(mp,pen->tx); append_char(',');
1042 mp_svg_store_double(mp,pen->ty);
1043 append_char(')');
1044 mp_svg_attribute(mp, "transform", mp->svg->buf);
1045 mp_svg_reset_buf(mp);
1046 mp_svg_close_starttag(mp);
1048 mp_svg_open_starttag(mp, "path");
1050 if (false) {
1051 if (transformed)
1052 mp_svg_path_trans_out(mp, gr_path_p((mp_fill_object *)h), pen);
1053 else
1054 mp_svg_path_out(mp, gr_path_p((mp_fill_object *)h));
1055 mp_svg_attribute(mp, "d", mp->svg->buf);
1056 mp_svg_reset_buf(mp);
1057 append_string("fill: ");
1058 mp_svg_color_out(mp,h);
1059 append_string("; stroke: none;");
1060 mp_svg_attribute(mp, "style", mp->svg->buf);
1061 mp_svg_reset_buf(mp);
1062 } else {
1063 if (transformed)
1064 mp_svg_path_trans_out(mp, gr_path_p((mp_stroked_object *)h), pen);
1065 else
1066 mp_svg_path_out(mp, gr_path_p((mp_stroked_object *)h));
1067 mp_svg_attribute(mp, "d", mp->svg->buf);
1068 mp_svg_reset_buf(mp);
1069 append_string("stroke:");
1070 mp_svg_color_out(mp,h);
1071 append_string("; stroke-width: ");
1072 if (pen != NULL) {
1073 mp_svg_store_double(mp, pen->ww);
1074 } else {
1075 append_char('0');
1077 append_char(';');
1078 if (gr_lcap_val(h)!=0) {
1079 append_string("stroke-linecap: ");
1080 switch (gr_lcap_val(h)) {
1081 case 1: append_string("round"); break;
1082 case 2: append_string("square"); break;
1083 default: append_string("butt"); break;
1085 append_char(';');
1087 if (gr_type(h)!=mp_fill_code) {
1088 mp_dash_object *hh;
1089 hh =gr_dash_p(h);
1090 if (hh != NULL && hh->array != NULL) {
1091 int i;
1092 append_string("stroke-dasharray: ");
1093 /* svg doesn't accept offsets */
1094 for (i=0; *(hh->array+i) != -1;i++) {
1095 mp_svg_store_double(mp, *(hh->array+i));
1096 append_char(' ') ;
1098 append_char(';');
1101 if (gr_ljoin_val((mp_stroked_object *)h)!=0) {
1102 append_string ("stroke-linejoin: ");
1103 switch (gr_ljoin_val((mp_stroked_object *)h)) {
1104 case 1: append_string("round"); break;
1105 case 2: append_string("bevel"); break;
1106 default: append_string("miter"); break;
1108 append_char(';');
1111 if (gr_miterlim_val((mp_stroked_object *)h) != 4*unity) {
1112 append_string("stroke-miterlimit: ");
1113 mp_svg_store_double(mp, gr_miterlim_val((mp_stroked_object *)h));
1114 append_char(';');
1118 append_string("fill: ");
1119 if (fill_also) {
1120 mp_svg_color_out(mp,h);
1121 } else {
1122 append_string("none");
1124 append_char(';');
1125 mp_svg_attribute(mp, "style", mp->svg->buf);
1126 mp_svg_reset_buf(mp);
1128 mp_svg_close_starttag(mp);
1129 mp_svg_endtag(mp, "path", false);
1130 if (transformed) {
1131 mp_svg_endtag(mp, "g", true);
1135 @ Here is a simple routine that just fills a cycle.
1137 @<Declarations@>=
1138 static void mp_svg_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h);
1140 @ @c
1141 void mp_svg_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h) {
1142 mp_svg_open_starttag(mp, "path");
1143 mp_svg_path_out(mp, p);
1144 mp_svg_attribute(mp, "d", mp->svg->buf);
1145 mp_svg_reset_buf(mp);
1146 append_string("fill: ");
1147 mp_svg_color_out(mp,h);
1148 append_string(";stroke: none;");
1149 mp_svg_attribute(mp, "style", mp->svg->buf);
1150 mp_svg_reset_buf(mp);
1151 mp_svg_close_starttag(mp); /* path */
1152 mp_svg_endtag(mp, "path", false);
1155 @ Clipping paths use IDs, so an extra global is needed:
1157 @<Globals...@>=
1158 int clipid;
1161 @<Set initial values@>=
1162 mp->svg->clipid = 0;
1164 @ @<Declarations@>=
1165 static void mp_svg_clip_out (MP mp, mp_clip_object *p);
1167 @ @c
1168 void mp_svg_clip_out (MP mp, mp_clip_object *p) {
1169 mp->svg->clipid++;
1170 mp_svg_starttag(mp, "g");
1171 mp_svg_starttag(mp, "defs");
1172 mp_svg_open_starttag(mp, "clipPath");
1174 append_string("CLIP");
1175 mp_svg_store_int(mp, mp->svg->clipid);
1176 mp_svg_attribute(mp, "id", mp->svg->buf);
1177 mp_svg_reset_buf(mp);
1179 mp_svg_close_starttag(mp);
1180 mp_svg_open_starttag(mp, "path");
1181 mp_svg_path_out(mp, gr_path_p(p));
1182 mp_svg_attribute(mp,"d", mp->svg->buf);
1183 mp_svg_reset_buf(mp);
1184 mp_svg_attribute(mp, "style", "fill: black; stroke: none;");
1185 mp_svg_close_starttag(mp); /* path */
1186 mp_svg_endtag(mp, "path", false);
1187 mp_svg_endtag(mp, "clipPath", true);
1188 mp_svg_endtag(mp, "defs", true);
1189 mp_svg_open_starttag(mp, "g");
1191 append_string("url(#CLIP");
1192 mp_svg_store_int(mp, mp->svg->clipid);
1193 append_string(");");
1194 mp_svg_attribute(mp, "clip-path", mp->svg->buf);
1195 mp_svg_reset_buf(mp);
1197 mp_svg_close_starttag(mp);
1202 @ The main output function
1204 @d gr_has_scripts(A) (gr_type((A))<mp_start_clip_code)
1205 @d pen_is_elliptical(A) ((A)==gr_next_knot((A)))
1207 @<Exported function ...@>=
1208 int mp_svg_gr_ship_out (mp_edge_object *hh, int prologues, int standalone) ;
1210 @ @c
1211 int mp_svg_gr_ship_out (mp_edge_object *hh, int qprologues, int standalone) {
1212 mp_graphic_object *p;
1213 mp_pen_info *pen = NULL;
1214 MP mp = hh->parent;
1215 if (standalone) {
1216 mp->jump_buf = malloc(sizeof(jmp_buf));
1217 if (mp->jump_buf == NULL || setjmp(*(mp->jump_buf)))
1218 return 0;
1220 if (mp->history >= mp_fatal_error_stop ) return 1;
1221 mp_open_output_file(mp);
1222 if ( (qprologues>=1) && (mp->last_ps_fnum==0) && mp->last_fnum>0)
1223 mp_read_psname_table(mp);
1224 /* The next seems counterintuitive, but calls from |mp_svg_ship_out|
1225 * set standalone to true, and because embedded use is likely, it is
1226 * better not to output the XML declaration in that case.
1228 if (!standalone)
1229 mp_svg_print(mp, "<?xml version=\"1.0\"?>");
1230 mp_svg_print_initial_comment(mp, hh);
1231 if (qprologues == 3) {
1232 mp_svg_print_glyph_defs(mp, hh);
1234 p = hh->body;
1235 while ( p!=NULL ) {
1236 if ( gr_has_scripts(p) ) {
1237 @<Write |pre_script| of |p|@>;
1239 switch (gr_type(p)) {
1240 case mp_fill_code:
1242 mp_fill_object *ph = (mp_fill_object *)p;
1243 if ( gr_pen_p(ph)==NULL ) {
1244 mp_svg_fill_out(mp, gr_path_p(ph), p);
1245 } else if ( pen_is_elliptical(gr_pen_p(ph)) ) {
1246 pen = mp_svg_pen_info(mp, gr_path_p(ph), gr_pen_p(ph));
1247 mp_svg_stroke_out(mp, p, pen, true);
1248 mp_xfree(pen);
1249 } else {
1250 mp_svg_fill_out(mp, gr_path_p(ph), p);
1251 mp_svg_fill_out(mp, gr_htap_p(ph), p);
1254 break;
1255 case mp_stroked_code:
1257 mp_stroked_object *ph = (mp_stroked_object *)p;
1258 if ( pen_is_elliptical(gr_pen_p(ph))) {
1259 pen = mp_svg_pen_info(mp, gr_path_p(ph), gr_pen_p(ph));
1260 mp_svg_stroke_out(mp, p, pen, false);
1261 mp_xfree(pen);
1262 } else {
1263 mp_svg_fill_out(mp, gr_path_p(ph), p);
1266 break;
1267 case mp_text_code:
1268 if ( (gr_font_n(p)!=null_font) && (gr_text_l(p)>0) ) {
1269 mp_svg_text_out(mp, (mp_text_object *)p, qprologues);
1271 break;
1272 case mp_start_clip_code:
1273 mp_svg_clip_out(mp, (mp_clip_object *)p);
1274 break;
1275 case mp_stop_clip_code:
1276 mp_svg_endtag(mp, "g", true);
1277 mp_svg_endtag(mp, "g", true);
1278 break;
1279 case mp_start_bounds_code:
1280 case mp_stop_bounds_code:
1281 break;
1282 case mp_special_code:
1284 mp_special_object *ps = (mp_special_object *)p;
1285 mp_svg_print_nl (mp, gr_pre_script(ps));
1286 mp_svg_print_ln (mp);
1288 break;
1289 } /* all cases are enumerated */
1290 if ( gr_has_scripts(p) ) {
1291 @<Write |post_script| of |p|@>;
1293 p=gr_link(p);
1295 mp_svg_endtag(mp, "svg", true);
1296 mp_svg_print_ln(mp);
1297 (mp->close_file)(mp,mp->output_file);
1298 return 1;
1301 @ @(mplibsvg.h@>=
1302 #ifndef MPLIBSVG_H
1303 #define MPLIBSVG_H 1
1304 #include "mplibps.h"
1305 int mp_svg_ship_out (mp_edge_object *hh, int prologues) ;
1306 #endif
1308 @ @c
1309 int mp_svg_ship_out (mp_edge_object *hh, int prologues) {
1310 return mp_svg_gr_ship_out (hh, prologues, (int)true);
1314 @d do_write_prescript(a,b) {
1315 if ( (gr_pre_script((b *)a))!=NULL ) {
1316 mp_svg_print_nl (mp, gr_pre_script((b *)a));
1317 mp_svg_print_ln(mp);
1321 @<Write |pre_script| of |p|@>=
1323 if (gr_type(p)==mp_fill_code) { do_write_prescript(p,mp_fill_object); }
1324 else if (gr_type(p)==mp_stroked_code) { do_write_prescript(p,mp_stroked_object); }
1325 else if (gr_type(p)==mp_text_code) { do_write_prescript(p,mp_text_object); }
1330 @d do_write_postscript(a,b) {
1331 if ( (gr_post_script((b *)a))!=NULL ) {
1332 mp_svg_print_nl (mp, gr_post_script((b *)a));
1333 mp_svg_print_ln(mp);
1337 @<Write |post_script| of |p|@>=
1339 if (gr_type(p)==mp_fill_code) { do_write_postscript(p,mp_fill_object); }
1340 else if (gr_type(p)==mp_stroked_code) { do_write_postscript(p,mp_stroked_object); }
1341 else if (gr_type(p)==mp_text_code) { do_write_postscript(p,mp_text_object); }