1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3 Written by Ben Pfaff <blp@gnu.org>.
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 #include <libpspp/message.h>
27 #include <libpspp/alloc.h>
28 #include <libpspp/message.h>
29 #include <data/filename.h>
32 #include <libpspp/misc.h>
33 #include <data/settings.h>
34 #include <libpspp/str.h>
37 #define _(msgid) gettext (msgid)
39 /* FIXME? Should the output configuration format be changed to
40 drivername:classname:devicetype:options, where devicetype is zero
41 or more of screen, printer, listing? */
43 /* FIXME: Have the reentrancy problems been solved? */
45 /* Where the output driver name came from. */
48 OUTP_S_COMMAND_LINE, /* Specified by the user. */
49 OUTP_S_INIT_FILE /* `default' or the init file. */
52 /* Names the output drivers to be used. */
55 char *name; /* Name of the output driver. */
56 int source; /* OUTP_S_* */
57 struct outp_names *next, *prev;
60 /* Defines an init file macro. */
65 struct outp_defn *next, *prev;
68 static struct outp_defn *outp_macros;
69 static struct outp_names *outp_configure_vec;
71 struct outp_driver_class_list *outp_class_list;
72 struct outp_driver *outp_driver_list;
77 /* A set of OUTP_DEV_* bits indicating the devices that are
79 static int disabled_devices;
81 static void destroy_driver (struct outp_driver *);
82 static void configure_driver_line (struct string *);
83 static void configure_driver (const char *, const char *,
84 const char *, const char *);
86 /* Add a class to the class list. */
88 add_class (struct outp_class *class)
90 struct outp_driver_class_list *new_list = xmalloc (sizeof *new_list);
92 new_list->class = class;
93 new_list->ref_count = 0;
97 outp_class_list = new_list;
98 new_list->next = NULL;
102 new_list->next = outp_class_list;
103 outp_class_list = new_list;
107 /* Finds the outp_names in outp_configure_vec with name between BP and
109 static struct outp_names *
110 search_names (char *bp, char *ep)
112 struct outp_names *n;
114 for (n = outp_configure_vec; n; n = n->next)
115 if ((int) strlen (n->name) == ep - bp && !memcmp (n->name, bp, ep - bp))
120 /* Deletes outp_names NAME from outp_configure_vec. */
122 delete_name (struct outp_names * n)
126 n->prev->next = n->next;
128 n->next->prev = n->prev;
129 if (n == outp_configure_vec)
130 outp_configure_vec = n->next;
134 /* Adds the name between BP and EP exclusive to list
135 outp_configure_vec with source SOURCE. */
137 add_name (char *bp, char *ep, int source)
139 struct outp_names *n = xmalloc (sizeof *n);
140 n->name = xmalloc (ep - bp + 1);
141 memcpy (n->name, bp, ep - bp);
142 n->name[ep - bp] = 0;
144 n->next = outp_configure_vec;
146 if (outp_configure_vec)
147 outp_configure_vec->prev = n;
148 outp_configure_vec = n;
151 /* Checks that outp_configure_vec is empty, bitches & clears it if it
154 check_configure_vec (void)
156 struct outp_names *n;
158 for (n = outp_configure_vec; n; n = n->next)
159 if (n->source == OUTP_S_COMMAND_LINE)
160 msg (ME, _("Unknown output driver `%s'."), n->name);
162 msg (IE, _("Output driver `%s' referenced but never defined."), n->name);
163 outp_configure_clear ();
166 /* Searches outp_configure_vec for the name between BP and EP
167 exclusive. If found, it is deleted, then replaced by the names
168 given in EP+1, if any. */
170 expand_name (char *bp, char *ep)
172 struct outp_names *n = search_names (bp, ep);
180 while (isspace ((unsigned char) *bp))
183 while (*ep && !isspace ((unsigned char) *ep))
187 if (!search_names (bp, ep))
188 add_name (bp, ep, OUTP_S_INIT_FILE);
193 /* Looks for a macro with key KEY, and returns the corresponding value
194 if found, or NULL if not. */
196 find_defn_value (const char *key)
198 static char buf[INT_STRLEN_BOUND (int) + 1];
201 for (d = outp_macros; d; d = d->next)
202 if (!strcmp (key, d->key))
203 return ds_c_str(&d->value);
204 if (!strcmp (key, "viewwidth"))
206 sprintf (buf, "%d", get_viewwidth ());
209 else if (!strcmp (key, "viewlength"))
211 sprintf (buf, "%d", get_viewlength ());
218 /* Initializes global variables. */
222 extern struct outp_class ascii_class;
223 extern struct outp_class postscript_class;
224 extern struct outp_class epsf_class;
225 extern struct outp_class html_class;
227 char def[] = "default";
229 add_class (&html_class);
230 add_class (&epsf_class);
231 add_class (&postscript_class);
232 add_class (&ascii_class);
234 add_name (def, &def[strlen (def)], OUTP_S_INIT_FILE);
237 /* Deletes all the output macros. */
241 struct outp_defn *d, *next;
243 for (d = outp_macros; d; d = next)
247 ds_destroy (&d->value);
253 init_default_drivers (void)
255 msg (MM, _("Using default output driver configuration."));
256 configure_driver ("list-ascii", "ascii", "listing",
257 "length=66 width=79 char-set=ascii "
258 "output-file=\"pspp.list\" "
259 "bold-on=\"\" italic-on=\"\" bold-italic-on=\"\"");
262 /* Reads the initialization file; initializes
265 outp_read_devices (void)
273 struct file_locator where;
275 init_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_INIT_FILE",
277 fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
280 where.filename = init_fn;
281 where.line_number = 0;
282 err_push_file_locator (&where);
284 ds_init (&line, 128);
288 msg (IE, _("Cannot find output initialization file. "
289 "Use `-vvvvv' to view search path."));
293 msg (VM (1), _("%s: Opening device description file..."), init_fn);
294 f = fopen (init_fn, "r");
297 msg (IE, _("Opening %s: %s."), init_fn, strerror (errno));
305 if (!ds_get_config_line (f, &line, &where))
308 msg (ME, _("Reading %s: %s."), init_fn, strerror (errno));
311 for (cp = ds_c_str (&line); isspace ((unsigned char) *cp); cp++);
312 if (!strncmp ("define", cp, 6) && isspace ((unsigned char) cp[6]))
313 outp_configure_macro (&cp[7]);
317 for (ep = cp; *ep && *ep != ':' && *ep != '='; ep++);
319 expand_name (cp, ep);
322 struct outp_names *n = search_names (cp, ep);
325 configure_driver_line (&line);
330 msg (IS, _("Syntax error."));
335 check_configure_vec ();
338 err_pop_file_locator (&where);
339 if (f && -1 == fclose (f))
340 msg (MW, _("Closing %s: %s."), init_fn, strerror (errno));
347 msg (VM (2), _("Device definition file read successfully."));
348 if (outp_driver_list == NULL)
349 msg (MW, _("No output drivers are active."));
352 msg (VM (1), _("Error reading device definition file."));
354 if (!result || outp_driver_list == NULL)
355 init_default_drivers ();
358 /* Clear the list of drivers to configure. */
360 outp_configure_clear (void)
362 struct outp_names *n, *next;
364 for (n = outp_configure_vec; n; n = next)
370 outp_configure_vec = NULL;
373 /* Adds the name BP to the list of drivers to configure into
376 outp_configure_add (char *bp)
378 char *ep = &bp[strlen (bp)];
379 if (!search_names (bp, ep))
380 add_name (bp, ep, OUTP_S_COMMAND_LINE);
383 /* Defines one configuration macro based on the text in BP, which
384 should be of the form `KEY=VALUE'. */
386 outp_configure_macro (char *bp)
391 while (isspace ((unsigned char) *bp))
394 while (*ep && !isspace ((unsigned char) *ep) && *ep != '=')
397 d = xmalloc (sizeof *d);
398 d->key = xmalloc (ep - bp + 1);
399 memcpy (d->key, bp, ep - bp);
402 /* Earlier definitions for a particular KEY override later ones. */
403 if (find_defn_value (d->key))
412 while (isspace ((unsigned char) *ep))
415 ds_create(&d->value, ep);
416 fn_interp_vars(&d->value, find_defn_value);
417 d->next = outp_macros;
420 outp_macros->prev = d;
424 /* Destroys all the drivers in driver list *DL and sets *DL to
427 destroy_list (struct outp_driver ** dl)
429 struct outp_driver *d, *next;
431 for (d = *dl; d; d = next)
440 /* Closes all the output drivers. */
444 struct outp_driver_class_list *n = outp_class_list ;
445 destroy_list (&outp_driver_list);
449 struct outp_driver_class_list *next = n->next;
453 outp_class_list = NULL;
458 free (outp_subtitle);
459 outp_subtitle = NULL;
462 /* Display on stdout a list of all registered driver classes. */
464 outp_list_classes (void)
466 int width = get_viewwidth();
467 struct outp_driver_class_list *c;
469 printf (_("Driver classes:\n\t"));
471 for (c = outp_class_list; c; c = c->next)
473 if ((int) strlen (c->class->name) + 1 > width)
476 width = get_viewwidth() - 8;
480 fputs (c->class->name, stdout);
485 static int op_token; /* `=', 'a', 0. */
486 static struct string op_tokstr;
487 static const char *prog;
489 /* Parses a token from prog into op_token, op_tokstr. Sets op_token
490 to '=' on an equals sign, to 'a' on a string or identifier token,
491 or to 0 at end of line. Returns the new op_token. */
497 msg (IS, _("Syntax error."));
501 while (isspace ((unsigned char) *prog))
513 ds_clear (&op_tokstr);
515 if (*prog == '\'' || *prog == '"')
519 while (*prog && *prog != quote)
522 ds_putc (&op_tokstr, *prog++);
528 assert ((int) *prog); /* How could a line end in `\'? */
577 while (*prog >= '0' && *prog <= '7')
578 c = c * 8 + *prog++ - '0';
585 while (isxdigit ((unsigned char) *prog))
588 if (isdigit ((unsigned char) *prog))
591 c += (tolower ((unsigned char) (*prog))
598 msg (IS, _("Syntax error in string constant."));
601 ds_putc (&op_tokstr, (unsigned char) c);
607 while (*prog && !isspace ((unsigned char) *prog) && *prog != '=')
608 ds_putc (&op_tokstr, *prog++);
615 /* Applies the user-specified options in string S to output driver D
616 (at configuration time). */
618 parse_options (const char *s, struct outp_driver * d)
623 ds_init (&op_tokstr, 64);
630 msg (IS, _("Syntax error in options."));
634 ds_truncate (&op_tokstr, 64);
635 strcpy (key, ds_c_str (&op_tokstr));
640 msg (IS, _("Syntax error in options (`=' expected)."));
647 msg (IS, _("Syntax error in options (value expected after `=')."));
650 d->class->option (d, key, &op_tokstr);
652 ds_destroy (&op_tokstr);
655 /* Find the driver in outp_driver_list with name NAME. */
656 static struct outp_driver *
657 find_driver (char *name)
659 struct outp_driver *d;
661 for (d = outp_driver_list; d; d = d->next)
662 if (!strcmp (d->name, name))
667 /* Tokenize string SRC into colon-separated fields, removing leading and
668 trailing whitespace on tokens. Tokens are placed into DEST.
670 CP should remain unchanged throughout.
671 It is the callers responsibility to destroy CP and DEST.
674 Returns true if there are more fields, false otherwise.
676 FIXME: Should ignore colons inside double quotes. */
678 colon_tokenize(const struct string *src, struct string *dest,
684 ds_create(cp, ds_c_str(src));
686 int first = ds_n_find(cp, "\t ");
687 int delim = ds_find(cp, ":") ;
690 last = ds_length(cp);
700 ds_create_substr(dest, cp, first, last);
703 if ( last < ds_length(cp) )
706 ds_create_substr(&temp, cp, last + 2, ds_length(cp));
719 /* String S is in format:
720 DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
721 Adds a driver to outp_driver_list pursuant to the specification
724 configure_driver (const char *driver_name, const char *class_name,
725 const char *device_type, const char *options)
727 struct outp_driver *d = NULL, *iter;
728 struct outp_driver_class_list *c = NULL;
730 d = xmalloc (sizeof *d);
732 d->name = xstrdup (driver_name);
735 d->next = d->prev = NULL;
736 d->device = OUTP_DEV_NONE;
739 for (c = outp_class_list; c; c = c->next)
740 if (!strcmp (c->class->name, class_name))
744 msg (IS, _("Unknown output driver class `%s'."), class_name);
749 if (!c->ref_count && !d->class->open_global (d->class))
751 msg (IS, _("Can't initialize output driver class `%s'."),
756 if (!d->class->preopen_driver (d))
758 msg (IS, _("Can't initialize output driver `%s' of class `%s'."),
759 d->name, d->class->name);
764 if (device_type != NULL)
766 char *copy = xstrdup (device_type);
769 for (type = strtok_r (copy, " \t\r\v", &sp); type;
770 type = strtok_r (NULL, " \t\r\v", &sp))
772 if (!strcmp (type, "listing"))
773 d->device |= OUTP_DEV_LISTING;
774 else if (!strcmp (type, "screen"))
775 d->device |= OUTP_DEV_SCREEN;
776 else if (!strcmp (type, "printer"))
777 d->device |= OUTP_DEV_PRINTER;
780 msg (IS, _("Unknown device type `%s'."), type);
790 parse_options (options, d);
791 if (!d->class->postopen_driver (d))
793 msg (IS, _("Can't complete initialization of output driver `%s' of "
794 "class `%s'."), d->name, d->class->name);
798 /* Find like-named driver and delete. */
799 iter = find_driver (d->name);
801 destroy_driver (iter);
804 d->next = outp_driver_list;
806 if (outp_driver_list)
807 outp_driver_list->prev = d;
808 outp_driver_list = d;
817 /* String LINE is in format:
818 DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
819 Adds a driver to outp_driver_list pursuant to the specification
822 configure_driver_line (struct string *line)
824 fn_interp_vars(line, find_defn_value);
826 struct string driver_name;
827 struct string class_name;
828 struct string device_type;
829 struct string options;
832 colon_tokenize (line, &driver_name, &sss);
833 colon_tokenize (NULL, &class_name, &sss);
834 colon_tokenize (NULL, &device_type, &sss);
835 colon_tokenize (NULL, &options, &sss);
837 if (ds_is_empty(&driver_name) || ds_is_empty(&class_name))
839 msg (IS, _("Driver definition line contains fewer fields "
844 configure_driver (ds_c_str(&driver_name), ds_c_str(&class_name),
845 ds_c_str(&device_type), ds_c_str(&options));
847 ds_destroy(&driver_name);
848 ds_destroy(&class_name);
849 ds_destroy(&device_type);
850 ds_destroy(&options);
854 /* Destroys output driver D. */
856 destroy_driver (struct outp_driver *d)
859 d->class->close_page (d);
862 struct outp_driver_class_list *c;
865 d->class->close_driver (d);
867 for (c = outp_class_list; c; c = c->next)
868 if (c->class == d->class)
873 if (c->ref_count == 0)
875 if (!d->class->close_global (d->class))
876 msg (IS, _("Can't deinitialize output driver class `%s'."),
882 /* Remove this driver from the global driver list. */
884 d->prev->next = d->next;
886 d->next->prev = d->prev;
887 if (d == outp_driver_list)
888 outp_driver_list = d->next;
892 option_cmp (const void *a, const void *b)
894 const struct outp_option *o1 = a;
895 const struct outp_option *o2 = b;
896 return strcmp (o1->keyword, o2->keyword);
899 /* Tries to match S as one of the keywords in TAB, with corresponding
900 information structure INFO. Returns category code or 0 on failure;
901 if category code is negative then stores subcategory in *SUBCAT. */
903 outp_match_keyword (const char *s, struct outp_option *tab,
904 struct outp_option_info *info, int *subcat)
907 struct outp_option *oip;
909 /* Form hash table. */
910 if (NULL == info->initial)
915 struct outp_option *ptr[255], **oip;
917 for (count = 0; tab[count].keyword[0]; count++)
921 qsort (tab, count, sizeof *tab, option_cmp);
925 *cp = tab[0].keyword[0];
927 for (i = 0; i < count; i++)
928 if (tab[i].keyword[0] != *cp)
930 *++cp = tab[i].keyword[0];
935 info->initial = xstrdup (s);
936 info->options = xnmalloc (cp - s, sizeof *info->options);
937 memcpy (info->options, ptr, sizeof *info->options * (cp - s));
941 oip = *info->options;
945 cp = strchr (info->initial, s[0]);
949 printf (_("Trying to find keyword `%s'...\n"), s);
951 oip = info->options[cp - info->initial];
952 while (oip->keyword[0] == s[0])
955 printf ("- %s\n", oip->keyword);
957 if (!strcmp (s, oip->keyword))
960 *subcat = oip->subcat;
969 /* Encapsulate two characters in a single int. */
970 #define TWO_CHARS(A, B) \
973 /* Determines the size of a dimensional measurement and returns the
974 size in units of 1/72000". Units if not specified explicitly are
975 inches for values under 50, millimeters otherwise. Returns 0,
976 stores NULL to *TAIL on error; otherwise returns dimension, stores
979 outp_evaluate_dimension (char *dimen, char **tail)
985 value = strtod (s, &ptail);
992 b = strtod (s, &ptail);
993 if (b <= 0.0 || ptail == s)
998 c = strtod (s, &ptail);
999 if (c <= 0.0 || ptail == s)
1009 else if (*ptail == '/')
1013 b = strtod (s, &ptail);
1014 if (b <= 0.0 || ptail == s)
1021 if (*s == 0 || isspace ((unsigned char) *s))
1026 value *= 72000 / 25.4;
1032 /* Standard TeX units are supported. */
1034 factor = 72000, s++;
1036 switch (TWO_CHARS (s[0], s[1]))
1038 case TWO_CHARS ('p', 't'):
1039 factor = 72000 / 72.27;
1041 case TWO_CHARS ('p', 'c'):
1042 factor = 72000 / 72.27 * 12.0;
1044 case TWO_CHARS ('i', 'n'):
1047 case TWO_CHARS ('b', 'p'):
1048 factor = 72000 / 72.0;
1050 case TWO_CHARS ('c', 'm'):
1051 factor = 72000 / 2.54;
1053 case TWO_CHARS ('m', 'm'):
1054 factor = 72000 / 25.4;
1056 case TWO_CHARS ('d', 'd'):
1057 factor = 72000 / 72.27 * 1.0700086;
1059 case TWO_CHARS ('c', 'c'):
1060 factor = 72000 / 72.27 * 12.840104;
1062 case TWO_CHARS ('s', 'p'):
1063 factor = 72000 / 72.27 / 65536.0;
1066 msg (SE, _("Unit \"%s\" is unknown in dimension \"%s\"."), s, dimen);
1081 msg (SE, _("Bad dimension \"%s\"."), dimen);
1085 /* Stores the dimensions in 1/72000" units of paper identified by
1086 SIZE, which is of form `HORZ x VERT' or `HORZ by VERT' where each
1087 of HORZ and VERT are dimensions, into *H and *V. Return nonzero on
1090 internal_get_paper_size (char *size, int *h, int *v)
1094 while (isspace ((unsigned char) *size))
1096 *h = outp_evaluate_dimension (size, &tail);
1099 while (isspace ((unsigned char) *tail))
1103 else if (*tail == 'b' && tail[1] == 'y')
1107 msg (SE, _("`x' expected in paper size `%s'."), size);
1110 *v = outp_evaluate_dimension (tail, &tail);
1113 while (isspace ((unsigned char) *tail))
1117 msg (SE, _("Trailing garbage `%s' on paper size `%s'."), tail, size);
1124 /* Stores the dimensions, in 1/72000" units, of paper identified by
1125 SIZE into *H and *V. SIZE may be a pair of dimensions of form `H x
1126 V', or it may be a case-insensitive paper identifier, which is
1127 looked up in the `papersize' configuration file. Returns nonzero
1128 on success. May modify SIZE. */
1129 /* Don't read further unless you've got a strong stomach. */
1131 outp_get_paper_size (char *size, int *h, int *v)
1140 static struct paper_size cache[4];
1147 struct file_locator where;
1151 int min_value, min_index;
1155 while (isspace ((unsigned char) *size))
1157 if (isdigit ((unsigned char) *size))
1158 return internal_get_paper_size (size, h, v);
1162 while (isspace ((unsigned char) *ep) && ep >= size)
1166 msg (SE, _("Paper size name must not be empty."));
1175 for (i = 0; i < 4; i++)
1176 if (cache[i].name != NULL && !strcasecmp (cache[i].name, size))
1184 pprsz_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_PAPERSIZE_FILE",
1186 fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
1190 where.filename = pprsz_fn;
1191 where.line_number = 0;
1192 err_push_file_locator (&where);
1193 ds_init (&line, 128);
1195 if (pprsz_fn == NULL)
1197 msg (IE, _("Cannot find `papersize' configuration file."));
1201 msg (VM (1), _("%s: Opening paper size definition file..."), pprsz_fn);
1202 f = fopen (pprsz_fn, "r");
1205 msg (IE, _("Opening %s: %s."), pprsz_fn, strerror (errno));
1213 if (!ds_get_config_line (f, &line, &where))
1216 msg (ME, _("Reading %s: %s."), pprsz_fn, strerror (errno));
1219 for (cp = ds_c_str (&line); isspace ((unsigned char) *cp); cp++);
1224 for (bp = ep = cp + 1; *ep && *ep != '"'; ep++);
1228 if (0 != strcasecmp (bp, size))
1231 for (cp = ep + 1; isspace ((unsigned char) *cp); cp++);
1234 size = xmalloc (ep - bp + 1);
1243 msg (IE, _("Syntax error in paper size definition."));
1246 /* We found the one we want! */
1247 result = internal_get_paper_size (size, h, v);
1250 min_value = cache[0].use;
1252 for (i = 1; i < 4; i++)
1253 if (cache[0].use < min_value)
1255 min_value = cache[i].use;
1258 free (cache[min_index].name);
1259 cache[min_index].name = xstrdup (size);
1260 cache[min_index].use = use;
1261 cache[min_index].h = *h;
1262 cache[min_index].v = *v;
1266 err_pop_file_locator (&where);
1272 msg (VM (2), _("Paper size definition file read successfully."));
1274 msg (VM (1), _("Error reading paper size definition file."));
1279 /* If D is NULL, returns the first enabled driver if any, NULL if
1280 none. Otherwise D must be the last driver returned by this
1281 function, in which case the next enabled driver is returned or NULL
1282 if that was the last. */
1283 struct outp_driver *
1284 outp_drivers (struct outp_driver *d)
1287 struct outp_driver *orig_d = d;
1293 d = outp_driver_list;
1300 || (d->device & disabled_devices) != d->device)))
1307 /* Enables (if ENABLE is nonzero) or disables (if ENABLE is zero) the
1308 device(s) given in mask DEVICE. */
1310 outp_enable_device (int enable, int device)
1313 disabled_devices &= ~device;
1315 disabled_devices |= device;
1318 /* Ejects the paper on device D, if the page is not blank. */
1320 outp_eject_page (struct outp_driver *d)
1322 if (d->page_open == 0)
1327 d->cp_x = d->cp_y = 0;
1329 if (d->class->close_page (d) == 0)
1330 msg (ME, _("Error closing page on %s device of %s class."),
1331 d->name, d->class->name);
1332 if (d->class->open_page (d) == 0)
1334 msg (ME, _("Error opening page on %s device of %s class."),
1335 d->name, d->class->name);
1342 /* Returns the width of string S, in device units, when output on
1345 outp_string_width (struct outp_driver *d, const char *s)
1347 struct outp_text text;
1349 text.options = OUTP_T_JUST_LEFT;
1350 ls_init (&text.s, (char *) s, strlen (s));
1351 d->class->text_metrics (d, &text);