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
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
}
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
)
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
>
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@
>
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|.
84 typedef struct svgout_data_struct
{
86 } svgout_data_struct
;
87 @
<Exported function headers@
>
90 @ @
<Exported function headers@
>=
91 void mp_svg_backend_initialize
(MP mp
) ;
92 void mp_svg_backend_free
(MP mp
) ;
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
);
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.
114 @ @
<Set initial values@
>=
115 mp-
>svg-
>file_offset
= 0;
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
) {
130 ss
[0]=(char
)s
; ss
[1]=0;
131 (mp-
>write_ascii_file
)(mp
,mp-
>output_file
,(char
*)ss
);
132 mp-
>svg-
>file_offset
++;
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 )
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|.
168 @ Start with a modest size of
256. the buffer will grow automatically
171 @
<Set initial values@
>=
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
)) {
185 l
= (unsigned
)(mp-
>svg-
>bufsize
+(mp-
>svg-
>bufsize
>>4));
187 mp_confusion
(mp
,"svg buffer size");
189 buffer
= mp_xmalloc
(mp
,l
,1);
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
);
199 @d append_string
(A
) do
{
200 const char
*ss
= (A
);
201 while
(*ss
!= '\
0'
) { append_char
(*ss
); ss
++ ;}
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
) {
211 memset
(mp-
>svg-
>buf
,0,mp-
>svg-
>bufsize
);
214 @ Printing the buffer is a matter of printing its string
, then
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}$
*/
234 if
( n
>-100000000 ) {
237 m
=-1-n
; n
=m
/ 10; m
=(m
% 10)+1; k
=1;
239 dig
[0]=(unsigned char
)m
;
246 dig
[k
]=(unsigned char
)(n
% 10); n
=n
/ 10; incr
(k
);
248 /* print the digits
*/
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
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
) {
281 value
= mp_xmalloc
(mp
,1,32);
282 mp_snprintf
(value
,32,"%f", s
);
294 In order to create a nicely indented output file
, the current tag
295 nesting level needs to be remembered.
300 @ @
<Set initial values@
>=
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;
319 mp_svg_print_buf
(mp
);
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
334 static void mp_svg_endtag
(MP mp
, const char
*s
, boolean indent
) {
337 int l
= mp-
>svg-
>level
* 2;
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
, ' '
);
356 mp_svg_print
(mp
,"=\"");
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
:
389 void mp_svg_pair_out
(MP mp
,double x
, double y
) {
390 mp_svg_store_double
(mp
, (x
+mp-
>svg-
>dx
));
392 mp_svg_store_double
(mp
, (-(y
+mp-
>svg-
>dy
)));
396 void mp_svg_font_pair_out
(MP mp
,double x
, double y
) ;
399 void mp_svg_font_pair_out
(MP mp
,double x
, double y
) {
400 mp_svg_store_double
(mp
, (x
));
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.
414 void mp_svg_trans_pair_out
(MP mp
, mp_pen_info
*pen
, double x
, double y
) ;
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
;
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
));
429 retval
= (sx
*py-rx
*px
)/divider
;
430 mp_svg_store_double
(mp
, (retval
));
436 static void mp_svg_pair_out
(MP mp
,double x
, double y
) ;
440 static void mp_svg_print_initial_comment
(MP mp
,mp_edge_object
*hh
);
443 void mp_svg_print_initial_comment
(MP mp
,mp_edge_object
*hh
) {
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
) {
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 @
>=
485 mp_svg_print_nl
(mp
, "<!-- Created by MetaPost ");
486 s
= mp_metapost_version
();
489 mp_svg_print
(mp
, " on ");
490 mp_svg_store_int
(mp
, round_unscaled
(internal_value
(mp_year
)));
492 mp_svg_store_dd
(mp
, round_unscaled
(internal_value
(mp_month
)));
494 mp_svg_store_dd
(mp
, round_unscaled
(internal_value
(mp_day
)));
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
);
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");
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
) {
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));
547 mp_svg_store_double
(mp
, (object_color_b
* 100));
550 mp_svg_store_double
(mp
, (object_color_c
* 100));
557 static void mp_svg_color_out
(MP mp
, mp_graphic_object
*p
);
559 @ This is the information that comes from a pen
562 typedef struct mp_pen_info
{
564 double sx
, rx
, ry
, sy
;
569 @
(Re
)discover the characteristics of an elliptical pen
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)
582 static double coord_range_x
(mp_gr_knot h
, double dz
) {
584 double zlo
= 0, zhi
= 0;
588 if
(z
< zlo
) zlo
= z
; else if
(z
> zhi
) zhi
= z
;
590 if
(z
< zlo
) zlo
= z
; else if
(z
> zhi
) zhi
= z
;
592 if
(z
< zlo
) zlo
= z
; else if
(z
> zhi
) zhi
= z
;
597 return
(zhi
- zlo
<= dz ? aspect_bound
: aspect_default
);
599 static double coord_range_y
(mp_gr_knot h
, double dz
) {
601 double zlo
= 0, zhi
= 0;
605 if
(z
< zlo
) zlo
= z
; else if
(z
> zhi
) zhi
= z
;
607 if
(z
< zlo
) zlo
= z
; else if
(z
> zhi
) zhi
= z
;
609 if
(z
< zlo
) zlo
= z
; else if
(z
> zhi
) zhi
= z
;
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
*/
624 pen
= mp_xmalloc
(mp
, 1, sizeof
(mp_pen_info
));
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
));
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
)))
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
) {
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
;
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|.
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
) )
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
)
701 static void mp_svg_path_out
(MP mp
, mp_gr_knot h
) {
702 mp_gr_knot p
, q
; /* for scanning the path
*/
704 mp_svg_pair_out
(mp
, gr_x_coord
(h
),gr_y_coord
(h
));
707 if
( gr_right_type
(p
)==mp_endpoint
) {
709 append_string
("l0 0");
714 if
(mp_is_curved
(p
, q
)){
716 mp_svg_pair_out
(mp
, gr_right_x
(p
),gr_right_y
(p
));
718 mp_svg_pair_out
(mp
, gr_left_x
(q
),gr_left_y
(q
));
720 mp_svg_pair_out
(mp
, gr_x_coord
(q
),gr_y_coord
(q
));
723 mp_svg_pair_out
(mp
, gr_x_coord
(q
),gr_y_coord
(q
));
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
*/
735 mp_svg_trans_pair_out
(mp
, pen
, gr_x_coord
(h
),gr_y_coord
(h
));
738 if
( gr_right_type
(p
)==mp_endpoint
) {
740 append_string
("l0 0");
745 if
(mp_is_curved
(p
, q
)){
747 mp_svg_trans_pair_out
(mp
, pen
, gr_right_x
(p
),gr_right_y
(p
));
749 mp_svg_trans_pair_out
(mp
, pen
,gr_left_x
(q
),gr_left_y
(q
));
751 mp_svg_trans_pair_out
(mp
, pen
,gr_x_coord
(q
),gr_y_coord
(q
));
754 mp_svg_trans_pair_out
(mp
, pen
,gr_x_coord
(q
),gr_y_coord
(q
));
764 static void mp_svg_font_path_out
(MP mp
, mp_gr_knot h
) {
765 mp_gr_knot p
, q
; /* for scanning the path
*/
767 mp_svg_font_pair_out
(mp
, gr_x_coord
(h
),gr_y_coord
(h
));
770 if
( gr_right_type
(p
)==mp_endpoint
) {
773 mp_svg_font_pair_out
(mp
, 0, 0);
778 if
(mp_is_curved
(p
, q
)){
780 mp_svg_font_pair_out
(mp
, gr_right_x
(p
),gr_right_y
(p
));
782 mp_svg_font_pair_out
(mp
, gr_left_x
(q
),gr_left_y
(q
));
784 mp_svg_font_pair_out
(mp
, gr_x_coord
(q
),gr_y_coord
(q
));
787 mp_svg_font_pair_out
(mp
, gr_x_coord
(q
),gr_y_coord
(q
));
794 @ If |prologues
:=3|
, any glyphs in labels will be converted into paths.
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;
810 void mp_svg_print_glyph_defs
(MP mp
, mp_edge_object
*h
);
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;
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
);
827 do_mark
(gr_font_n
(p
), *s
);
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
*/
839 ds
=(mp-
>font_dsize
[k
]+8) / 16;
840 scale
= (1/1000.0) * (ds
);
844 for
(l
=0;l
<256;l
++) {
845 if
(mp_chars
[k
][l
] == 1) {
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
);
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);
860 mp_svg_store_double
(mp
,ds
/65536);
863 append_string
(" skewX(");
864 mp_svg_store_double
(mp
,-sk
);
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
]);
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
);
878 ch
= mp_ps_font_charstring
(mp
,f
,(int
)l
);
881 mp_svg_open_starttag
(mp
,"path");
882 mp_svg_attribute
(mp
, "style", "fill-rule: evenodd;");
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
));
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
);
904 for
(k
=0;k
<(int
)mp-
>font_max
;k
++) {
905 mp_xfree
(mp_chars
[k
]);
912 @ Now for outputting the actual graphic objects.
915 static void mp_svg_text_out
(MP mp
, mp_text_object
*p
, int prologues
) ;
918 void mp_svg_text_out
(MP mp
, mp_text_object
*p
, int prologues
) {
919 /* -Wunused
: char
*fname
; */
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
);
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");
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
('
,'
);
938 append_string
("translate(");
940 mp_svg_pair_out
(mp
,gr_tx_val
(p
),gr_ty_val
(p
));
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
);
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 ) {
957 double wd
= 0.0; /* this is in PS design units
*/
960 mp_svg_open_starttag
(mp
, "use");
961 append_string
("#GLYPH");
962 append_string
(mp-
>font_name
[gr_font_n
(p
)]);
964 mp_svg_store_int
(mp
,k
);
965 mp_svg_attribute
(mp
,"xlink:href", mp-
>svg-
>buf
);
966 mp_svg_reset_buf
(mp
);
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
);
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
);
987 if
(@
<Character |k| is illegal in SVG output@
>) {
989 mp_snprintf
(S
,99,"The character %d cannot be output in SVG "
990 "unless prologues:=3;",k
);
992 } else if
( (@
<Character |k| is not allowed in SVG output@
>) ) {
994 mp_svg_store_int
(mp
,k
);
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.
1015 static void mp_svg_stroke_out
(MP mp
, mp_graphic_object
*h
,
1016 mp_pen_info
*pen
, boolean fill_also
) ;
1020 void mp_svg_stroke_out
(MP mp
, mp_graphic_object
*h
,
1021 mp_pen_info
*pen
, boolean fill_also
) {
1022 boolean transformed
= false
;
1025 if
((pen-
>sx
==unity
) &&
1031 transformed
= false
;
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
);
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");
1052 mp_svg_path_trans_out
(mp
, gr_path_p
((mp_fill_object
*)h
), pen
);
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
);
1064 mp_svg_path_trans_out
(mp
, gr_path_p
((mp_stroked_object
*)h
), pen
);
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: ");
1073 mp_svg_store_double
(mp
, pen-
>ww
);
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
;
1087 if
(gr_type
(h
)!=mp_fill_code
) {
1090 if
(hh
!= NULL && hh->array != NULL) {
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
));
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
;
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
));
1118 append_string
("fill: ");
1120 mp_svg_color_out
(mp
,h
);
1122 append_string
("none");
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
);
1131 mp_svg_endtag
(mp
, "g", true
);
1135 @ Here is a simple routine that just fills a cycle.
1138 static void mp_svg_fill_out
(MP mp
, mp_gr_knot p
, mp_graphic_object
*h
);
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
:
1161 @
<Set initial values@
>=
1162 mp-
>svg-
>clipid
= 0;
1165 static void mp_svg_clip_out
(MP mp
, mp_clip_object
*p
);
1168 void mp_svg_clip_out
(MP mp
, mp_clip_object
*p
) {
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
) ;
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;
1216 mp-
>jump_buf
= malloc
(sizeof
(jmp_buf
));
1217 if
(mp-
>jump_buf
== NULL || setjmp
(*(mp-
>jump_buf
)))
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.
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
);
1236 if
( gr_has_scripts
(p
) ) {
1237 @
<Write |pre_script| of |p|@
>;
1239 switch
(gr_type
(p
)) {
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
);
1250 mp_svg_fill_out
(mp
, gr_path_p
(ph
), p
);
1251 mp_svg_fill_out
(mp
, gr_htap_p
(ph
), p
);
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
);
1263 mp_svg_fill_out
(mp
, gr_path_p
(ph
), p
);
1268 if
( (gr_font_n
(p
)!=null_font
) && (gr_text_l(p)>0) ) {
1269 mp_svg_text_out
(mp
, (mp_text_object
*)p
, qprologues
);
1272 case mp_start_clip_code
:
1273 mp_svg_clip_out
(mp
, (mp_clip_object
*)p
);
1275 case mp_stop_clip_code
:
1276 mp_svg_endtag
(mp
, "g", true
);
1277 mp_svg_endtag
(mp
, "g", true
);
1279 case mp_start_bounds_code
:
1280 case mp_stop_bounds_code
:
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
);
1289 } /* all cases are enumerated
*/
1290 if
( gr_has_scripts
(p
) ) {
1291 @
<Write |post_script| of |p|@
>;
1295 mp_svg_endtag
(mp
, "svg", true
);
1296 mp_svg_print_ln
(mp
);
1297 (mp-
>close_file
)(mp
,mp-
>output_file
);
1303 #define MPLIBSVG_H
1
1304 #include
"mplibps.h"
1305 int mp_svg_ship_out
(mp_edge_object
*hh
, int prologues
) ;
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
); }