/* 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 * * 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 * Defaults to the magic subspecifiers ".4" for * metric units, ".2" for imperial, which is * consistent with existing pcb code but hardly * obvious. * */ #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) #define COORD_TO_INCH(n) (COORD_TO_MIL(n) / 1000.0) #define INCH_TO_COORD(n) (MIL_TO_COORD(n) * 1000.0) /* end rip */ enum e_force { NO_FORCE, FORCE_MM, FORCE_MIL }; 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 measurement to a string, with units, * choosing metric/imperial to use least sig.figs. */ static char *CoordsToString(BDimension coord[], int n_coords, const char *printf_spec, enum e_force force, bool scale, bool add_suffix) { char *buff, *pbuff; char printf_buff[100]; bool units_mm; double *value; double scale_factor; const char *units; 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; /* Check our freedom in choosing units */ switch (force) { case FORCE_MM: units_mm = 1; break; case FORCE_MIL: units_mm = 0; break; case NO_FORCE: units_mm = 0; for (i = 0; i < n_coords; ++i) if(min_sig_figs(COORD_TO_MM(coord[i])) < min_sig_figs(COORD_TO_MIL(coord[i]))) units_mm = 1; break; } /* Determine scale factor */ if (units_mm) { scale_factor = COORD_TO_MM(1); units = "mm"; if (*printf_spec == '\0') printf_spec = ".4"; if (scale) { bool to_um = 1; for (i = 0; i < n_coords; ++i) if (fabs(coord[i] * scale_factor) > 1) to_um = 0; if (to_um) { scale_factor *= 1000; units = "um"; } } } else { scale_factor = COORD_TO_MIL(1); units = "mil"; if (*printf_spec == '\0') printf_spec = ".2"; if (scale) { bool to_in = 1; for (i = 0; i < n_coords; ++i) if (fabs(coord[i] * scale_factor) < 1000) to_in = 0; if (to_in) { scale_factor /= 1000; units = "in"; } } } /* Apply scale factor */ for (i = 0; i < n_coords; ++i) value[i] = coord[i] * scale_factor; /* Create sprintf specifier */ sprintf(printf_buff, ", %%%sf%%n", printf_spec); /* 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", units); 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; BDimension value[2]; int jump = 0; ++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; value[0] = va_arg(args, BDimension); switch(*fmt) { case 's': unit_str = CoordsToString(value, 1, buf, NO_FORCE, 0, b_suffix); break; case 'S': unit_str = CoordsToString(value, 1, buf, NO_FORCE, 1, b_suffix); break; case 'm': unit_str = CoordsToString(value, 1, buf, FORCE_MM, 0, b_suffix); break; case 'M': unit_str = CoordsToString(value, 1, buf, FORCE_MM, 1, b_suffix); break; case 'l': unit_str = CoordsToString(value, 1, buf, FORCE_MIL, 0, b_suffix); break; case 'L': unit_str = CoordsToString(value, 1, buf, FORCE_MIL, 1, b_suffix); break; case 'd': value[1] = va_arg(args, BDimension); unit_str = CoordsToString(value, 2, buf, NO_FORCE, 0, b_suffix); break; case 'D': value[1] = va_arg(args, BDimension); unit_str = CoordsToString(value, 2, buf, NO_FORCE, 1, 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 = 3937; d < 10000; d += 984) { pcb_sprintf(buff, "Inputted %$mD.", d, d + 5); puts (buff); } return 0; }