(no commit message)
[geda-pcb/pcjc2.git] / src / pcb-printf.c
bloba214d85de1229ff0f7f201fcdd34a918ce725ae2
1 /*
2 * COPYRIGHT
4 * PCB, interactive printed circuit board design
5 * Copyright (C) 2011 Andrew Poelstra
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * Contact addresses for paper mail and Email:
22 * Andrew Poelstra, 16966 60A Ave, V3S 8X5 Surrey, BC, Canada
23 * asp11@sfu.ca
27 /*! \file <pcb-printf.c>
28 * \brief Implementation of printf wrapper to output pcb coords and angles
29 * \par Description
30 * For details of all supported specifiers, see the comment at the
31 * top of pcb-printf.h
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
38 #include "global.h"
40 #include "pcb-printf.h"
42 /* Helper macros for tables */
43 #define MM_TO_COORD3(a,b,c) MM_TO_COORD (a), MM_TO_COORD (b), MM_TO_COORD (c)
44 #define MIL_TO_COORD3(a,b,c) MIL_TO_COORD (a), MIL_TO_COORD (b), MIL_TO_COORD (c)
45 #define MM_TO_COORD5(a,b,c,d,e) MM_TO_COORD (a), MM_TO_COORD (b), MM_TO_COORD (c), \
46 MM_TO_COORD (d), MM_TO_COORD (e)
47 #define MIL_TO_COORD5(a,b,c,d,e) MIL_TO_COORD (a), MIL_TO_COORD (b), MIL_TO_COORD (c), \
48 MIL_TO_COORD (d), MIL_TO_COORD (e)
50 /* These should be kept in order of smallest scale_factor
51 * to largest -- the code uses this ordering when finding
52 * the best scale to use for a group of measures. */
54 static Unit Units[] = {
55 { 0, "km", NULL, 'k', 0.000001, METRIC, ALLOW_KM, 5,
56 0.00005, 0.0005, 0.0025, 0.05, 0.25,
57 { "" } },
58 { 0, "m", NULL, 'f', 0.001, METRIC, ALLOW_M, 5,
59 0.0005, 0.005, 0.025, 0.5, 2.5,
60 { "" } },
61 { 0, "cm", NULL, 'e', 0.1, METRIC, ALLOW_CM, 5,
62 0.005, 0.05, 0.25, 5, 25,
63 { "" } },
64 { 0, "mm", NULL, 'm', 1, METRIC, ALLOW_MM, 4,
65 0.005, 0.05, 0.25, 5, 25,
66 { "" } },
67 { 0, "um", NULL, 'u', 1000, METRIC, ALLOW_UM, 2,
68 0.005, 0.05, 0.25, 5, 25,
69 { "" } },
70 { 0, "nm", NULL, 'n', 1000000, METRIC, ALLOW_NM, 0,
71 5, 50, 2500, 5000, 25000,
72 { "" } },
73 /* Hack: Pixels get parsed like nanometers. If the value of the
74 * resulting integer is sufficiently small, the code interprets
75 * it a screen pixels. This affects rat thickness. */
76 { 0, "px", NULL, 'n', 1000000, METRIC, ALLOW_NM, 0,
77 5, 50, 2500, 5000, 25000,
78 { "" } },
81 { 0, "in", NULL, 'i', 0.001, IMPERIAL, ALLOW_IN, 5,
82 0.1, 1.0, 5.0, 25, 100,
83 { "inch" } },
84 { 0, "mil", NULL, 'l', 1, IMPERIAL, ALLOW_MIL, 2,
85 0.1, 1.0, 10, 100, 1000,
86 { "" } },
87 { 0, "dmil", NULL, 't', 10, IMPERIAL, ALLOW_DMIL, 0,
88 1, 10, 100, 1000, 10000,
89 { "" } },
90 { 0, "cmil", NULL, 'c', 100, IMPERIAL, ALLOW_CMIL, 0,
91 1, 10, 100, 1000, 10000,
92 { "pcb" } }
94 #define N_UNITS ((int) (sizeof Units / sizeof Units[0]))
95 /* \brief Initialize non-static data for pcb-printf
96 * \par Function Description
97 * Assigns each unit its index for quick access through the
98 * main units array, and internationalize the units for GUI
99 * display.
101 void initialize_units()
103 int i;
104 for (i = 0; i < N_UNITS; ++i)
106 Units[i].index = i;
107 Units[i].in_suffix = _(Units[i].suffix);
111 /* \brief Get/set a mask of units to use when saving .pcb files
112 * \par Function Description
113 * If passed 0, returns the current mask of units to use in a .pcb
114 * file; if passed anything else, replaces the current mask. This
115 * mask should only contain units which are readable by recent versions
116 * of pcb; currently this means only ALLOW_MIL and ALLOW_MM. (Versions
117 * prior to 20110703 nominally support other units, but in fact the scaling
118 * calculations are incorrect so the wrong values will be read! See commit
119 * 750a1c5 for more details.)
121 * \return the current mask.
123 enum e_allow set_allow_readable(enum e_allow new_mask)
125 static enum e_allow readable_mask = ALLOW_READABLE;
126 if (new_mask != 0)
127 readable_mask = new_mask;
128 return readable_mask;
132 /* TABLE FORMAT | default | min | max
133 * grid | | |
134 * size | | |
135 * line | | |
136 * clear | | |
138 static Increments increments_metric = {
139 "mm",
140 MM_TO_COORD3 (0.1, 0.01, 1.0),
141 MM_TO_COORD3 (0.2, 0.01, 0.5),
142 MM_TO_COORD3 (0.1, 0.005, 0.5),
143 MM_TO_COORD3 (0.05, 0.005, 0.5)
145 static Increments increments_imperial = {
146 "mil",
147 MIL_TO_COORD3 (5, 1, 25),
148 MIL_TO_COORD3 (10, 1, 10),
149 MIL_TO_COORD3 (5, 0.5, 10),
150 MIL_TO_COORD3 (2, 0.5, 10)
153 /* \brief Obtain a unit object from its suffix
154 * \par Function Description
155 * Looks up a given suffix in the main units array. Internationalized
156 * unit suffixes are not supported, though pluralized units are, for
157 * backward-compatibility.
159 * \param [in] const_suffix The suffix to look up
161 * \return A const pointer to the Unit struct, or NULL if none was found
163 const Unit *get_unit_struct (const char *const_suffix)
165 int i;
166 int s_len = 0;
167 /* Turn given suffix into something we can modify... */
168 char *m_suffix = g_strdup (const_suffix);
169 /* ...and store this in a pointer we can move. */
170 char *suffix = m_suffix;
172 /* Determine bounds */
173 while (isspace (*suffix))
174 ++suffix;
175 while (isalnum (suffix[s_len]))
176 ++s_len;
178 /* Also understand plural suffixes: "inches", "mils" */
179 if (s_len > 2)
181 if (suffix[s_len - 2] == 'e' && suffix[s_len - 1] == 's')
182 suffix[s_len - 2] = 0;
183 else if (suffix[s_len - 1] == 's')
184 suffix[s_len - 1] = 0;
187 /* Do lookup */
188 if (*suffix && s_len > 0)
189 for (i = 0; i < N_UNITS; ++i)
190 if (strncmp (suffix, Units[i].suffix, s_len) == 0 ||
191 strncmp (suffix, Units[i].alias[0], s_len) == 0)
193 g_free (m_suffix);
194 return &Units[i];
196 g_free (m_suffix);
197 return NULL;
200 void copy_nonzero_increments (Increments *dst, const Increments *src)
202 if (src->grid >= dst->grid_min && src->grid <= dst->grid_max)
203 dst->grid = src->grid;
204 if (src->line >= dst->line_min && src->line <= dst->line_max)
205 dst->line = src->line;
206 if (src->size >= dst->size_min && src->size <= dst->size_max)
207 dst->size = src->size;
208 if (src->clear >= dst->clear_min && src->clear <= dst->clear_max)
209 dst->clear = src->clear;
212 /* ACCESSORS */
213 /* \brief Returns the master unit list. This may not be modified. */
214 const Unit *get_unit_list (void)
216 return Units;
218 /* \brief Returns the length of the master unit list. */
219 int get_n_units (void)
221 return N_UNITS;
224 /* \brief Obtain the increment values for a given family of units
225 * \par Function Description
227 * \param [in] family One of METRIC or IMPERIAL.
229 * \return A pointer to the appropriate increments structure.
231 Increments *get_increments_struct (enum e_family family)
233 switch (family)
235 case METRIC:
236 return &increments_metric;
237 case IMPERIAL:
238 return &increments_imperial;
240 return NULL;
243 /* \brief Convert a pcb coord to the given unit
245 * \param [in] unit The unit to convert to
246 * \param [in] x The quantity to convert
248 * \return The converted measure
250 double coord_to_unit (const Unit *unit, Coord x)
252 double base;
253 if (unit == NULL)
254 return -1;
255 base = unit->family == METRIC
256 ? COORD_TO_MM (1)
257 : COORD_TO_MIL (1);
258 return x * unit->scale_factor * base;
261 /* \brief Convert a given unit to pcb coords
263 * \param [in] unit The unit to convert from
264 * \param [in] x The quantity to convert
266 * \return The converted measure
268 Coord unit_to_coord (const Unit *unit, double x)
270 return x / coord_to_unit (unit, 1);
273 static int min_sig_figs(double d)
275 char buf[50];
276 int rv;
278 if(d == 0) return 0;
280 /* Normalize to x.xxxx... form */
281 if(d < 0) d *= -1;
282 while(d >= 10) d /= 10;
283 while(d < 1) d *= 10;
285 rv = sprintf(buf, "%g", d);
286 return rv;
289 /* \brief Internal coord-to-string converter for pcb-printf
290 * \par Function Description
291 * Converts a (group of) measurement(s) to a comma-deliminated
292 * string, with appropriate units. If more than one coord is
293 * given, the list is enclosed in parens to make the scope of
294 * the unit suffix clear.
296 * \param [in] coord Array of coords to convert
297 * \param [in] n_coords Number of coords in array
298 * \param [in] printf_spec printf sub-specifier to use with %f
299 * \param [in] e_allow Bitmap of units the function may use
300 * \param [in] suffix_type Whether to add a suffix
302 * \return A string containing the formatted coords. Must be freed with g_free.
304 static gchar *CoordsToString(Coord coord[], int n_coords, const char *printf_spec, enum e_allow allow, enum e_suffix suffix_type)
306 GString *buff;
307 gchar *printf_buff;
308 gchar filemode_buff[G_ASCII_DTOSTR_BUF_SIZE];
309 enum e_family family;
310 double *value;
311 const char *suffix;
312 int i, n;
314 value = malloc (n_coords * sizeof *value);
315 buff = g_string_new ("");
317 /* Sanity checks */
318 if (buff == NULL || value == NULL)
319 return NULL;
320 if (allow == 0)
321 allow = ALLOW_ALL;
322 if (printf_spec == NULL)
323 printf_spec = "";
325 /* Check our freedom in choosing units */
326 if ((allow & ALLOW_IMPERIAL) == 0)
327 family = METRIC;
328 else if ((allow & ALLOW_METRIC) == 0)
329 family = IMPERIAL;
330 else
332 int met_votes = 0,
333 imp_votes = 0;
335 for (i = 0; i < n_coords; ++i)
336 if(min_sig_figs(COORD_TO_MIL(coord[i])) < min_sig_figs(COORD_TO_MM(coord[i])))
337 ++imp_votes;
338 else
339 ++met_votes;
341 if (imp_votes > met_votes)
342 family = IMPERIAL;
343 else
344 family = METRIC;
347 /* Set base unit */
348 for (i = 0; i < n_coords; ++i)
350 switch (family)
352 case METRIC: value[i] = COORD_TO_MM (coord[i]); break;
353 case IMPERIAL: value[i] = COORD_TO_MIL (coord[i]); break;
357 /* Determine scale factor -- find smallest unit that brings
358 * the whole group above unity */
359 for (n = 0; n < N_UNITS; ++n)
361 if ((Units[n].allow & allow) != 0 && (Units[n].family == family))
363 int n_above_one = 0;
365 for (i = 0; i < n_coords; ++i)
366 if (fabs(value[i] * Units[n].scale_factor) > 1)
367 ++n_above_one;
368 if (n_above_one == n_coords)
369 break;
372 /* If nothing worked, wind back to the smallest allowable unit */
373 if (n == N_UNITS)
375 do {
376 --n;
377 } while ((Units[n].allow & allow) == 0 || Units[n].family != family);
380 /* Apply scale factor */
381 suffix = Units[n].suffix;
382 for (i = 0; i < n_coords; ++i)
383 value[i] = value[i] * Units[n].scale_factor;
385 /* Create sprintf specifier, using default_prec no precision is given */
386 i = 0;
387 while (printf_spec[i] == '%' || isdigit(printf_spec[i]) ||
388 printf_spec[i] == '-' || printf_spec[i] == '+' ||
389 printf_spec[i] == '#')
390 ++i;
391 if (printf_spec[i] == '.')
392 printf_buff = g_strdup_printf (", %sf", printf_spec);
393 else
394 printf_buff = g_strdup_printf (", %s.%df", printf_spec, Units[n].default_prec);
396 /* Actually sprintf the values in place
397 * (+ 2 skips the ", " for first value) */
398 if (n_coords > 1)
399 g_string_append_c (buff, '(');
400 if (suffix_type == FILE_MODE || suffix_type == FILE_MODE_NO_SUFFIX)
402 g_ascii_formatd (filemode_buff, sizeof filemode_buff, printf_buff + 2, value[0]);
403 g_string_append_printf (buff, "%s", filemode_buff);
405 else
406 g_string_append_printf (buff, printf_buff + 2, value[0]);
407 for (i = 1; i < n_coords; ++i)
409 if (suffix_type == FILE_MODE || suffix_type == FILE_MODE_NO_SUFFIX)
411 g_ascii_formatd (filemode_buff, sizeof filemode_buff, printf_buff, value[i]);
412 g_string_append_printf (buff, "%s", filemode_buff);
414 else
415 g_string_append_printf (buff, printf_buff, value[i]);
417 if (n_coords > 1)
418 g_string_append_c (buff, ')');
419 /* Append suffix */
420 if (value[0] != 0 || n_coords > 1)
422 switch (suffix_type)
424 case NO_SUFFIX:
425 case FILE_MODE_NO_SUFFIX:
426 break;
427 case SUFFIX:
428 g_string_append_printf (buff, " %s", suffix);
429 break;
430 case FILE_MODE:
431 g_string_append_printf (buff, "%s", suffix);
432 break;
436 g_free (printf_buff);
437 free (value);
438 /* Return just the gchar* part of our string */
439 return g_string_free (buff, FALSE);
442 /* \brief Main pcb-printf function
443 * \par Function Description
444 * This is a printf wrapper that accepts new format specifiers to
445 * output pcb coords as various units. See the comment at the top
446 * of pcb-printf.h for full details.
448 * \param [in] fmt Format specifier
449 * \param [in] args Arguments to specifier
451 * \return A formatted string. Must be freed with g_free.
453 gchar *pcb_vprintf(const char *fmt, va_list args)
455 GString *string = g_string_new ("");
456 GString *spec = g_string_new ("");
458 enum e_allow mask = ALLOW_ALL;
460 if (string == NULL || spec == NULL)
461 return NULL;
463 while(*fmt)
465 enum e_suffix suffix = NO_SUFFIX;
467 if(*fmt == '%')
469 gchar *unit_str = NULL;
470 const char *ext_unit = "";
471 Coord value[10];
472 int count, i;
474 g_string_assign (spec, "");
476 /* Get printf sub-specifiers */
477 g_string_append_c (spec, *fmt++);
478 while(isdigit(*fmt) || *fmt == '.' || *fmt == ' ' || *fmt == '*'
479 || *fmt == '#' || *fmt == 'l' || *fmt == 'L'
480 || *fmt == 'h' || *fmt == '+' || *fmt == '-')
482 if (*fmt == '*')
484 g_string_append_printf (spec, "%d", va_arg (args, int));
485 fmt++;
487 else
488 g_string_append_c (spec, *fmt++);
490 /* Get our sub-specifiers */
491 if(*fmt == '#')
493 mask = ALLOW_CMIL; /* This must be pcb's base unit */
494 fmt++;
496 if(*fmt == '$')
498 suffix = (suffix == NO_SUFFIX) ? SUFFIX : FILE_MODE;
499 fmt++;
501 if(*fmt == '`')
503 suffix = (suffix == SUFFIX) ? FILE_MODE : FILE_MODE_NO_SUFFIX;
504 fmt++;
506 /* Tack full specifier onto specifier */
507 if (*fmt != 'm')
508 g_string_append_c (spec, *fmt);
509 switch(*fmt)
511 /* Printf specs */
512 case 'o': case 'i': case 'd':
513 case 'u': case 'x': case 'X':
514 if(spec->str[1] == 'l')
516 if(spec->str[2] == 'l')
517 unit_str = g_strdup_printf (spec->str, va_arg(args, long long));
518 else
519 unit_str = g_strdup_printf (spec->str, va_arg(args, long));
521 else
523 unit_str = g_strdup_printf (spec->str, va_arg(args, int));
525 break;
526 case 'e': case 'E': case 'f':
527 case 'g': case 'G':
528 if (strchr (spec->str, '*'))
530 int prec = va_arg(args, int);
531 unit_str = g_strdup_printf (spec->str, va_arg(args, double), prec);
533 else
534 unit_str = g_strdup_printf (spec->str, va_arg(args, double));
535 break;
536 case 'c':
537 if(spec->str[1] == 'l' && sizeof(int) <= sizeof(wchar_t))
538 unit_str = g_strdup_printf (spec->str, va_arg(args, wchar_t));
539 else
540 unit_str = g_strdup_printf (spec->str, va_arg(args, int));
541 break;
542 case 's':
543 if(spec->str[0] == 'l')
544 unit_str = g_strdup_printf (spec->str, va_arg(args, wchar_t *));
545 else
546 unit_str = g_strdup_printf (spec->str, va_arg(args, char *));
547 break;
548 case 'n':
549 /* Depending on gcc settings, this will probably break with
550 * some silly "can't put %n in writeable data space" message */
551 unit_str = g_strdup_printf (spec->str, va_arg(args, int *));
552 break;
553 case 'p':
554 unit_str = g_strdup_printf (spec->str, va_arg(args, void *));
555 break;
556 case '%':
557 g_string_append_c (string, '%');
558 break;
559 /* Our specs */
560 case 'm':
561 ++fmt;
562 if (*fmt == '*')
563 ext_unit = va_arg(args, const char *);
564 if (*fmt != '+' && *fmt != 'a')
565 value[0] = va_arg(args, Coord);
566 count = 1;
567 switch(*fmt)
569 case 's': unit_str = CoordsToString(value, 1, spec->str, ALLOW_MM | ALLOW_MIL, suffix); break;
570 case 'S': unit_str = CoordsToString(value, 1, spec->str, mask & ALLOW_ALL, suffix); break;
571 case 'M': unit_str = CoordsToString(value, 1, spec->str, mask & ALLOW_METRIC, suffix); break;
572 case 'L': unit_str = CoordsToString(value, 1, spec->str, mask & ALLOW_IMPERIAL, suffix); break;
573 case 'r': unit_str = CoordsToString(value, 1, spec->str, set_allow_readable(0), FILE_MODE); break;
574 /* All these fallthroughs are deliberate */
575 case '9': value[count++] = va_arg(args, Coord);
576 case '8': value[count++] = va_arg(args, Coord);
577 case '7': value[count++] = va_arg(args, Coord);
578 case '6': value[count++] = va_arg(args, Coord);
579 case '5': value[count++] = va_arg(args, Coord);
580 case '4': value[count++] = va_arg(args, Coord);
581 case '3': value[count++] = va_arg(args, Coord);
582 case '2':
583 case 'D':
584 value[count++] = va_arg(args, Coord);
585 unit_str = CoordsToString(value, count, spec->str, mask & ALLOW_ALL, suffix);
586 break;
587 case 'd':
588 value[1] = va_arg(args, Coord);
589 unit_str = CoordsToString(value, 2, spec->str, ALLOW_MM | ALLOW_MIL, suffix);
590 break;
591 case '*':
592 for (i = 0; i < N_UNITS; ++i)
593 if (strcmp (ext_unit, Units[i].suffix) == 0)
594 unit_str = CoordsToString(value, 1, spec->str, Units[i].allow, suffix);
595 if (unit_str == NULL)
596 unit_str = CoordsToString(value, 1, spec->str, mask & ALLOW_ALL, suffix);
597 break;
598 case 'a':
599 g_string_append (spec, ".0f");
600 if (suffix == SUFFIX)
601 g_string_append (spec, " deg");
602 unit_str = g_strdup_printf (spec->str, (double) va_arg(args, Angle));
603 break;
604 case '+':
605 mask = va_arg(args, enum e_allow);
606 break;
607 default:
608 for (i = 0; i < N_UNITS; ++i)
609 if (*fmt == Units[i].printf_code)
610 unit_str = CoordsToString(value, 1, spec->str, Units[i].allow, suffix);
611 if (unit_str == NULL)
612 unit_str = CoordsToString(value, 1, spec->str, ALLOW_ALL, suffix);
613 break;
615 break;
617 if (unit_str != NULL)
619 g_string_append (string, unit_str);
620 g_free (unit_str);
623 else
624 g_string_append_c (string, *fmt);
625 ++fmt;
627 g_string_free (spec, TRUE);
628 /* Return just the gchar* part of our string */
629 return g_string_free (string, FALSE);
633 /* \brief Wrapper for pcb_vprintf that outputs to a string
635 * \param [in] string Pointer to string to output into
636 * \param [in] fmt Format specifier
638 * \return The length of the written string
640 int pcb_sprintf(char *string, const char *fmt, ...)
642 gchar *tmp;
644 va_list args;
645 va_start(args, fmt);
647 tmp = pcb_vprintf (fmt, args);
648 strcpy (string, tmp);
649 g_free (tmp);
651 va_end(args);
652 return strlen (string);
655 /* \brief Wrapper for pcb_vprintf that outputs to a file
657 * \param [in] fh File to output to
658 * \param [in] fmt Format specifier
660 * \return The length of the written string
662 int pcb_fprintf(FILE *fh, const char *fmt, ...)
664 int rv;
665 gchar *tmp;
667 va_list args;
668 va_start(args, fmt);
670 if (fh == NULL)
671 rv = -1;
672 else
674 tmp = pcb_vprintf (fmt, args);
675 rv = fprintf (fh, "%s", tmp);
676 g_free (tmp);
679 va_end(args);
680 return rv;
683 /* \brief Wrapper for pcb_vprintf that outputs to stdout
685 * \param [in] fmt Format specifier
687 * \return The length of the written string
689 int pcb_printf(const char *fmt, ...)
691 int rv;
692 gchar *tmp;
694 va_list args;
695 va_start(args, fmt);
697 tmp = pcb_vprintf (fmt, args);
698 rv = printf ("%s", tmp);
699 g_free (tmp);
701 va_end(args);
702 return rv;
705 /* \brief Wrapper for pcb_vprintf that outputs to a newly allocated string
707 * \param [in] fmt Format specifier
709 * \return The newly allocated string. Must be freed with g_free.
711 char *pcb_g_strdup_printf(const char *fmt, ...)
713 gchar *tmp;
715 va_list args;
716 va_start(args, fmt);
717 tmp = pcb_vprintf (fmt, args);
718 va_end(args);
719 return tmp;