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