1 /* This file is part of the GNU plotutils package.  Copyright (C) 1995,
2    1996, 1997, 1998, 1999, 2000, 2005, 2008, Free Software Foundation, Inc.
3 
4    The GNU plotutils package is free software.  You may redistribute it
5    and/or modify it under the terms of the GNU General Public License as
6    published by the Free Software foundation; either version 2, or (at your
7    option) any later version.
8 
9    The GNU plotutils package is distributed in the hope that it will be
10    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with the GNU plotutils package; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor,
17    Boston, MA 02110-1301, USA. */
18 
19 /* This prints a single-font, single-font-size label.  When this is called,
20    the current point is on the intended baseline of the label.  */
21 
22 /* This version is for SVGPlotters.  We use the ISO-Latin-1 encoding, so <
23    > & and left-quote and double-quote are the only characters we need to
24    escape. */
25 
26 #include "sys-defines.h"
27 #include "extern.h"
28 
29 /* maximum length we support */
30 #define PL_MAX_SVG_STRING_LEN 256
31 
32 /* The fixed value we specify for the font-size parameter, when any font is
33    retrieved, in terms of `px'.  (We now scale as needed by choosing an
34    appropriate transformation matrix.)  According to the SVG Authoring
35    Guide, a `px' means simply a user-space unit, but some SVG renderers
36    (e.g., in Firefox) get confused if it's smaller than 1.0 or so, and
37    return absurdly scaled fonts.  Maybe they think px stands for pixels?
38    :-) */
39 #define PL_SVG_FONT_SIZE_IN_PX 20.0
40 
41 /* forward references */
42 static void write_svg_text_style (plOutbuf *page, const plDrawState *drawstate, int h_just, int v_just);
43 
44 typedef struct
45 {
46   char c;
47   const char *s;
48 }
49 plCharEscape;
50 
51 #define NUM_SVG_CHAR_ESCAPES 5
52 static const plCharEscape _svg_char_escapes[NUM_SVG_CHAR_ESCAPES] =
53 {
54   {'\'', "apos"},
55   {'"', "quot"},
56   {'&', "amp"},
57   {'<', "lt"},
58   {'>', "gt"}
59 };
60 #define MAX_SVG_CHAR_ESCAPE_LEN 4 /* i.e., length of "apos" or "quot" */
61 
62 /* SVG horizontal alignment styles, i.e., text-anchor attribute, indexed by
63    internal number (left/center/right) */
64 static const char * const svg_horizontal_alignment_style[PL_NUM_HORIZ_JUST_TYPES] =
65 { "start", "middle", "end" };
66 
67 /* SVG vertical alignment styles, i.e., alignment-baseline attribute,
68    indexed by internal number (top/half/base/bottom/cap) */
69 static const char * const svg_vertical_alignment_style[PL_NUM_VERT_JUST_TYPES] =
70 { "text-before-edge", "central", "alphabetic", "text-after-edge", "hanging" };
71 /* This version of the paint_text_string method, for SVG Plotters, supports
72    each of libplot's possible vertical justifications (see the list
73    immediately above).  However, only the `baseline' justification is
74    currently used.  That's because in s_defplot.c we set the Plotter
75    parameter `have_vertical_justification' to false.  Too many SVG
76    renderers don't support the SVG alignment-baseline attribute; e.g.,
77    Firefox 1.5 doesn't.  So it's best for libplot to do its own vertical
78    positioning of text strings. */
79 
80 double
_pl_s_paint_text_string(R___ (Plotter * _plotter)const unsigned char * s,int h_just,int v_just)81 _pl_s_paint_text_string (R___(Plotter *_plotter) const unsigned char *s, int h_just, int v_just)
82 {
83   const unsigned char *sp = s;
84   unsigned char *t, *tp;
85   int i, n = 0;
86   double local_matrix[6];
87   double angle = _plotter->drawstate->text_rotation;
88 
89   /* replace certain printable ASCII characters by entities */
90   tp = t = (unsigned char *)_pl_xmalloc ((2 + MAX_SVG_CHAR_ESCAPE_LEN) * strlen ((const char *)s) + 1);
91   while (*sp && n < PL_MAX_SVG_STRING_LEN)
92     {
93       bool matched;
94       int i;
95 
96       matched = false;
97       for (i = 0; i < NUM_SVG_CHAR_ESCAPES; i++)
98 	{
99 	  if (*sp == (unsigned char)_svg_char_escapes[i].c)
100 	    {
101 	      matched = true;
102 	      break;
103 	    }
104 	}
105       if (matched)
106 	{
107 	  *tp++ = (unsigned char)'&';
108 	  strcpy ((char *)tp, _svg_char_escapes[i].s);
109 	  tp += strlen (_svg_char_escapes[i].s);
110 	  *tp++ = (unsigned char)';';
111 	}
112       else
113 	*tp++ = *sp;
114 
115       sp++;
116       n++;
117     }
118   *tp = '\0';
119 
120   sprintf (_plotter->data->page->point, "<text ");
121   _update_buffer (_plotter->data->page);
122 
123   /* CTM equals CTM_local * CTM_base, if matrix multiplication is defined
124      as in PS and libplot. (Which is the opposite of the SVG convention,
125      since SVG documentation uses column vectors instead of row vectors, so
126      that the CTM is effectively transposed.  Although SVG's matrix()
127      construct uses PS order for the six matrix elements... go figure.)
128 
129      Here CTM_local rotates by the libplot's text angle parameter, and
130      translates to the correct position.  And CTM_base is libplot's current
131      user_to_ndc transformation matrix.  We separate them because we use
132      the CTM of the first-plotted object on the page as the page's global
133      transformation matrix, and if that object happens to be a text object,
134      we'd like it to simply to be the current user_to_ndc transformation
135      matrix, i.e. not to include irrelevancies such as the text position
136      and angle.
137 
138      Sigh... If only things were so simple.  SVG's native coordinate frame,
139      which libplot's user coordinates must ultimately be mapped to,
140      unfortunately uses a flipped-y convention, unlike PS and libplot.
141      (The global flipping of y, relative to libplot's NDC coordinates, is
142      accomplished by a scale(1,-1) that's placed at the head of the SVG
143      file; see s_output.c.)  This flipping has a special effect on the
144      drawing of text strings, though no other libplot primitive.  For
145      everything to work out when drawing a text string, we must precede the
146      sequence of transformations leading from user coordinates to native
147      SVG coordinates by an initial scale(1,-1).  CTM_local, as defined
148      above, must have two elements sign-flipped (see below).  Trust me. */
149 
150   local_matrix[0] = cos (M_PI * angle / 180.0);
151   local_matrix[1] = sin (M_PI * angle / 180.0);
152   local_matrix[2] = -sin (M_PI * angle / 180.0) * (-1);	/* SEE ABOVE */
153   local_matrix[3] = cos (M_PI * angle / 180.0) * (-1); /* SEE ABOVE */
154 
155   /* since we now specify a fixed font-size, equal to PL_SVG_FONT_SIZE_IN_PX
156      (see below), rather than specifying a font size equal to the
157      font size in user units, we must here scale the text string to
158      the right size */
159   for (i = 0; i < 4; i++)
160     local_matrix[i] *= (_plotter->drawstate->font_size
161 			/ PL_SVG_FONT_SIZE_IN_PX);
162 
163   local_matrix[4] = _plotter->drawstate->pos.x;
164   local_matrix[5] = _plotter->drawstate->pos.y;
165   _pl_s_set_matrix (R___(_plotter) local_matrix);
166 
167   write_svg_text_style (_plotter->data->page, _plotter->drawstate,
168 			 h_just, v_just);
169 
170   sprintf (_plotter->data->page->point, ">");
171   _update_buffer (_plotter->data->page);
172 
173   sprintf (_plotter->data->page->point, "%s",
174 	   (char *)t);
175   _update_buffer (_plotter->data->page);
176 
177   sprintf (_plotter->data->page->point, "</text>\n");
178   _update_buffer (_plotter->data->page);
179 
180   free (t);
181 
182   return _plotter->get_text_width (R___(_plotter) s);
183 }
184 
185 static void
write_svg_text_style(plOutbuf * page,const plDrawState * drawstate,int h_just,int v_just)186 write_svg_text_style (plOutbuf *page, const plDrawState *drawstate, int h_just, int v_just)
187 {
188   const char *ps_name, *css_family, *css_generic_family; /* last may be NULL */
189   const char *css_style, *css_weight, *css_stretch;
190   bool css_family_is_ps_name;
191   char color_buf[8];		/* enough room for "#ffffff", incl. NUL */
192 
193   /* extract official PS font name, and CSS font specification, from master
194      table of PS [or PCL] fonts, in g_fontdb.c */
195   switch (drawstate->font_type)
196     {
197       int master_font_index;
198 
199     case PL_F_POSTSCRIPT:
200       master_font_index =
201 	(_pl_g_ps_typeface_info[drawstate->typeface_index].fonts)[drawstate->font_index];
202       ps_name = _pl_g_ps_font_info[master_font_index].ps_name;
203       css_family = _pl_g_ps_font_info[master_font_index].css_family;
204       css_generic_family = _pl_g_ps_font_info[master_font_index].css_generic_family;
205       css_style = _pl_g_ps_font_info[master_font_index].css_style;
206       css_weight = _pl_g_ps_font_info[master_font_index].css_weight;
207       css_stretch = _pl_g_ps_font_info[master_font_index].css_stretch;
208 
209       /* flag this font as used */
210       page->ps_font_used[master_font_index] = true;
211 
212       break;
213     case PL_F_PCL:
214       master_font_index =
215 	(_pl_g_pcl_typeface_info[drawstate->typeface_index].fonts)[drawstate->font_index];
216       ps_name = _pl_g_pcl_font_info[master_font_index].ps_name;
217       css_family = _pl_g_pcl_font_info[master_font_index].css_family;
218       css_generic_family = _pl_g_pcl_font_info[master_font_index].css_generic_family;
219       css_style = _pl_g_pcl_font_info[master_font_index].css_style;
220       css_weight = _pl_g_pcl_font_info[master_font_index].css_weight;
221       css_stretch = _pl_g_pcl_font_info[master_font_index].css_stretch;
222 
223       /* flag this font as used */
224       page->pcl_font_used[master_font_index] = true;
225 
226       break;
227     default:			/* shouldn't happen */
228       return;
229       break;
230     }
231 
232   if (strcmp (ps_name, css_family) == 0)
233     /* no need to specify both */
234     css_family_is_ps_name = true;
235   else
236     css_family_is_ps_name = false;
237 
238   /* N.B. In each of the following four sprintf()'s, we should apparently
239      enclose css_family in single quotes, at least if it contains a space.
240      But doing so would cause the SVG renderer in `display', which is part
241      of the ImageMagick package, to reject the emitted SVG file. */
242 
243   if (css_generic_family)
244     {
245       if (css_family_is_ps_name)
246 	sprintf (page->point, "font-family=\"%s,%s\" ",
247 		 css_family, css_generic_family);
248       else
249 	sprintf (page->point, "font-family=\"%s,%s,%s\" ",
250 		 ps_name, css_family, css_generic_family);
251     }
252   else
253     {
254       if (css_family_is_ps_name)
255 	sprintf (page->point, "font-family=\"%s\" ",
256 		 css_family);
257       else
258 	sprintf (page->point, "font-family=\"%s,%s\" ",
259 		 ps_name, css_family);
260     }
261   _update_buffer (page);
262 
263   if (strcmp (css_style, "normal") != 0) /* not default */
264     {
265       sprintf (page->point, "font-style=\"%s\" ",
266 	       css_style);
267       _update_buffer (page);
268     }
269 
270   if (strcmp (css_weight, "normal") != 0) /* not default */
271     {
272       sprintf (page->point, "font-weight=\"%s\" ",
273 	       css_weight);
274       _update_buffer (page);
275     }
276 
277   if (strcmp (css_stretch, "normal") != 0) /* not default */
278     {
279       sprintf (page->point, "font-stretch=\"%s\" ",
280 	       css_stretch);
281       _update_buffer (page);
282     }
283 
284   sprintf (page->point, "font-size=\"%.5gpx\" ",
285 	   /* see comments above for why we don't simply specify
286 	      drawstate->font_size here */
287 	   PL_SVG_FONT_SIZE_IN_PX);
288   _update_buffer (page);
289 
290   if (h_just != PL_JUST_LEFT)	/* not default */
291     {
292       sprintf (page->point, "text-anchor=\"%s\" ",
293 	       svg_horizontal_alignment_style[h_just]);
294       _update_buffer (page);
295     }
296 
297   if (v_just != PL_JUST_BASE)	/* not default */
298     {
299       sprintf (page->point, "alignment-baseline=\"%s\" ",
300 	       svg_vertical_alignment_style[v_just]);
301       _update_buffer (page);
302     }
303 
304   /* currently, we never draw character outlines; we only fill */
305   sprintf (page->point, "stroke=\"none\" ");
306   _update_buffer (page);
307 
308   if (drawstate->pen_type)
309     /* according to libplot convention, text should be filled, and since
310        SVG's default filling is "none", we must say so */
311     {
312       sprintf (page->point, "fill=\"%s\" ",
313 	       _libplot_color_to_svg_color (drawstate->fgcolor, color_buf));
314       _update_buffer (page);
315     }
316 }
317