1 /* ppmtoarbtxt.c - convert PPM to a custom text-based format
2 **
3 ** Renamed from ppmtotxt.c by Bryan Henderson in January 2003.
4 **
5 ** Copyright (C) 1995 by Peter Kirchgessner
6 **
7 ** Permission to use, copy, modify, and distribute this software and its
8 ** documentation for any purpose and without fee is hereby granted, provided
9 ** that the above copyright notice appear in all copies and that both that
10 ** copyright notice and this permission notice appear in supporting
11 ** documentation.  This software is provided "as is" without express or
12 ** implied warranty.
13 */
14 
15 #include <assert.h>
16 #include <string.h>
17 #ifdef __GLIBC__
18   #include <printf.h>  /* Necessary for parse_printf_format() */
19 #endif
20 
21 #include "pm_c_util.h"
22 #include "mallocvar.h"
23 #include "nstring.h"
24 #include "shhopt.h"
25 #include "ppm.h"
26 
27 /* HAVE_PARSE_PRINTF_FORMAT means the system library has
28    parse_printf_format(), declared in <printf.h>.  This essentially means
29    systems with GNU libc.
30 */
31 
32 #ifndef HAVE_PARSE_PRINTF_FORMAT
33   #ifdef PA_FLAG_MASK                   /* Defined in printf.h */
34     #define HAVE_PARSE_PRINTF_FORMAT 1
35   #else
36     #define HAVE_PARSE_PRINTF_FORMAT 0
37   #endif
38 #endif
39 
40 
41 
42 struct CmdlineInfo {
43     /* All the information the user supplied in the command line,
44        in a form easy for the program to use.
45     */
46     const char * inputFileName;
47     const char * bodySklFileName;
48     const char * hd;
49     const char * tl;
50     unsigned int debug;
51 };
52 
53 
54 
55 static void
parseCommandLine(int argc,const char ** argv,struct CmdlineInfo * const cmdlineP)56 parseCommandLine(int argc, const char ** argv,
57                  struct CmdlineInfo * const cmdlineP) {
58 /*----------------------------------------------------------------------------
59    Note that many of the strings that this function returns in the
60    *cmdline_p structure are actually in the supplied argv array.  And
61    sometimes, one of these strings is actually just a suffix of an entry
62    in argv!
63 -----------------------------------------------------------------------------*/
64     optEntry * option_def;
65         /* Instructions to OptParseOptions3 on how to parse our options.
66          */
67     optStruct3 opt;
68 
69     unsigned int hdSpec, tlSpec;
70 
71     unsigned int option_def_index;
72 
73     MALLOCARRAY(option_def, 100);
74 
75     option_def_index = 0;   /* incremented by OPTENTRY */
76     OPTENT3(0,   "hd",   OPT_STRING, &cmdlineP->hd,
77             &hdSpec,             0);
78     OPTENT3(0,   "tl",   OPT_STRING, &cmdlineP->tl,
79             &tlSpec,             0);
80     OPTENT3(0,   "debug", OPT_FLAG, NULL,
81             &cmdlineP->debug,      0);
82 
83     opt.opt_table = option_def;
84     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
85     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
86 
87     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
88     free(option_def);
89 
90     if (!hdSpec)
91         cmdlineP->hd = NULL;
92 
93     if (!tlSpec)
94         cmdlineP->tl = NULL;
95 
96     if (argc-1 < 1)
97         pm_error("You must specify the body skeleton file name as an "
98                  "argument");
99     else {
100         cmdlineP->bodySklFileName = strdup(argv[1]);
101 
102         if (argc-1 < 2)
103             cmdlineP->inputFileName = strdup("-");  /* he wants stdin */
104         else {
105             cmdlineP->inputFileName = strdup(argv[2]);
106             if (argc-1 > 2)
107                 pm_error("Too many arguments.  The only possible arguments "
108                          "are the body skeleton file name and input image "
109                          "file name");
110         }
111     }
112 }
113 
114 
115 
116 
117 typedef enum {
118 /* The types of object we handle */
119     BDATA, IRED, IGREEN, IBLUE, ILUM, FRED, FGREEN, FBLUE, FLUM,
120     WIDTH, HEIGHT, POSX, POSY
121 } SkeletonObjectType;
122 
123 typedef enum {
124     OBJTYP_ICOLOR, OBJTYP_FCOLOR, OBJTYP_INT, OBJTYP_BDATA
125 } SkeletonObjectClass;
126 
127 /* Maximum size for a format string ("%d" etc.) */
128 /* Add one to this size for the terminating '\0'. */
129 #define MAXFORMAT 16
130 
131 typedef union {
132 /* The data we keep for each object */
133     struct Bndat {
134         char * bdat;   /* Binary data (text with newlines etc.) */
135         unsigned int ndat;
136     } binData;
137 
138     struct Icdat {
139         char icformat[MAXFORMAT+1];  /* Integer colors */
140         unsigned int icolmin, icolmax;
141     } icolData;
142 
143     struct Fcdat {
144         char fcformat[MAXFORMAT+1];  /* Float colors */
145         double fcolmin, fcolmax;
146     } fcolData;
147 
148     struct Idat {
149         char iformat[MAXFORMAT+1];   /* Integer data */
150     } iData;
151 } SkeletonObjectData;
152 
153 
154 /* Each object has a type and some data */
155 typedef struct {
156     SkeletonObjectType objType;
157     SkeletonObjectData odata;
158 } SkeletonObject;
159 
160 
161 
162 #define MAX_SKL_HEAD_OBJ 64
163 #define MAX_SKL_BODY_OBJ 256
164 #define MAX_SKL_TAIL_OBJ 64
165 #define MAX_LINE_BUF 1024
166 #define MAX_OBJ_BUF 80
167 
168 
169 
170 static void
dumpSkeleton(SkeletonObject ** const skeletonPList,unsigned int const nSkeleton)171 dumpSkeleton(SkeletonObject ** const skeletonPList,
172              unsigned int      const nSkeleton) {
173 
174     unsigned int i;
175 
176     pm_message("%u objects", nSkeleton);
177 
178     for (i = 0; i < nSkeleton; ++i) {
179         SkeletonObject * const skeletonP = skeletonPList[i];
180 
181         pm_message("  Object: Type %u", skeletonP->objType);
182     }
183 }
184 
185 
186 
187 static void
dumpAllSkeleton(SkeletonObject ** const bodySkeletonPList,unsigned int const bodyNskl,SkeletonObject ** const headSkeletonPList,unsigned int const headNskl,SkeletonObject ** const tailSkeletonPList,unsigned int const tailNskl)188 dumpAllSkeleton(SkeletonObject ** const bodySkeletonPList,
189                 unsigned int      const bodyNskl,
190                 SkeletonObject ** const headSkeletonPList,
191                 unsigned int      const headNskl,
192                 SkeletonObject ** const tailSkeletonPList,
193                 unsigned int      const tailNskl) {
194 
195     pm_message("Body skeleton:");
196     dumpSkeleton(bodySkeletonPList, bodyNskl);
197 
198     pm_message("Head skeleton:");
199     dumpSkeleton(headSkeletonPList, headNskl);
200 
201     pm_message("Tail skeleton:");
202     dumpSkeleton(tailSkeletonPList, tailNskl);
203 }
204 
205 
206 
207 static void
writeBndat(FILE * const ofP,SkeletonObject * const objectP)208 writeBndat(FILE *           const ofP,
209            SkeletonObject * const objectP) {
210 
211     struct Bndat * const bdataP = &objectP->odata.binData;
212 
213     fwrite(bdataP->bdat, bdataP->ndat, 1, ofP);
214 }
215 
216 
217 
218 static void
writeIcol(FILE * const ofP,SkeletonObject * const objectP,double const value)219 writeIcol(FILE *           const ofP,
220           SkeletonObject * const objectP,
221           double           const value) {
222 
223     /* Unlike Netpbm, the output format does not have an upper limit for
224        maxval.  Here we allow all values representable by unsigned int.
225     */
226 
227     struct Icdat * const icdataP = &objectP->odata.icolData;
228     unsigned int const outValue =
229         ROUNDU( icdataP->icolmin +
230                 ((double)icdataP->icolmax - icdataP->icolmin) * value);
231 
232     fprintf(ofP, icdataP->icformat, outValue);
233 }
234 
235 
236 
237 static void
writeFcol(FILE * const ofP,SkeletonObject * const objectP,double const value)238 writeFcol(FILE *           const ofP,
239           SkeletonObject * const objectP,
240           double           const value) {
241 
242     struct Fcdat * const fcdataP = &objectP->odata.fcolData;
243 
244     fprintf(ofP, fcdataP->fcformat,
245             (double)
246             (fcdataP->fcolmin
247              + (fcdataP->fcolmax - fcdataP->fcolmin) * value));
248 }
249 
250 
251 
252 static void
writeIdat(FILE * const ofP,SkeletonObject * const objectP,unsigned int const value)253 writeIdat(FILE *           const ofP,
254           SkeletonObject * const objectP,
255           unsigned int     const value) {
256 
257     struct Idat * const idataP = &objectP->odata.iData;
258 
259     fprintf(ofP, idataP->iformat, value);
260 }
261 
262 
263 
264 static void
writeText(FILE * const ofP,unsigned int const nObj,SkeletonObject ** const obj,unsigned int const width,unsigned int const height,unsigned int const x,unsigned int const y,double const red,double const green,double const blue)265 writeText(FILE *            const ofP,
266           unsigned int      const nObj,
267           SkeletonObject ** const obj,
268           unsigned int      const width,
269           unsigned int      const height,
270           unsigned int      const x,
271           unsigned int      const y,
272           double            const red,
273           double            const green,
274           double            const blue) {
275 
276     unsigned int i;
277 
278     for (i = 0; i < nObj; ++i) {
279         switch (obj[i]->objType) {
280         case BDATA:
281             writeBndat(ofP, obj[i]);
282             break;
283         case IRED:
284             writeIcol(ofP, obj[i], red);
285             break;
286         case IGREEN:
287             writeIcol(ofP, obj[i], green);
288             break;
289         case IBLUE:
290             writeIcol(ofP, obj[i], blue);
291             break;
292         case ILUM:
293             writeIcol(ofP, obj[i],
294                       PPM_LUMINR*red + PPM_LUMING*green + PPM_LUMINB*blue);
295             break;
296         case FRED:
297             writeFcol(ofP, obj[i], red);
298             break;
299         case FGREEN:
300             writeFcol(ofP, obj[i], green);
301             break;
302         case FBLUE:
303             writeFcol(ofP, obj[i], blue);
304             break;
305         case FLUM:
306             writeFcol(ofP, obj[i],
307                       PPM_LUMINR*red + PPM_LUMING*green + PPM_LUMINB*blue);
308             break;
309         case WIDTH:
310             writeIdat(ofP, obj[i], width);
311             break;
312         case HEIGHT:
313             writeIdat(ofP, obj[i], height);
314             break;
315         case POSX:
316             writeIdat(ofP, obj[i], x);
317             break;
318         case POSY:
319             writeIdat(ofP, obj[i], y);
320             break;
321         }
322     }
323 }
324 
325 
326 
327 static SkeletonObjectClass
objClass(SkeletonObjectType const objType)328 objClass(SkeletonObjectType const objType) {
329 
330     switch (objType) {
331     case IRED:
332     case IGREEN:
333     case IBLUE:
334     case ILUM:
335         return OBJTYP_ICOLOR;
336 
337     case FRED:
338     case FGREEN:
339     case FBLUE:
340     case FLUM:
341         return OBJTYP_FCOLOR;
342 
343     case WIDTH:
344     case HEIGHT:
345     case POSX:
346     case POSY:
347         return OBJTYP_INT;
348     case BDATA:
349         return OBJTYP_BDATA;
350     }
351     return 999; /* quiet compiler warning */
352 }
353 
354 
355 /*----------------------------------------------------------------------------
356   Format string validation
357 
358   We validate format strings (such as "%f" "%03d") found in the skeleton files
359   for convenience, even though behavior is documented as undefined when the
360   user supplies a bogus format string.  Certain strings, most notably those
361   with "%n", are especially risky; they pose a security threat.
362 
363   On systems with Glibc, we check with parse_printf_format().  On other
364   systems we conduct a cursory scan of the characters in the format string,
365   looking for characters that trigger non-numeric conversions, etc.
366 
367   Documentation for parse_printf_format() is usually available in texinfo
368   format on GNU/Linux systems.  As of Dec. 2014 there is no official man page.
369 
370   Online documentation is available from:
371   https://
372   www.gnu.org/software/libc/manual/html_node/Parsing-a-Template-String.html
373 -----------------------------------------------------------------------------*/
374 
375 #if HAVE_PARSE_PRINTF_FORMAT
376 static void
validateParsePrintfFlag(int const printfConversion,SkeletonObjectType const ctyp,const char ** const errorP)377 validateParsePrintfFlag(int                const printfConversion,
378                         SkeletonObjectType const ctyp,
379                         const char **      const errorP) {
380 /*----------------------------------------------------------------------------
381   Assuming 'printfConversion' is the value reported by parse_printf_format()
382   as the type of argument a format string requires,
383   return an explanation of how it is incompatible with 'ctyp' as
384   *errorP -- return null string if it is compatible.
385 -----------------------------------------------------------------------------*/
386     /* We first check for "%n", then the type modifiers, and finally the
387        actual conversion type (char, int, float, double, string or pointer.)
388     */
389     switch (printfConversion & PA_FLAG_MASK) {
390     case PA_FLAG_PTR:  /* This means %n */
391         pm_asprintf(errorP, "Contains a %%n conversion specifier");
392         break;
393 
394     case PA_FLAG_SHORT:
395     case PA_FLAG_LONG:
396     case PA_FLAG_LONG_LONG:
397         /* We omit PA_FLAG_LONG_DOUBLE because it is a synonym for
398            PA_FLAG_LONG_LONG: listing both causes compilation errors.
399         */
400         pm_asprintf(errorP, "Invalid type modifier");
401         break;
402 
403     default:
404         switch (printfConversion & ~PA_FLAG_MASK) {
405         case PA_CHAR:
406             pm_message("Warning: char type conversion.");
407         case PA_INT:
408             if(objClass(ctyp) == OBJTYP_ICOLOR ||
409                objClass(ctyp) == OBJTYP_INT )
410                 *errorP = NULL;
411             else
412                 pm_asprintf(errorP, "Conversion specifier requires a "
413                             "character or integer argument, but it is in "
414                             "a replacement sequence for a different type");
415             break;
416         case PA_DOUBLE:
417             if(objClass(ctyp) == OBJTYP_FCOLOR)
418                 *errorP = NULL;
419             else
420                 pm_asprintf(errorP, "Conversion specifier requires a "
421                             "double precision floating point argument, "
422                             "but it is in "
423                             "a replacement sequence for a different type");
424             break;
425         case PA_FLOAT:
426         case PA_STRING:    /* %s */
427         case PA_POINTER:   /* %p */
428         default:
429             pm_asprintf(errorP, "Conversion specifier requires an argument of "
430                         "a type that this program never provides for "
431                         "any replacement sequence");
432         }
433     }
434 }
435 #endif
436 
437 
438 
439 #if HAVE_PARSE_PRINTF_FORMAT
440 static void
validateFormatWithPpf(const char * const format,SkeletonObjectType const ctyp,const char ** const errorP)441 validateFormatWithPpf(const char *       const format,
442                       SkeletonObjectType const ctyp,
443                       const char **      const errorP) {
444 /*----------------------------------------------------------------------------
445   Validate format string 'format' for use with a skeleton of type 'ctyp',
446   using the system parse_printf_format() function.
447 
448   Return as *errorP an explanation of how it is invalid, or a null string
449   if it is valid.
450 -----------------------------------------------------------------------------*/
451     /* We request parse_printf_format() to report the details of the first
452        8 conversions.  8 because the maximum length of format is 16 means it
453        can have up to 8 conversions: "%d%d%d%d%d%d%d%d".
454 
455        Actually this is more than necessary: we are concerned with only the
456        first conversion and whether there it is the only one.
457     */
458 
459     int printfConversion[MAXFORMAT/2] = {0, 0, 0, 0, 0, 0, 0, 0};
460 
461     size_t const n =
462         parse_printf_format(format, MAXFORMAT/2, printfConversion);
463 
464     switch (n) {
465     case 0:
466         pm_asprintf(errorP, "No transformation found");
467         break;
468 
469     case 1:
470         validateParsePrintfFlag(printfConversion[0], ctyp, errorP);
471         break;
472 
473     default:
474         pm_asprintf(errorP, "Has %lu extra transformation%s ",
475                     (unsigned long)n-1, n-1 > 1 ? "s" : "");
476         break;
477     }
478 }
479 #endif
480 
481 
482 
483 static void
validateFormatOne(char const typeSpecifier,bool const isLastInString,SkeletonObjectType const ctyp,bool * const validatedP,const char ** const errorP)484 validateFormatOne(char               const typeSpecifier,
485                   bool               const isLastInString,
486                   SkeletonObjectType const ctyp,
487                   bool *             const validatedP,
488                   const char **      const errorP) {
489 
490     switch (typeSpecifier) {
491         /* Valid character encountered.  Skip. */
492         /* ' ' (space) is listed here, but should never occur for
493            we use sscanf() to parse the fields.
494         */
495     case ' ': case '-': case '+': case '\'': case '#': case '.':
496     case '0': case '1': case '2': case '3': case '4': case '5':
497     case '6': case '7': case '8': case '9':
498         break;
499 
500     case 'c': case 'C':
501         pm_message("Warning: char type conversion: %%%c.", typeSpecifier);
502     case 'i': case 'd': case 'u': case 'o': case 'x': case 'X':
503         if (!isLastInString)
504             pm_asprintf(errorP, "Extra characters at end");
505         else if(objClass(ctyp) != OBJTYP_ICOLOR &&
506                 objClass(ctyp) != OBJTYP_INT )
507             pm_asprintf(errorP, "Conversion type mismatch");
508         else
509             *validatedP = true;
510         break;
511 
512     case 'f': case 'F': case 'g': case 'G': case 'a': case 'A':
513         if (!isLastInString)
514             pm_asprintf(errorP, "Extra characters at end");
515         else if(objClass(ctyp) != OBJTYP_FCOLOR)
516             pm_asprintf(errorP, "Conversion type mismatch");
517         else
518             *validatedP = true;
519         break;
520 
521     case '\0':
522         pm_asprintf(errorP, "No conversion specified");
523         break;
524     case '%':
525         pm_asprintf(errorP, "No more than one %% is allowed");
526         break;
527     case '$':
528     case '*':
529         pm_asprintf(errorP, "%c is not allowed", typeSpecifier);
530         break;
531     case 'h': case 'l': case 'L': case 'q': case 'j': case 'Z': case 't':
532         pm_asprintf(errorP, "Modifier %c is not allowed in format",
533                     typeSpecifier);
534         break;
535     case 's': case 'S': case 'm': case 'p': case 'n':
536         pm_asprintf(errorP, "Invalid conversion type");
537         break;
538     default:
539         pm_asprintf(errorP, "Abnormal character");
540         break;
541     }
542 }
543 
544 
545 
546 static void
validateFormatGen(const char * const format,SkeletonObjectType const ctyp,const char ** const errorP)547 validateFormatGen(const char *       const format,
548                   SkeletonObjectType const ctyp,
549                   const char **      const errorP)  {
550 /*----------------------------------------------------------------------------
551   Validate format string 'format' for use with a skeleton of type 'ctyp',
552   without using the system parse_printf_format() function.
553 
554   The string must begin with "%" and end with the translation type character
555   ("%d", "%x", "%f", etc.)
556 
557   We check only for invalid characters.  Invalid constructs, such as
558   "%00.00.00d" will pass this test.
559 
560   Return as *errorP an explanation of how it is invalid, or a null string
561   if it is valid.
562 -----------------------------------------------------------------------------*/
563     if (format[0] != '%')
564         pm_asprintf(errorP, "Does not start with %%");
565     else {
566         unsigned int i;
567         bool validated;
568 
569         for (i = 1, validated = false, *errorP = NULL;
570              !validated && !*errorP;
571              ++i) {
572 
573             validateFormatOne(format[i], format[i+1] == '\0', ctyp,
574                               &validated, errorP);
575         }
576     }
577 }
578 
579 
580 
581 static void
validateFormat(const char * const format,SkeletonObjectType const ctyp)582 validateFormat(const char *       const format,
583                SkeletonObjectType const ctyp) {
584 
585     const char * error;
586 
587     if (strlen(format) > MAXFORMAT)
588         pm_asprintf(&error, "Too long");
589     else {
590 #if HAVE_PARSE_PRINTF_FORMAT
591         if (true)
592             validateFormatWithPpf(format, ctyp, &error);
593         else  /* Silence compiler warning about unused function */
594             validateFormatGen(format, ctyp, &error);
595 #else
596         validateFormatGen(format, ctyp, &error);
597 #endif
598     }
599 
600     if (error)
601         pm_error("Invalid format string '%s'.  %s", format, error);
602 }
603 
604 
605 
606 static SkeletonObject *
newBinDataObj(unsigned int const nDat,const char * const bdat)607 newBinDataObj(unsigned int const nDat,
608               const char * const bdat) {
609 /*----------------------------------------------------------------------------
610   Create a binary data object.
611 -----------------------------------------------------------------------------*/
612     SkeletonObject * objectP;
613 
614     objectP = malloc(sizeof(*objectP) + nDat);
615 
616     if (!objectP)
617         pm_error("Failed to allocate memory for binary data object "
618                  "with %u bytes", nDat);
619 
620     objectP->objType = BDATA;
621     objectP->odata.binData.ndat = nDat;
622     objectP->odata.binData.bdat = ((char *)objectP) + sizeof(SkeletonObject);
623     memcpy(objectP->odata.binData.bdat, bdat, nDat);
624 
625     return objectP;
626 }
627 
628 
629 
630 static SkeletonObject *
newIcolDataObj(SkeletonObjectType const ctyp,const char * const format,unsigned int const icolmin,unsigned int const icolmax)631 newIcolDataObj(SkeletonObjectType const ctyp,
632                const char *       const format,
633                unsigned int       const icolmin,
634                unsigned int       const icolmax) {
635 /*----------------------------------------------------------------------------
636   Create integer color data object.
637 -----------------------------------------------------------------------------*/
638     SkeletonObject * objectP;
639 
640     MALLOCVAR(objectP);
641 
642     if (!objectP)
643         pm_error("Failed to allocate memory for an integer color data "
644                  "object");
645 
646     objectP->objType = ctyp;
647     validateFormat(format, ctyp);
648     strcpy(objectP->odata.icolData.icformat, format);
649     objectP->odata.icolData.icolmin = icolmin;
650     objectP->odata.icolData.icolmax = icolmax;
651 
652     return objectP;
653 }
654 
655 
656 
657 static SkeletonObject *
newFcolDataObj(SkeletonObjectType const ctyp,const char * const format,double const fcolmin,double const fcolmax)658 newFcolDataObj(SkeletonObjectType  const ctyp,
659                const char *        const format,
660                double              const fcolmin,
661                double              const fcolmax) {
662 /*----------------------------------------------------------------------------
663   Create float color data object.
664 -----------------------------------------------------------------------------*/
665     SkeletonObject * objectP;
666 
667     MALLOCVAR(objectP);
668 
669     if (!objectP)
670         pm_error("Failed to allocate memory for a float color data object");
671 
672     objectP->objType = ctyp;
673     validateFormat(format, ctyp);
674     strcpy(objectP->odata.fcolData.fcformat, format);
675     objectP->odata.fcolData.fcolmin = fcolmin;
676     objectP->odata.fcolData.fcolmax = fcolmax;
677 
678     return objectP;
679 }
680 
681 
682 
683 static SkeletonObject *
newIdataObj(SkeletonObjectType const ctyp,const char * const format)684 newIdataObj(SkeletonObjectType const ctyp,
685             const char *       const format) {
686 /*----------------------------------------------------------------------------
687   Create universal data object.
688 -----------------------------------------------------------------------------*/
689     SkeletonObject * objectP;
690 
691     MALLOCVAR(objectP);
692 
693     if (!objectP)
694         pm_error("Failed to allocate memory for a universal data object");
695 
696     objectP->objType = ctyp;
697     validateFormat(format, ctyp);
698     strcpy(objectP->odata.iData.iformat, format);
699 
700     return objectP;
701 }
702 
703 
704 
705 static char const escape = '#';
706 
707 
708 
709 static SkeletonObjectType
interpretObjType(const char * const typstr)710 interpretObjType(const char * const typstr) {
711 
712     SkeletonObjectType objType;
713 
714     /* handle integer colors */
715     if      (streq(typstr, "ired")  ) objType = IRED;
716     else if (streq(typstr, "igreen")) objType = IGREEN;
717     else if (streq(typstr, "iblue") ) objType = IBLUE;
718     else if (streq(typstr, "ilum")  ) objType = ILUM;
719     /* handle real colors */
720     else if (streq(typstr, "fred")  ) objType = FRED;
721     else if (streq(typstr, "fgreen")) objType = FGREEN;
722     else if (streq(typstr, "fblue") ) objType = FBLUE;
723     else if (streq(typstr, "flum")  ) objType = FLUM;
724     /* handle integer data */
725     else if (streq(typstr, "width") ) objType = WIDTH;
726     else if (streq(typstr, "height")) objType = HEIGHT;
727     else if (streq(typstr, "posx")  ) objType = POSX;
728     else if (streq(typstr, "posy")  ) objType = POSY;
729     else                              objType = BDATA;
730 
731     return objType;
732 }
733 
734 
735 
736 static SkeletonObject *
newIcSkelFromReplString(const char * const icolorObjstr,SkeletonObjectType const objType)737 newIcSkelFromReplString(const char *       const icolorObjstr,
738                         SkeletonObjectType const objType) {
739 /*----------------------------------------------------------------------------
740   A new skeleton for an integer color substitution specifier (class
741   OBJTYP_ICOLOR) whose replacement string (the stuff between the parentheses
742   in #(...)) says substitution type 'objType' and the rest of the
743   replacement string is 'icolorObjstr'.
744 -----------------------------------------------------------------------------*/
745     SkeletonObject * retval;
746     unsigned int icolmin, icolmax;
747     char formstr[MAX_OBJ_BUF];
748     int nOdata;
749 
750     nOdata = sscanf(icolorObjstr, "%s%u%u", formstr, &icolmin, &icolmax);
751 
752     if (nOdata == 3)
753         retval = newIcolDataObj(objType, formstr, icolmin, icolmax);
754     else if (nOdata == EOF) {
755         /* No arguments specified.  Use defaults */
756         retval = newIcolDataObj(objType, "%u", 0, 255);
757     } else
758         retval = NULL;
759 
760     return retval;
761 }
762 
763 
764 
765 static SkeletonObject *
newFcSkelFromReplString(const char * const fcolorObjstr,SkeletonObjectType const objType)766 newFcSkelFromReplString(const char *       const fcolorObjstr,
767                         SkeletonObjectType const objType) {
768 /*----------------------------------------------------------------------------
769   A new skeleton for a floating point color substitution specifier (class
770   OBJTYP_FCOLOR) whose replacement string (the stuff between the parentheses
771   in #(...)) says substitution type 'objType' and the rest of the
772   replacement string is 'fcolorObjstr'.
773 -----------------------------------------------------------------------------*/
774     SkeletonObject * retval;
775     double fcolmin, fcolmax;
776     char formstr[MAX_OBJ_BUF];
777     int nOdata;
778 
779     nOdata = sscanf(fcolorObjstr, "%s%lf%lf", formstr, &fcolmin, &fcolmax);
780 
781     if (nOdata == 3)
782         retval = newFcolDataObj(objType, formstr, fcolmin, fcolmax);
783     else if (nOdata == EOF) {
784         /* No arguments specified.  Use defaults */
785         retval = newFcolDataObj(objType, "%f", 0.0, 1.0);
786     } else
787         retval = NULL;
788 
789     return retval;
790 }
791 
792 
793 
794 static SkeletonObject *
newISkelFromReplString(const char * const intObjstr,SkeletonObjectType const objType)795 newISkelFromReplString(const char *       const intObjstr,
796                        SkeletonObjectType const objType) {
797 /*----------------------------------------------------------------------------
798   A new skeleton for an integer substitution specifier (class OBJTYP_INT)
799   whose replacement string (the stuff between the parentheses in #(...))
800   says substitution type 'objType' and the rest of the replacement string is
801   'intObjstr'.
802 -----------------------------------------------------------------------------*/
803     SkeletonObject * retval;
804     char formstr[MAX_OBJ_BUF];
805     int nOdata;
806 
807     nOdata = sscanf(intObjstr, "%s", formstr);
808 
809     if (nOdata == 1)
810         retval = newIdataObj(objType, formstr);
811     else if (nOdata == EOF) {
812         /* No arguments specified.  Use defaults */
813         retval = newIdataObj(objType, "%u");
814     } else
815         retval = NULL;
816 
817     return retval;
818 }
819 
820 
821 
822 static SkeletonObject *
newSkeletonFromReplString(const char * const objstr)823 newSkeletonFromReplString(const char * const objstr) {
824 /*----------------------------------------------------------------------------
825   A new skeleton created from the replacement string 'objstr' (the stuff
826   between the parentheses in #(...) ).
827 
828   Return NULL if it isn't a valid replacement string.
829 -----------------------------------------------------------------------------*/
830     /* We use sscanf() to parse the contents of objstr, giving it a format
831        template with the largest number of fields possible plus one extra to
832        pick up and check for the existence of invalid trailing characters.  We
833        read and discard fields beyond the first, if any.  The appropriate
834        new**SkelFromReplString() function determines their contents with a
835        separate call to sscanf().
836     */
837 
838     SkeletonObject * retval;
839     char typstr[MAX_OBJ_BUF];
840     int typlen;
841     SkeletonObjectType objType;
842     int conversionCt;
843     char s1[MAX_OBJ_BUF];    /* Dry read. */
844     char s2[MAX_OBJ_BUF];    /* Extra tailing characters. */
845     float f1, f2;            /* Dry read. */
846 
847     typstr[0] = '\0';  /* initial value */
848 
849     conversionCt = sscanf(objstr, "%s%n%s%f%f%s",
850                           typstr, &typlen, s1, &f1, &f2, s2);
851     switch (conversionCt) {
852     case 1: case 2: case 4:
853         objType = interpretObjType(typstr);
854       break;
855     default:
856         objType = BDATA;
857     }
858 
859     switch (objClass(objType)) {
860     case OBJTYP_ICOLOR:
861         retval = newIcSkelFromReplString(&objstr[typlen], objType);
862         break;
863     case OBJTYP_FCOLOR:
864         retval = newFcSkelFromReplString(&objstr[typlen], objType);
865         break;
866     case OBJTYP_INT:
867         retval = newISkelFromReplString(&objstr[typlen], objType);
868         break;
869     case OBJTYP_BDATA:
870         retval = NULL;
871     }
872     return retval;
873 }
874 
875 
876 
877 static void
readThroughCloseParen(FILE * const ifP,char * const objstr,size_t const objstrSize,bool * const unclosedP)878 readThroughCloseParen(FILE * const ifP,
879                       char * const objstr,
880                       size_t const objstrSize,
881                       bool * const unclosedP) {
882 /*----------------------------------------------------------------------------
883    Read *ifP up through close parenthesis ( ')' ) into 'objstr', which
884    is of size 'objstrSize'.  Make it a NUL-terminated string.
885 
886    Return *unclosedP true iff we run out of file or run out of objstr
887    before we see a close parenthesis.  In this case, return the rest of
888    the file, or as much as fits, in 'objstr', not NUL-terminated.
889 -----------------------------------------------------------------------------*/
890     unsigned int i;
891     bool eof;
892     bool gotEscSeq;
893 
894     for (i= 0, eof = false, gotEscSeq = false;
895          i < objstrSize - 1 && !gotEscSeq && !eof;
896          ++i) {
897 
898         int rc;
899 
900         rc = getc(ifP);
901         if (rc == EOF)
902             eof = true;
903         else {
904             char const chr = rc;
905             if (chr == ')') {
906                 gotEscSeq = true;
907                 objstr[i] = '\0';
908 	        } else
909                 objstr[i] = chr;
910         }
911     }
912     *unclosedP = !gotEscSeq;
913 }
914 
915 
916 
917 typedef struct {
918     unsigned int      capacity;
919     SkeletonObject ** skeletonPList;
920     unsigned int      nSkeleton;
921 } SkeletonBuffer;
922 
923 
924 
925 static void
SkeletonBuffer_init(SkeletonBuffer * const bufferP,unsigned int const capacity,SkeletonObject ** const skeletonPList)926 SkeletonBuffer_init(SkeletonBuffer *  const bufferP,
927                     unsigned int      const capacity,
928                     SkeletonObject ** const skeletonPList) {
929 
930     bufferP->capacity      = capacity;
931     bufferP->skeletonPList = skeletonPList;
932     bufferP->nSkeleton     = 0;
933 }
934 
935 
936 
937 static void
SkeletonBuffer_add(SkeletonBuffer * const bufferP,SkeletonObject * const skeletonP)938 SkeletonBuffer_add(SkeletonBuffer * const bufferP,
939                    SkeletonObject * const skeletonP) {
940 
941     if (bufferP->nSkeleton >= bufferP->capacity)
942         pm_error("Too many skeletons.  Max = %u", bufferP->capacity);
943 
944     bufferP->skeletonPList[bufferP->nSkeleton++] = skeletonP;
945 }
946 
947 
948 
949 typedef struct {
950 
951     char data[MAX_LINE_BUF + MAX_OBJ_BUF + 16];
952 
953     unsigned int length;
954 
955     SkeletonBuffer * skeletonBufferP;
956         /* The buffer to which we flush.  Flushing means turning all the
957            characters currently in our buffer into a binary skeleton object
958            here.
959         */
960 
961 } Buffer;
962 
963 
964 
965 static void
Buffer_init(Buffer * const bufferP,SkeletonBuffer * const skeletonBufferP)966 Buffer_init(Buffer *         const bufferP,
967             SkeletonBuffer * const skeletonBufferP) {
968 
969     bufferP->skeletonBufferP = skeletonBufferP;
970     bufferP->length = 0;
971 }
972 
973 
974 
975 static void
Buffer_flush(Buffer * const bufferP)976 Buffer_flush(Buffer * const bufferP) {
977 /*----------------------------------------------------------------------------
978    Flush the buffer out to a binary skeleton object.
979 -----------------------------------------------------------------------------*/
980     SkeletonBuffer_add(bufferP->skeletonBufferP,
981                        newBinDataObj(bufferP->length, bufferP->data));
982 
983     bufferP->length = 0;
984 }
985 
986 
987 
988 static void
Buffer_add(Buffer * const bufferP,char const newChar)989 Buffer_add(Buffer * const bufferP,
990            char     const newChar) {
991 
992     if (bufferP->length >= MAX_LINE_BUF)
993         Buffer_flush(bufferP);
994 
995     assert(bufferP->length < MAX_LINE_BUF);
996 
997     bufferP->data[bufferP->length++] = newChar;
998 }
999 
1000 
1001 
1002 static void
Buffer_dropFinalNewline(Buffer * const bufferP)1003 Buffer_dropFinalNewline(Buffer * const bufferP) {
1004 /*----------------------------------------------------------------------------
1005    If the last thing in the buffer is a newline, remove it.
1006 -----------------------------------------------------------------------------*/
1007     if (bufferP->length >= 1 && bufferP->data[bufferP->length-1] == '\n') {
1008             /* Drop finishing newline character */
1009             --bufferP->length;
1010     }
1011 }
1012 
1013 
1014 
1015 static void
addImpostorReplacementSeq(Buffer * const bufferP,const char * const seqContents)1016 addImpostorReplacementSeq(Buffer *     const bufferP,
1017                           const char * const seqContents) {
1018 /*----------------------------------------------------------------------------
1019   Add to buffer *bufferP something that looks like a replacement sequence but
1020   doesn't have the proper contents (the stuff between the parentheses) to be
1021   one.  For example,
1022 
1023   "#(fread x)"
1024 
1025   seqContents[] is the contents, NUL-terminated.
1026 -----------------------------------------------------------------------------*/
1027     const char * p;
1028 
1029     Buffer_add(bufferP, escape);
1030     Buffer_add(bufferP, '(');
1031 
1032     for (p = &seqContents[0]; *p; ++p)
1033         Buffer_add(bufferP, *p);
1034 
1035     Buffer_add(bufferP, ')');
1036 }
1037 
1038 
1039 
1040 static void
readSkeletonFile(const char * const filename,unsigned int const maxskl,const char ** const errorP,unsigned int * const nSkeletonP,SkeletonObject ** const skeletonPList)1041 readSkeletonFile(const char *      const filename,
1042                  unsigned int      const maxskl,
1043                  const char **     const errorP,
1044                  unsigned int *    const nSkeletonP,
1045                  SkeletonObject ** const skeletonPList) {
1046 /*----------------------------------------------------------------------------
1047 -----------------------------------------------------------------------------*/
1048     FILE * sklfileP;
1049     SkeletonBuffer skeletonBuffer;
1050         /* A buffer for accumulating skeleton objects */
1051     Buffer buffer;
1052         /* A buffer for accumulating binary (literal; unsubstituted) data, on
1053            its way to becoming a binary skeleton object.
1054         */
1055     bool eof;
1056     const char * error;
1057 
1058     SkeletonBuffer_init(&skeletonBuffer, maxskl, skeletonPList);
1059 
1060     Buffer_init(&buffer, &skeletonBuffer);
1061 
1062     sklfileP = pm_openr(filename);
1063 
1064     for (eof = false, error = NULL; !eof && !error; ) {
1065 
1066         int rc;
1067 
1068         rc = getc(sklfileP);
1069 
1070         if (rc == EOF)
1071             eof = true;
1072         else {
1073             char const chr = rc;
1074 
1075             if (chr != escape) {
1076                 /* Not a replacement sequence; just a literal character */
1077                 Buffer_add(&buffer, chr);
1078             } else {
1079                 int rc;
1080                 rc = getc(sklfileP);
1081                 if (rc == EOF) {
1082                     /* Not a replacement sequence, just an escape character
1083                        at the end of the file.
1084                     */
1085                     Buffer_add(&buffer, escape);
1086                     eof = true;
1087                 } else {
1088                     char const chr = rc;
1089 
1090                     if (chr != '(') {
1091                         /* Not a replacement sequence, just a lone escape
1092                            character
1093                         */
1094                         Buffer_add(&buffer, escape);
1095                         Buffer_add(&buffer, chr);
1096                     } else {
1097                         char objstr[MAX_OBJ_BUF];
1098                         bool unclosed;
1099                         readThroughCloseParen(sklfileP,
1100                                               objstr, sizeof(objstr),
1101                                               &unclosed);
1102                         if (unclosed)
1103                             pm_asprintf(&error, "Unclosed parentheses "
1104                                         "in #() escape sequence");
1105                         else {
1106                             SkeletonObject * const skeletonP =
1107                                 newSkeletonFromReplString(objstr);
1108 
1109                             if (skeletonP) {
1110                                 Buffer_flush(&buffer);
1111                                 SkeletonBuffer_add(&skeletonBuffer, skeletonP);
1112                             } else
1113                                 addImpostorReplacementSeq(&buffer, objstr);
1114                         }
1115                     }
1116                 }
1117             }
1118         }
1119     }
1120 
1121     if (!error) {
1122         Buffer_dropFinalNewline(&buffer);
1123         Buffer_flush(&buffer);
1124     }
1125     *errorP = error;
1126     *nSkeletonP = skeletonBuffer.nSkeleton;
1127 
1128     fclose(sklfileP);
1129 }
1130 
1131 
1132 
1133 static void
convertIt(FILE * const ifP,FILE * const ofP,SkeletonObject ** const bodySkeletonPList,unsigned int const bodyNskl,SkeletonObject ** const headSkeletonPList,unsigned int const headNskl,SkeletonObject ** const tailSkeletonPList,unsigned int const tailNskl)1134 convertIt(FILE *            const ifP,
1135           FILE *            const ofP,
1136           SkeletonObject ** const bodySkeletonPList,
1137           unsigned int      const bodyNskl,
1138           SkeletonObject ** const headSkeletonPList,
1139           unsigned int      const headNskl,
1140           SkeletonObject ** const tailSkeletonPList,
1141           unsigned int      const tailNskl) {
1142 
1143     pixel * pixelrow;
1144     pixval maxval;
1145     double dmaxval;
1146     int rows, cols;
1147     int format;
1148     unsigned int row;
1149 
1150     ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
1151 
1152     pixelrow = ppm_allocrow(cols);
1153 
1154     dmaxval = (double)maxval;
1155 
1156     /* Write header */
1157     writeText(ofP, headNskl, headSkeletonPList,
1158               cols, rows , 0, 0, 0.0, 0.0, 0.0);
1159 
1160     /* Write raster */
1161     for (row = 0; row < rows; ++row) {
1162         unsigned int col;
1163 
1164         ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
1165 
1166         for (col = 0; col < cols; ++col) {
1167             pixel const thisPixel = pixelrow[col];
1168 
1169             writeText(ofP, bodyNskl, bodySkeletonPList,
1170                       cols, rows, col, row,
1171                       PPM_GETR(thisPixel)/dmaxval,
1172                       PPM_GETG(thisPixel)/dmaxval,
1173                       PPM_GETB(thisPixel)/dmaxval);
1174         }
1175     }
1176 
1177     /* Write trailer */
1178     writeText(ofP, tailNskl, tailSkeletonPList,
1179               cols, rows, 0, 0, 0.0, 0.0, 0.0);
1180 }
1181 
1182 
1183 
1184 int
main(int argc,const char ** argv)1185 main(int           argc,
1186      const char ** argv) {
1187 
1188     struct CmdlineInfo cmdline;
1189 
1190     unsigned int headNskl, bodyNskl, tailNskl;
1191     SkeletonObject * headSkeletonPList[MAX_SKL_HEAD_OBJ];
1192     SkeletonObject * bodySkeletonPList[MAX_SKL_BODY_OBJ];
1193     SkeletonObject * tailSkeletonPList[MAX_SKL_TAIL_OBJ];
1194     FILE * ifP;
1195     const char * error;
1196 
1197     pm_proginit(&argc, argv);
1198 
1199     parseCommandLine(argc, argv, &cmdline);
1200 
1201     ifP = pm_openr(cmdline.inputFileName);
1202 
1203     readSkeletonFile(cmdline.bodySklFileName, ARRAY_SIZE(bodySkeletonPList),
1204                      &error, &bodyNskl, bodySkeletonPList);
1205     if (error)
1206         pm_error("Invalid body skeleton file '%s'.  %s",
1207                  cmdline.bodySklFileName, error);
1208 
1209     if (cmdline.hd) {
1210         readSkeletonFile(cmdline.hd, ARRAY_SIZE(headSkeletonPList),
1211                          &error, &headNskl, headSkeletonPList);
1212         if (error)
1213             pm_error("Invalid head skeleton file '%s'.  %s",
1214                      cmdline.hd, error);
1215     } else
1216         headNskl = 0;
1217 
1218     if (cmdline.tl) {
1219         readSkeletonFile(cmdline.tl, ARRAY_SIZE(tailSkeletonPList),
1220                          &error, &tailNskl, tailSkeletonPList);
1221         if (error)
1222             pm_error("Invalid tail skeleton file '%s'.  %s",
1223                      cmdline.tl, error);
1224     } else
1225         tailNskl = 0;
1226 
1227     if (cmdline.debug)
1228         dumpAllSkeleton(bodySkeletonPList, bodyNskl,
1229                         headSkeletonPList, headNskl,
1230                         tailSkeletonPList, tailNskl);
1231 
1232     convertIt(ifP, stdout,
1233               bodySkeletonPList, bodyNskl,
1234               headSkeletonPList, headNskl,
1235               tailSkeletonPList, tailNskl);
1236 
1237     pm_close(ifP);
1238 
1239     return 0;
1240 }
1241 
1242 
1243