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