/* pointchart.c  American Printers' Points in Decimal Inches

Copyright 2011 by David M. MacMillan.
Licensed under the GNU General Public License version 3.0 or greater

Output a "comma-separated value" list of printer's point size equivalents.
(But use a semicolon, not a comma, as the value separator because the
comma will be used as a thousands separator within the values.)

The idea here is to compute the values accurately and then to import them
(by hand) into a spreadsheet so that they can be formatted to be pretty.
This is a step backwards from Babbage, who built the printer in to his
table-calculating Difference Engine.

The output values are to be text strings, even though they are numbers.
(It's hard to get some spreadsheets to stop making assumptions about number
formatting.)  When importing this into a spreadsheet, preset the cell
formatting to "text".

The trick here is that the natural order of computation is in increasing
point size and the natural order of presentation of these is vertically
down one column and then subsequent columns.
However, CSV format is row-oriented.
So for each position in a row we must first compute what point value
(in fractional points) that position should contain
and then compute its equivalents.

Assume in the output table that each row will correspond to one particular
fractional point size (e.g., X 3/8, Y 3/8, Z 3/8).

Note: Start the table at 0, even though 0 pt obviously = 0 in.
      Doing so means that when dividing the results up visually
      all of the entries for each point and its fractions are
      grouped togther.

Note on Rounding:
   IEEE 754-1985 specifies four rounding methods:
      - round to nearest, ties to even (so 2.5 rounds to 2 but 3.5 rounds to 4)
        This distributes the error more evenly, but is not what is usually
        expected and is more difficult to use in a shop environment.
      - round toward zero
      - round toward +infinity
      - round toward -infinity
 
   IEEE 754-2008 adds the missing method:
      - round to nearest, ties away from 0 (so 2.5 rounds to 3)

   Unfortunately, C was last revised in 1999, so it implements IEEE 754-1985
   and does not provide "rounds to nearest, ties away from zero" as a
   possible rounding behavior (see fesetround())

   Since "rounds to nearest, ties away from zero" is what a typefounder
   expects (see, e.g., Rehak's _Practical Typecasting_, p. 177),
   we have to implement rounding by hand here.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

int main (int argc, char *argv[]) {
int pointwhole;
int pointfraction; /* number of eigths (pieces of eighth for pirates :-) */
double point;
double point_in_inches;
double point_in_inches_rounded;
double point_in_mm;
int lessthanpoint;   /* ARGUMENT 1. Print everything from 0 up to 
                                 but not including this integral point size. */
                     /*          So to get a chart from 0 to 47, specify 48. */
int numdivisions;    /* ARGUMENT 2. Number of divisions of each point.  
                                    It takes these four values only:
                                       0 = by whole points
                                       2 = by half points
                                       4 = by quarter points
                                       8 = by eighth points */
int numitems;
int position;

int  places;      /* ARGUMENT 3, number of decimal places to print */
char buffer [10];
int  current_place;
char digit_beyond_last_significant;
char digit_before_last_significant;
int  carry_flag;
int  ix;

int column;       /* column is a count, starting from 0 */
int numcolumns;   /* ARGUMENT 3. Number of columns to print.
                                 numcolumns is a cardinal number, starting from 1 */
int row;          /* row is a count, starting from 0 */
int numrows;      /* numrows is a cardinal number, starting from 1 */

   if (argc != 5) { 
      printf ("usage: %s LessThanPoint DivisionsPerPoint Places NumColumns\n", argv[0]);
      exit (1); 
   }

   lessthanpoint     = atoi(argv[1]);
   numdivisions = atoi(argv[2]);
   if ((numdivisions != 0) &&
       (numdivisions != 2) &&
       (numdivisions != 4) &&
       (numdivisions != 8) ) {
      printf ("usage: %s LessThanPoint DivisionsPerPoint Places NumColumns\n", argv[0]);
      printf ("       DivisionsPerPoint must be 0, 2, 4, or 8\n");
      exit (1); 
   }
   if (numdivisions != 0) 
      { numitems     = (lessthanpoint * numdivisions) + numdivisions; }
   else 
      { numitems     = lessthanpoint + 1; }  /* the +1 because we start at 0 */

   places     = atoi(argv[3]);
   if ((places < 3) || (places > 6)) {
      printf ("usage: %s LessThanPoint DivisionsPerPoint Places NumColumns\n", argv[0]);
      printf ("       3 <= Places <= 6 \n");
      exit (1); 
   }

   numcolumns = atoi(argv[4]);

   /* This is tricky - don't want to divide "cells" of a point and its 
      fractional sizes between columns. */
   if (numdivisions != 0) 
      { numrows    = ((int) ceil ((double)lessthanpoint / (double)numcolumns)) * numdivisions; }
   else 
      { numrows    = ((int) ceil ((double)lessthanpoint / (double)numcolumns)); }

   /* The outer loop is by row, by fractional components (1/8, 1/4, 3/8, ...) 
      Don't start at 0. 
      Remember that "pointfraction" is in eighths.
      */

   pointwhole = 0;
   pointfraction = 0;
   row = 0;
   do { /* a row */
      column = 0;
      printf ("\n");
      do {
         position   = (column*numrows) + row;
         if (numdivisions != 0) 
            { pointwhole = (int)floor((position)/numdivisions); }
         else 
            { pointwhole = position; }

         if (position > numitems) break; 
         /* reduce fractions */
              if (pointfraction == 0) {printf ("%d;", pointwhole);} 
         else if (pointfraction == 1) {printf ("%d 1/8;", pointwhole);}
         else if (pointfraction == 2) {printf ("%d 1/4;", pointwhole);}
         else if (pointfraction == 3) {printf ("%d 3/8;", pointwhole);}
         else if (pointfraction == 4) {printf ("%d 1/2;", pointwhole);}
         else if (pointfraction == 5) {printf ("%d 5/8;", pointwhole);}
         else if (pointfraction == 6) {printf ("%d 3/4;", pointwhole);}
         else if (pointfraction == 7) {printf ("%d 7/8;", pointwhole);}

         point = (double)((double)pointwhole+((double)pointfraction/8.0));

         /* calculate to a precision to 7 decimal places */
         point_in_inches = point * ((350.0/83.0)/25.4) / 12.0;
         /* initizlize and terminate the buffer, just in case */
         for (ix = 0; ix <= 8; ix++) { buffer[ix] = '0'; }
         buffer[9] = '\0';

         /* THIS WON'T WORK until C implements IEEE 754-2008.
            Leave it here in hopeful anticipation of that day */
/*
                if (places == 6) {
            sprintf(buffer,"%.6f",point_in_inches);
         } else if (places == 5) {
            sprintf(buffer,"%.5f",point_in_inches);
         } else if (places == 4) {
            sprintf(buffer,"%.4f",point_in_inches);
         } else if (places == 3) {
            sprintf(buffer,"%.3f",point_in_inches);
         }
*/

         /* INSTEAD, do rounding by hand */
                if (places == 6) {
            point_in_inches_rounded=trunc(point_in_inches*1000000 +0.5)/1000000;
            sprintf(buffer,"%.6f",point_in_inches_rounded);
         } else if (places == 5) {
            point_in_inches_rounded=trunc(point_in_inches*100000 + 0.5)/100000;
            sprintf(buffer,"%.5f",point_in_inches_rounded);
         } else if (places == 4) {
            point_in_inches_rounded=trunc(point_in_inches*10000 + 0.5)/10000;
            sprintf(buffer,"%.4f",point_in_inches_rounded);
         } else if (places == 3) {
            point_in_inches_rounded=trunc(point_in_inches*1000 + 0.5)/1000;
            sprintf(buffer,"%.3f",point_in_inches_rounded);
         }

         /* always print at least 3 decimal places */
         for (ix = 0; ix <= 4; ix++) { printf("%c",buffer[ix]); }
         /* then, optionally, print a comma and the rest */
         if (places > 3) {
            printf(",");
            for (ix = 5; ix <= (5+(places-4)); ix++) { 
               printf("%c",buffer[ix]); 
            }
         }

         /* no semicolon at the end of a line, or you get an extra
            blank column in the table */
         if ((position < (numitems-1)) && 
            (column < numcolumns-1)) { printf (";"); }
         column += 1;
      } while (column < numcolumns);
      row += 1;
           if (numdivisions == 0) {pointfraction += 8;}
      else if (numdivisions == 2) {pointfraction += 4;}
      else if (numdivisions == 4) {pointfraction += 2;}
      else if (numdivisions == 8) {pointfraction += 1;}
      if (pointfraction >= 8) { pointfraction = 0; }
   } while (row < numrows);
   printf("\n");
   return (0);
}

