/* pcb copyright notice... */ /* This file defines a wrapper around sprintf, that * defines new specifiers that take pcb BDimension * objects as input. * * There is a fair bit of nasty (repetitious) code in * here, but I feel the gain in clarity for output * code elsewhere in the project will make it worth * it. * * The new specifiers are: * %mm output a measure in mm * %mM output a measure in scaled (mm/um) metric * %ml output a measure in mil * %mL output a measure in scaled (mil/in) imperial * %ms output a measure in most natural mm/mil units * %mS output a measure in most natural scaled units * %md output a pair of measures in most natural mm/mil units * %mD output a pair of measures in most natural scaled units * %m3 output 3 measures in most natural scaled units * ... * %m9 output 9 measures in most natural scaled units * %m* output a measure with unit given as an additional * const char* parameter * %mr output a measure in a unit readable by parse_l.l * * These accept the usual printf modifiers for %f, * as well as the additional modifier $ which is * used to output a unit suffix after the measure. * * KNOWN ISSUES: * No support for %zu size_t printf spec * No support for .* subspecifier for pcb specs */ #include #include #include #include #include #include /* Ripped from PCB to make test driver standalone */ typedef int bool; typedef int BDimension; #define COORD_TO_MIL(n) ((n) / 100.0) #define MIL_TO_COORD(n) ((n) * 100.0) #define COORD_TO_MM(n) ((n) * 0.000254) #define MM_TO_COORD(n) ((n) / 0.000254) /* end rip */ enum e_allow { ALLOW_NM = 1, ALLOW_UM = 2, ALLOW_MM = 4, ALLOW_CM = 8, ALLOW_M = 16, ALLOW_KM = 32, ALLOW_CMIL = 1024, ALLOW_MIL = 2048, ALLOW_IN = 4096, ALLOW_METRIC = ALLOW_NM | ALLOW_UM | ALLOW_MM | ALLOW_CM | ALLOW_M | ALLOW_KM, ALLOW_IMPERIAL = ALLOW_CMIL | ALLOW_MIL | ALLOW_IN, ALLOW_READABLE = ALLOW_UM | ALLOW_MM | ALLOW_MIL | ALLOW_IN, ALLOW_ALL = ~0 }; enum e_family { METRIC, IMPERIAL }; struct unit { const char *suffix; char printf_code; double scale_factor; enum e_family family; enum e_allow allow; int default_prec; }; /* These should be kept in order of smallest scale_factor * to largest -- the code uses this ordering when finding * the best scale to use for a group of measures */ static struct unit Units[] = { { "km", 'k', 0.000001, METRIC, ALLOW_KM, 5 }, { "m", 'f', 0.001, METRIC, ALLOW_M, 5 }, { "cm", 'e', 0.1, METRIC, ALLOW_CM, 5 }, { "mm", 'm', 1, METRIC, ALLOW_MM, 4 }, { "um", 'u', 1000, METRIC, ALLOW_UM, 2 }, { "nm", 'n', 1000000, METRIC, ALLOW_NM, 0 }, { "in", 'i', 0.001, IMPERIAL, ALLOW_IN, 5 }, { "mil", 'l', 1, IMPERIAL, ALLOW_MIL, 2 }, { "cmil", 'c', 100, IMPERIAL, ALLOW_CMIL, 0 } }; #define N_UNITS ((int) (sizeof Units / sizeof Units[0])) static int min_sig_figs(double d) { char buf[50]; int rv; if(d == 0) return 0; /* Normalize to x.xxxx... form */ if(d < 0) d *= -1; while(d >= 10) d /= 10; while(d < 1) d *= 10; sprintf(buf, "%g%n", d, &rv); return rv; } /* Converts a (group of) measurement(s) to a comma-deliminated * string, with appropriate units. If more than one coord is * given, the list is enclosed in parens to make the scope of * the unit suffix clear. */ static char *CoordsToString(BDimension coord[], int n_coords, const char *printf_spec, enum e_allow allow, bool add_suffix) { char *buff, *pbuff; char printf_buff[100]; enum e_family family; double *value; const char *suffix; int i, n; value = malloc (n_coords * sizeof *value); buff = malloc (n_coords * 50); /* TODO: max 50 chars per unit */ /* Sanity checks */ if (buff == NULL || value == NULL || strlen(printf_spec) > (sizeof printf_buff) - 10) return NULL; if (allow == 0) allow = ALLOW_ALL; /* Check our freedom in choosing units */ if ((allow & ALLOW_IMPERIAL) == 0) family = METRIC; else if ((allow & ALLOW_METRIC) == 0) family = IMPERIAL; else { int met_votes = 0, imp_votes = 0; for (i = 0; i < n_coords; ++i) if(min_sig_figs(COORD_TO_MIL(coord[i])) < min_sig_figs(COORD_TO_MM(coord[i]))) ++imp_votes; else ++met_votes; if (imp_votes > met_votes) family = IMPERIAL; else family = METRIC; } /* Set base unit */ for (i = 0; i < n_coords; ++i) { switch (family) { case METRIC: value[i] = COORD_TO_MM (coord[i]); break; case IMPERIAL: value[i] = COORD_TO_MIL (coord[i]); break; } } /* Determine scale factor -- find smallest unit that brings * the whole group above unity */ for (n = 0; n < N_UNITS; ++n) { if ((Units[n].allow & allow) != 0 && (Units[n].family == family)) { int n_above_one = 0; for (i = 0; i < n_coords; ++i) if (value[i] * Units[n].scale_factor > 1 || coord[0] == 0) ++n_above_one; if (n_above_one == n_coords) break; } } /* If nothing worked, just fall to the smallest allowable unit */ if (n == N_UNITS) { n = 0; while ((Units[n].allow & allow) == 0 || Units[n].family != family) ++n; } /* Apply scale factor */ suffix = Units[n].suffix; for (i = 0; i < n_coords; ++i) value[i] = value[i] * Units[n].scale_factor; /* Create sprintf specifier */ if (printf_spec && *printf_spec) sprintf(printf_buff, ", %%%sf%%n", printf_spec); else sprintf(printf_buff, ", %%.%df%%n", Units[n].default_prec); /* Actually sprintf the values in place * (+ 2 skips the ", " for first value) */ pbuff = buff; if (n_coords > 1) *pbuff++ = '('; sprintf(pbuff, printf_buff + 2, value[0], &n); for (i = 1; i < n_coords; ++i) { pbuff += n; sprintf(pbuff, printf_buff, value[i], &n); } if (n_coords > 1) pbuff[n++] = ')'; if (add_suffix) sprintf(pbuff + n, " %s", suffix); free (value); return buff; } int pcb_sprintf(char *string, const char *fmt, ...) { char buf[255]; char buf2[255]; char *pbuf; va_list args; va_start(args, fmt); while(*fmt) { bool b_suffix = 0; int multiplier = 0; if(*fmt == '%') { char *unit_str = NULL; const char *ext_unit; BDimension value[2]; int jump = 0; int count, i; ++fmt; pbuf = buf; buf[0] = '\0'; /* Get printf sub-specifiers */ while(isdigit(*fmt) || *fmt == '.' || *fmt == ' ' || *fmt == '*' || *fmt == '#' || *fmt == 'l' || *fmt == 'L' || *fmt == 'h') *pbuf++ = *fmt++; *pbuf = '\0'; /* Get our sub-specifiers */ if(*fmt == ':') { multiplier = strtol(fmt + 1, (char **)&fmt, 0); } if(*fmt == '$') { b_suffix = 1; fmt++; } /* Handle format */ switch(*fmt) { /* Printf specs */ case 'o': case 'i': case 'd': case 'u': case 'x': case 'X': sprintf(buf2, "%%%s%c%%n", buf, *fmt); sprintf(string, buf2, va_arg(args, int), &jump); break; case 'e': case 'E': case 'f': case 'g': case 'G': sprintf(buf2, "%%%s%c%%n", buf, *fmt); if (strchr (buf, '*')) sprintf(string, buf2, va_arg(args, double), &jump); else sprintf(string, buf2, va_arg(args, double), va_arg(args, int), &jump); break; case 'c': sprintf(buf2, "%%%s%c%%n", buf, *fmt); if(buf[0] == 'l' && sizeof(int) <= sizeof(wchar_t)) { sprintf(string, buf2, va_arg(args, wchar_t), &jump); } else { sprintf(string, buf2, va_arg(args, int), &jump); } case 's': sprintf(buf2, "%%%s%c%%n", buf, *fmt); if(buf[0] == 'l') { sprintf(string, buf2, va_arg(args, wchar_t *), &jump); } else { sprintf(string, buf2, va_arg(args, char *), &jump); } break; case 'n': sprintf(buf2, "%%%s%c%%n", buf, *fmt); sprintf(string, buf2, va_arg(args, int *), &jump); break; case 'p': sprintf(buf2, "%%%s%c%%n", buf, *fmt); sprintf(string, buf2, va_arg(args, void *), &jump); break; case '%': *string++ = *fmt; break; /* Our specs */ case 'm': ++fmt; if (*fmt == '*') ext_unit = va_arg(args, const char *); value[0] = va_arg(args, BDimension); count = 1; switch(*fmt) { case 's': unit_str = CoordsToString(value, 1, buf, ALLOW_MM | ALLOW_MIL, b_suffix); break; case 'S': unit_str = CoordsToString(value, 1, buf, ALLOW_ALL, b_suffix); break; case 'M': unit_str = CoordsToString(value, 1, buf, ALLOW_METRIC, b_suffix); break; case 'L': unit_str = CoordsToString(value, 1, buf, ALLOW_IMPERIAL, b_suffix); break; case 'r': unit_str = CoordsToString(value, 1, buf, ALLOW_READABLE, b_suffix); break; case '9': value[count++] = va_arg(args, BDimension); case '8': value[count++] = va_arg(args, BDimension); case '7': value[count++] = va_arg(args, BDimension); case '6': value[count++] = va_arg(args, BDimension); case '5': value[count++] = va_arg(args, BDimension); case '4': value[count++] = va_arg(args, BDimension); case '3': value[count++] = va_arg(args, BDimension); case 'D': value[count++] = va_arg(args, BDimension); unit_str = CoordsToString(value, count, buf, ALLOW_ALL, b_suffix); break; case 'd': value[1] = va_arg(args, BDimension); unit_str = CoordsToString(value, 2, buf, ALLOW_MM | ALLOW_MIL, b_suffix); break; case '*': for (i = 0; i < N_UNITS; ++i) if (strcmp (ext_unit, Units[i].suffix) == 0) unit_str = CoordsToString(value, 1, buf, Units[i].allow, b_suffix); if (unit_str == NULL) unit_str = CoordsToString(value, 1, buf, ALLOW_ALL, b_suffix); break; default: for (i = 0; i < N_UNITS; ++i) if (*fmt == Units[i].printf_code) unit_str = CoordsToString(value, 1, buf, Units[i].allow, b_suffix); if (unit_str == NULL) unit_str = CoordsToString(value, 1, buf, ALLOW_ALL, b_suffix); break; } sprintf(string, "%s%n", unit_str, &jump); free(unit_str); break; } string += jump; } else *string++ = *fmt; ++fmt; } *string = 0; va_end(args); return 0; } /* TEST DRIVER */ int main(void) { char buff[200]; BDimension d; for(d = 10; d < 100; d += 20) { pcb_sprintf(buff, "Inputted %$mD.", d, d + 5); puts (buff); } for(d = MM_TO_COORD(10); d < MM_TO_COORD(100); d += MM_TO_COORD(2.5)) { pcb_sprintf(buff, "Inputted %$m*.", "in", (BDimension) d); puts (buff); } return 0; }