/* opts.c Copyright (C) 2005,2006,2007 Eugene K. Ressler, Jr. This file is part of Sketch, a small, simple system for making 3d drawings with LaTeX and the PSTricks or TikZ package. Sketch is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Sketch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Sketch; see the file COPYING.txt. If not, see http://www.gnu.org/copyleft */ #include "opts.h" #include "geometry.h" DECLARE_DYNAMIC_ARRAY_FUNCS (OPT_LIST, OPT, opt_list, elt, n_elts, NO_OTHER_INIT) // ---- useful string stuff ---------------------------------------------------- // slice a string using Perl/Python position indexing conventions // (position == dst_size is always at the end of the string) char *str_slice (char *dst, int dst_size, char *src, int beg, int end) { int len; if (dst_size > 0) { len = strlen (src); if (beg < 0) beg = len + beg; else if (beg > len) beg = len; if (end < 0) end = len + end; else if (end > len) end = len; len = end - beg; if (len <= 0) { dst[0] = '\0'; } else { if (len >= dst_size) len = dst_size - 1; memcpy (dst, &src[beg], len); dst[len] = '\0'; } } return dst; } // a modified version of C library strtok // uses state variable p, which should be // initially set to zero. char * istrtok (int *p, char *s, char sep) { int i, r; // advance r to next non-space character for (r = *p; s[r] == ' ' || s[r] == '\t'; r++) /* skip */ ; // if we're at terminating null, return null if (s[r] == '\0') { *p = r; return NULL; } // look for a separator character for (i = r; s[i] != '\0'; i++) { if (s[i] == sep) { // found one; set to null char, // advance state variable, and // return pointer to first char s[i] = '\0'; *p = i + 1; return &s[r]; } } // did not find a terminator, so this // is the last token; return it *p = i; return &s[r]; } int str_last_occurance (char *src, char *set) { int i; for (i = 0; src[i]; i++) /* skip */ ; for (--i; i >= 0 && !strchr (set, src[i]); --i) /* skip */ ; return i; } // ---- options ---------------------------------------------------------------- void init_opts (OPTS * opts) { init_opt_list (opts->list); } OPTS * raw_opts (void) { OPTS *r = safe_malloc (sizeof *r); init_opts (r); return r; } void setup_opts (OPTS * opts, char *opts_str, SRC_LINE line) { int p_pair, p_side; char *pair, *key, *val, *buf; OPT *opt; clear_opts (opts); buf = safe_strdup (opts_str); p_pair = 0; while ((pair = istrtok (&p_pair, buf, ',')) != NULL) { p_side = 0; key = istrtok (&p_side, pair, '='); if (key == NULL) { err (line, "null keyword in option"); key = ""; } val = istrtok (&p_side, pair, ','); if (val == NULL) { err (line, "null value in option"); val = ""; } opt = pushed_opt_list_elt (opts->list); opt->key = safe_strdup (key); opt->val = safe_strdup (val); } safe_free (buf); } OPTS * new_opts (char *opts_str, SRC_LINE line) { OPTS *r = raw_opts (); setup_opts (r, opts_str, line); return r; } void clear_opts (OPTS * opts) { int i; for (i = 0; i < opts->list->n_elts; ++i) { safe_free (opts->list->elt[i].key); safe_free (opts->list->elt[i].val); } clear_opt_list (opts->list); } char * opt_val (OPTS * opts, char *opt) { int i; if (!opts) return 0; for (i = 0; i < opts->list->n_elts; i++) if (strcmp (opts->list->elt[i].key, opt) == 0) return opts->list->elt[i].val; return NULL; } int bool_opt_p (OPTS * opts, char *opt, int default_p) { char *r = opt_val (opts, opt); if (!r) return default_p; return strcmp (r, "false") != 0; // all not false is true } typedef struct opt_desc_t { char *opt; int type; } OPT_DESC; typedef struct opt_desc_tbl_t { OPT_DESC *key_desc; int n_key_desc; OPT_DESC *val_desc; int n_val_desc; } OPT_DESC_TBL; static OPT_DESC key_tbl_pst[] = { {"arrows", OPT_LINE}, {"cull", OPT_INTERNAL}, {"dash", OPT_LINE}, {"dotsep", OPT_LINE}, {"fillcolor", OPT_POLYGON | OPT_FILL_COLOR}, {"fillstyle", OPT_POLYGON | OPT_FILL_STYLE}, {"lay", OPT_INTERNAL}, {"linecolor", OPT_LINE}, {"linestyle", OPT_LINE | OPT_LINE_STYLE}, {"linewidth", OPT_LINE}, {"opacity", OPT_POLYGON}, {"showpoints", OPT_LINE | OPT_POLYGON}, {"split", OPT_INTERNAL}, {"strokeopacity", OPT_LINE }, {"transpalpha", OPT_POLYGON} }; OPT_DESC_TBL opt_desc_tbl_pst[1] = { { key_tbl_pst, ARRAY_SIZE (key_tbl_pst), NULL, 0} }; static OPT_DESC opt_key_tbl_tikz[] = { {"arrows", OPT_LINE}, {"cap", OPT_LINE}, {"color", OPT_LINE | OPT_POLYGON | OPT_FILL_COLOR}, {"cull", OPT_INTERNAL}, {"dash pattern", OPT_LINE}, {"dash phase", OPT_LINE}, {"double distance", OPT_LINE}, {"draw", OPT_LINE | OPT_LINE_STYLE}, {"draw opacity", OPT_LINE}, {"fill", OPT_POLYGON | OPT_FILL_COLOR}, {"fill opacity", OPT_POLYGON}, {"fill style", OPT_POLYGON | OPT_FILL_COLOR | OPT_EMIT_VAL}, {"join", OPT_LINE}, {"lay", OPT_INTERNAL}, {"line style", OPT_LINE | OPT_EMIT_VAL}, {"line width", OPT_LINE}, {"miter limit", OPT_LINE}, {"pattern", OPT_POLYGON | OPT_FILL_COLOR}, {"pattern color", OPT_POLYGON}, {"split", OPT_INTERNAL}, {"style", OPT_TYPE_IN_VAL | OPT_EMIT_VAL}, }; static OPT_DESC opt_val_tbl_tikz[] = { {"dashed", OPT_LINE}, {"densely dashed", OPT_LINE}, {"densely dotted", OPT_LINE}, {"dotted", OPT_LINE}, {"double", OPT_LINE}, {"loosely dashed", OPT_LINE}, {"loosely dotted", OPT_LINE}, {"nearly opaque", OPT_POLYGON}, {"nearly transparent", OPT_POLYGON}, {"semithick", OPT_LINE}, {"semitransparent", OPT_POLYGON}, {"solid", OPT_LINE}, {"thick", OPT_LINE}, {"thin", OPT_LINE}, {"transparent", OPT_POLYGON}, {"ultra nearly transparent", OPT_POLYGON}, {"ultra thick", OPT_LINE}, {"ultra thin", OPT_LINE}, {"very nearly transparent", OPT_POLYGON}, {"very thick", OPT_LINE}, {"very thin", OPT_LINE}, }; OPT_DESC_TBL opt_desc_tbl_tikz[1] = { { opt_key_tbl_tikz, ARRAY_SIZE (opt_key_tbl_tikz), opt_val_tbl_tikz, ARRAY_SIZE (opt_val_tbl_tikz), } }; int opt_index (char *opt, OPT_DESC * desc, int n_desc) { int hi, lo, mid, cmp_val; hi = n_desc - 1; lo = 0; while (hi >= lo) { mid = (hi + lo) / 2; cmp_val = strcmp (opt, desc[mid].opt); if (cmp_val < 0) hi = mid - 1; else if (cmp_val > 0) lo = mid + 1; else return mid; } return -1; } static OPT_DESC_TBL *lang_to_opt_desc_tbl[] = { opt_desc_tbl_pst, opt_desc_tbl_tikz, opt_desc_tbl_pst, opt_desc_tbl_tikz, }; int simple_opt_type (OPT * opt, int default_type, int lang) { OPT_DESC_TBL *desc; int i; if (lang < 0) return default_type; desc = lang_to_opt_desc_tbl[lang]; i = opt_index (opt->key, desc->key_desc, desc->n_key_desc); return (i < 0) ? default_type : desc->key_desc[i].type; } int opt_type (OPT * opt, int default_type, int lang) { OPT_DESC_TBL *desc; int i, type; type = simple_opt_type (opt, default_type, lang); if (type & OPT_TYPE_IN_VAL) { desc = lang_to_opt_desc_tbl[lang]; i = opt_index (opt->val, desc->val_desc, desc->n_val_desc); if (i < 0) return default_type; type = desc->val_desc[i].type; } return type; } typedef struct opts_desc_t { OPT *opts; int n_opts; } OPTS_DESC; OPT no_edges_opts_pst[] = { {"linestyle", "none"} }; OPT no_edges_opts_tikz[] = { {"draw", "none"} }; OPTS_DESC no_edges_opts_desc_tbl[] = { {no_edges_opts_pst, ARRAY_SIZE (no_edges_opts_pst)}, {no_edges_opts_tikz, ARRAY_SIZE (no_edges_opts_tikz)}, {no_edges_opts_pst, ARRAY_SIZE (no_edges_opts_pst)}, {no_edges_opts_tikz, ARRAY_SIZE (no_edges_opts_tikz)}, }; static int any_opt_p (OPTS * opts, int type, int lang) { int i; if (!opts) return 0; for (i = 0; i < opts->list->n_elts; i++) if (type & opt_type (&opts->list->elt[i], OPT_NONE, lang)) return 1; return 0; } static void add_default_opt (OPTS ** opts_ptr, OPT * default_opt, int lang) { OPT *opt; OPTS *opts; int default_type; opts = *opts_ptr; default_type = opt_type (default_opt, OPT_NONE, lang) & OPT_DEFAULTS; if (any_opt_p (opts, default_type, lang)) return; if (!opts) opts = raw_opts (); opt = pushed_opt_list_elt (opts->list); opt->key = safe_strdup (default_opt->key); opt->val = safe_strdup (default_opt->val); *opts_ptr = opts; } static void add_default_opts (OPTS ** opts_ptr, OPTS_DESC * opts_desc, int lang) { int i; for (i = 0; i < opts_desc->n_opts; i++) add_default_opt (opts_ptr, &opts_desc->opts[i], lang); } void add_no_edges_default_opt (OPTS ** opts_ptr, int lang) { add_default_opts (opts_ptr, &no_edges_opts_desc_tbl[lang], lang); } OPT solid_white_opts_pst[] = { {"fillstyle", "solid"}, {"fillcolor", "white"} }; OPT solid_white_opts_tikz[] = { {"fill", "white"} }; OPTS_DESC solid_white_opts_desc_tbl[] = { {solid_white_opts_pst, ARRAY_SIZE (solid_white_opts_pst)}, {solid_white_opts_tikz, ARRAY_SIZE (solid_white_opts_tikz)}, {solid_white_opts_pst, ARRAY_SIZE (solid_white_opts_pst)}, {solid_white_opts_tikz, ARRAY_SIZE (solid_white_opts_tikz)}, }; void add_solid_white_default_opt (OPTS ** opts_ptr, int lang) { add_default_opts (opts_ptr, &solid_white_opts_desc_tbl[lang], lang); } void check_opts (OPTS * opts, int allowed, char *allowed_msg, int lang, SRC_LINE line) { int i, type; if (!opts) return; for (i = 0; i < opts->list->n_elts; i++) { type = opt_type (&opts->list->elt[i], OPT_NONE, lang); if ((type & allowed) == 0) warn (line, allowed_msg, opts->list->elt[i].key, opts->list->elt[i].val); } } // selective copy for splitting option lists by type OPTS * copy_opts (OPTS * opts, int type_mask, int lang) { int i; OPTS *r; OPT *opt; if (!opts) return NULL; r = raw_opts (); for (i = 0; i < opts->list->n_elts; i++) if (type_mask & opt_type (&opts->list->elt[i], OPT_NONE, lang)) { opt = pushed_opt_list_elt (r->list); opt->key = safe_strdup (opts->list->elt[i].key); opt->val = safe_strdup (opts->list->elt[i].val); } return r; } OPTS * cat_opts (OPTS * dst, OPTS * src) { int i; OPT *opt; for (i = 0; i < src->list->n_elts; i++) { opt = pushed_opt_list_elt (dst->list); opt->key = safe_strdup(src->list->elt[i].key); opt->val = safe_strdup(src->list->elt[i].val); } return dst; } // selective copy for splitting out line options and modifying arrows OPTS * copy_line_opts (OPTS * opts, int first_p, int last_p, int lang) { int i; OPTS *r; char buf[100]; if (!opts) return NULL; // no modifications necessary if line contains first and last points if (first_p && last_p) return opts; // make a clean copy and modify the arrows r = copy_opts (opts, OPT_LINE, lang); for (i = 0; i < r->list->n_elts; i++) { if (strcmp ("arrows", r->list->elt[i].key) == 0) { char *val = r->list->elt[i].val; char *dash = strchr (val, '-'); if (!dash) { warn (no_line, "could not find '-' while splitting arrows option"); continue; } if (first_p) { str_slice (buf, sizeof buf, val, 0, dash - val + 1); } else if (last_p) { str_slice (buf, sizeof buf, val, dash - val, SLICE_TO_END); } else { // could just delete option entirely, but this is good for debugging str_slice (buf, sizeof buf, val, dash - val, dash - val + 1); } r->list->elt[i].val = safe_strdup (buf); } } return r; } static int member_p(char *str, char **str_list) { if (str_list == NULL) return 0; while (*str_list) { if (strcmp(str, *str_list) == 0) return 1; ++str_list; } return 0; } static void emit_opts_internal (FILE * f, OPTS * opts, char ** exceptions, int brackets_p, int lang) { int i, n, type; // do nothing if no options if (!opts || !opts->list || opts->list->n_elts == 0) return; // do nothing if no non-excepted options for (n = i = 0; i < opts->list->n_elts; i++) { if ( !member_p(opts->list->elt[i].key, exceptions) ) ++n; } if (n == 0) return; if (brackets_p) fputc ('[', f); for (n = i = 0; i < opts->list->n_elts; i++) { if ( member_p(opts->list->elt[i].key, exceptions) ) continue; type = simple_opt_type (&opts->list->elt[i], OPT_NONE, lang); if ((type & OPT_INTERNAL) == 0) { if (n > 0) fprintf (f, ","); if (type & OPT_EMIT_VAL) fprintf (f, "%s", opts->list->elt[i].val); else fprintf (f, "%s=%s", opts->list->elt[i].key, opts->list->elt[i].val); ++n; } } if (brackets_p) fputc (']', f); } void emit_opts_raw (FILE * f, OPTS * opts, int lang) { emit_opts_internal (f, opts, NULL, 0, lang); } void emit_opts (FILE * f, OPTS * opts, int lang) { emit_opts_internal (f, opts, NULL, 1, lang); } void emit_opts_with_exceptions (FILE * f, OPTS * opts, char ** exceptions, int lang) { emit_opts_internal (f, opts, exceptions, 1, lang); }