diff options
Diffstat (limited to 'lib')
72 files changed, 5715 insertions, 2250 deletions
diff --git a/lib/Makefile b/lib/Makefile index 0738e5cb..1e607ee5 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -13,7 +13,7 @@ else LIBNETPBM = $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).$(NETPBMLIBSUFFIX) endif -ifeq ($(STATICLIB_TOO),y) +ifeq ($(STATICLIB_TOO),Y) EXTRA_STATICLIB = libnetpbm.$(STATICLIBSUFFIX) else EXTRA_STATICLIB = @@ -25,7 +25,8 @@ else LIBSYSTEM = libsystem.o endif -LIBOBJECTS = libpm.o pmfileio.o fileio.o bitio.o colorname.o \ +LIBOBJECTS = libpm.o pmfileio.o fileio.o colorname.o \ + libpamd.o \ libpbm1.o libpbm2.o libpbm3.o libpbmfont.o \ libpgm1.o libpgm2.o \ libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \ @@ -36,24 +37,28 @@ LIBOBJECTS = libpm.o pmfileio.o fileio.o bitio.o colorname.o \ libpamn.o libpammap.o libpamcolor.o \ $(LIBSYSTEM) \ -ifneq (${VMS}x,x) -LIBOBJECTS += libpbmvms.o -endif # Library objects to be linked but not built by common.mk: LIBOBJECTS_X = \ - util/shhopt.o \ - util/nstring.o \ - util/vasprintf.o \ + util/bitio.o \ util/filename.o \ + util/io.o \ + util/mallocvar.o \ + util/matrix.o \ util/nsleep.o \ + util/nstring.o \ + util/runlength.o \ + util/shhopt.o \ + util/token.o \ + util/vasprintf.o \ MANUALS3 = libnetpbm MANUALS5 = pbm pgm ppm pnm pam -INTERFACE_HEADERS = pm.h pbm.h bitio.h pbmfont.h \ - pgm.h ppm.h ppmcmap.h ppmfloyd.h colorname.h \ - pnm.h pam.h pammap.h util/shhopt.h util/mallocvar.h \ - pm_system.h pm_gamma.h ppmdraw.h ppmdfont.h \ +INTERFACE_HEADERS = colorname.h \ + pam.h pamdraw.h pammap.h pbm.h pbmfont.h \ + pgm.h pm.h pm_gamma.h pm_system.h pnm.h \ + ppm.h ppmcmap.h ppmdfont.h ppmdraw.h ppmfloyd.h \ + util/mallocvar.h util/runlength.h util/shhopt.h \ DATAFILES = rgb.txt @@ -65,10 +70,9 @@ SCRIPTS = BINARIES = OMIT_LIBRARY_RULE = 1 +ALL_INTERNAL_HEADER_FILES_ARE_QUALIFIED = Y include $(SRCDIR)/common.mk -INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc - # The following must go after common.mk because $(LIBNETPBM) may # contain a reference to $(NETPBM_MAJOR_RELEASE). .PHONY: libnetpbm @@ -83,12 +87,14 @@ extra_staticlib: $(EXTRA_STATICLIB) # type, but request a static library in addition. #---------------------------------------------------------------------------- +$(LIBOBJECTS): CFLAGS_TARGET=$(CFLAGS_SHLIB) + +libpbm3.o: CFLAGS_TARGET+=$(CFLAGS_SSE) + $(LIBOBJECTS): %.o: %.c importinc -# Note that the user may have configured -I options into CPPFLAGS/CFLAGS. - $(CC) -c $(INCLUDES) -DNDEBUG $(CPPFLAGS) $(CFLAGS) $(CFLAGS_SHLIB) \ - $(CFLAGS_PERSONAL) $(CADD) -o $@ $< + $(CC) -c $(INCLUDES) $(CFLAGS_ALL) -o $@ $< -MAJ = $(NETPBM_MAJOR_RELEASE) +MAJ = 11 MIN = $(NETPBM_MINOR_RELEASE) SONAME = libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ) @@ -127,7 +133,7 @@ libnetpbm.$(NETPBMLIBSUFFIX).$(MAJ).$(MIN): $(LIBOBJECTS) $(LIBOBJECTS_X) endif ifeq ($(NETPBMLIBTYPE),dll) -ifeq ($(STATICLIB_TOO),y) +ifeq ($(STATICLIB_TOO),Y) $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X) libnetpbm.$(STATICLIBSUFFIX) else $(NETPBMSHLIBPREFIX)netpbm$(DLLVER).dll: $(LIBOBJECTS) $(LIBOBJECTS_X) @@ -167,22 +173,23 @@ endif # STATICLIB_SUFFIX may just be arbitrary. #----------------------------------------------------------------------------- ifeq ($(NETPBMLIBTYPE),unixstatic) - BUILD_STATICLIB = y + BUILD_STATICLIB = Y else - ifeq ($(STATICLIB_TOO),y) - BUILD_STATICLIB = y + ifeq ($(STATICLIB_TOO),Y) + BUILD_STATICLIB = Y else - BUILD_STATICLIB = n + BUILD_STATICLIB = N endif endif -ifeq ($(BUILD_STATICLIB),y) +ifeq ($(BUILD_STATICLIB),Y) libnetpbm.$(STATICLIBSUFFIX): $(LIBOBJECTS) $(LIBOBJECTS_X) -rm -f $@ $(AR) rc $@ $(LIBOBJECTS) $(LIBOBJECTS_X) -$(RANLIB) $@ endif + # To avoid major hassles with having ppmdcfont available here, we just ship a # pre-made standardppmfont.c, so this rule will not normally be used. Though # standardppmdfont.c depends upon standard.ppmdfont, we don't declare that diff --git a/lib/colorname.c b/lib/colorname.c index ce53bdcd..83cf5d1a 100644 --- a/lib/colorname.c +++ b/lib/colorname.c @@ -15,28 +15,32 @@ #define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ -#include "pm_c_util.h" +#include "netpbm/pm_c_util.h" #include <ctype.h> #include <string.h> #include <stdlib.h> #include <errno.h> -#include "nstring.h" +#include "netpbm/nstring.h" + #include "colorname.h" static int lineNo; + + void -pm_canonstr(char * const str) { - - char * p; - for (p = str; *p; ) { - if (ISSPACE(*p)) { - strcpy(p, &(p[1])); - } else { - if (ISUPPER(*p)) - *p = tolower(*p); - ++p; +pm_canonstr(char * const arg) { +/*---------------------------------------------------------------------------- + Modify string 'arg' to canonical form: lower case, no white space. +-----------------------------------------------------------------------------*/ + const char * srcCursor; + char * dstCursor; + + for (srcCursor = arg, dstCursor = arg; *srcCursor; ++srcCursor) { + if (!ISSPACE(*srcCursor)) { + *dstCursor++ = + ISUPPER(*srcCursor) ? tolower(*srcCursor) : *srcCursor; } } } @@ -65,7 +69,7 @@ openColornameFileSearch(const char * const searchPath, *filePP = NULL; /* initial value */ while (!eol && !*filePP) { const char * token; - token = strsepN(&cursor, ":"); + token = pm_strsep(&cursor, ":"); if (token) { *filePP = fopen(token, "r"); } else @@ -84,8 +88,8 @@ pm_openColornameFile(const char * const fileName, const int must_open) { Open the colorname dictionary file. Its file name is 'fileName', unless 'fileName' is NULL. In that case, its file name is the value of the environment variable whose name is RGB_ENV (e.g. "RGBDEF"). Except - if that environment variable is not set, it is RGB_DB1, RGB_DB2, - or RGB_DB3 (e.g. "/usr/lib/X11/rgb.txt"), whichever exists. + if that environment variable is not set, it is the first file found, + if any, in the search path RGB_DB_PATH. 'must_open' is a logical: we must get the file open or die. If 'must_open' is true and we can't open the file (e.g. it doesn't @@ -195,7 +199,7 @@ pm_parse_dictionary_name(char const colorname[], pixval r,g,b; f = pm_openColornameFile(NULL, TRUE); /* exits if error */ - canoncolor = strdup(colorname); + canoncolor = pm_strdup(colorname); if (!canoncolor) pm_error("Failed to allocate memory for %u-byte color name", diff --git a/lib/libpam.c b/lib/libpam.c index ab75fab6..cc6368e1 100644 --- a/lib/libpam.c +++ b/lib/libpam.c @@ -1,9 +1,12 @@ -/*---------------------------------------------------------------------------- +/*============================================================================= libpam.c ------------------------------------------------------------------------------- +=============================================================================== These are the library functions, which belong in the libnetpbm library, that deal with the PAM (Portable Arbitrary Format) image format. ------------------------------------------------------------------------------*/ + + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ /* See pmfileio.c for the complicated explanation of this 32/64 bit file offset stuff. @@ -20,9 +23,10 @@ #include <math.h> -#include "pm_c_util.h" -#include "mallocvar.h" -#include "nstring.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + #include "pam.h" #include "ppm.h" #include "libpbm.h" @@ -70,6 +74,7 @@ pamCommentP(const struct pam * const pamP) { } + static void validateComputableSize(struct pam * const pamP) { /*---------------------------------------------------------------------------- @@ -205,21 +210,6 @@ pnm_createBlackTuple(const struct pam * const pamP, -void -createBlackTuple(const struct pam * const pamP, - tuple * const blackTupleP) { - -/* This is poorly named, because it lacks the "pnm" prefix. But for some - reason, this is how we originally named this. So to maintain backward - compatibility with binaries that refer to "createBlackTuple", we define - this. The preferred name, pnm_createBlackTuple() was new in Netpbm 10.20, - January 2004. We should eventually retire createBlackTuple(). -*/ - pnm_createBlackTuple(pamP, blackTupleP); -} - - - static tuple * allocPamRow(const struct pam * const pamP) { /*---------------------------------------------------------------------------- @@ -227,12 +217,11 @@ allocPamRow(const struct pam * const pamP) { overflow will not occur in our calculations. NOTE: pnm_readpaminit() ensures this assumption is valid. -----------------------------------------------------------------------------*/ - /* The tuple row data structure starts with 'width' pointers to - the tuples, immediately followed by the 'width' tuples - themselves. Each tuple consists of 'depth' samples. + /* The tuple row data structure starts with pointers to the tuples, + immediately followed by the tuples themselves. */ - int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample); + unsigned int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample); tuple * tuplerow; tuplerow = malloc(pamP->width * (sizeof(tuple *) + bytesPerTuple)); @@ -262,9 +251,9 @@ pnm_allocpamrow(const struct pam * const pamP) { tuple * const tuplerow = allocPamRow(pamP); if (tuplerow == NULL) - pm_error("Out of memory allocating space for a tuple row of\n" - "%d tuples by %d samples per tuple by %d bytes per sample.", - pamP->width, allocationDepth(pamP), sizeof(sample)); + pm_error("Out of memory allocating space for a tuple row of " + "%d tuples by %d samples per tuple by %u bytes per sample.", + pamP->width, allocationDepth(pamP), (unsigned)sizeof(sample)); return tuplerow; } @@ -401,7 +390,7 @@ pnm_setminallocationdepth(struct pam * const pamP, pm_error("Can't set minimum allocation depth in pam structure, " "because the structure is only %u bytes long, and to " "have an allocation_depth field, it must bea at least %u", - pamP->len, PAM_STRUCT_SIZE(allocation_depth)); + pamP->len, (unsigned)PAM_STRUCT_SIZE(allocation_depth)); pamP->allocation_depth = MAX(allocationDepth, pamP->depth); @@ -430,8 +419,9 @@ pnm_setpamrow(const struct pam * const pamP, #define MAX_VALUE_LENGTH 255 static void -parse_header_line(const char buffer[], char label[MAX_LABEL_LENGTH+1], - char value[MAX_VALUE_LENGTH+1]) { +parseHeaderLine(const char buffer[], + char label[MAX_LABEL_LENGTH+1], + char value[MAX_VALUE_LENGTH+1]) { int buffer_curs; @@ -483,9 +473,69 @@ struct headerSeen { static void -process_header_line(char const buffer[], - struct pam * const pamP, - struct headerSeen * const headerSeenP) { +parseHeaderUint(const char * const valueString, + unsigned int * const valueNumP, + const char * const name) { +/*---------------------------------------------------------------------------- + Interpret 'valueString' as the number in a header such as + "WIDTH 200". + + 'name' is the header name ("WIDTH" in the example). +-----------------------------------------------------------------------------*/ + + if (strlen(valueString) == 0) + pm_error("Missing value for %s in PAM file header.", name); + else { + char * endptr; + long int valueNum; + errno = 0; /* Clear errno so we can detect strtol() failure */ + valueNum = strtol(valueString, &endptr, 10); + if (errno != 0) + pm_error("Too-large value for %s in " + "PAM file header: '%s'", name, valueString); + else if (*endptr != '\0') + pm_error("Non-numeric value for %s in " + "PAM file header: '%s'", name, valueString); + else if (valueNum < 0) + pm_error("Negative value for %s in " + "PAM file header: '%s'", name, valueString); + else if ((unsigned int)valueNum != valueNum) + pm_error("Ridiculously large value for %s in " + "PAM file header: %lu", name, valueNum); + else + *valueNumP = (unsigned int)valueNum; + } +} + + + +static void +parseHeaderInt(const char * const valueString, + int * const valueNumP, + const char * const name) { +/*---------------------------------------------------------------------------- + This is not what it seems. It is the same thing as + parseHeaderUint, except that the type of the value it returns is + "int" instead of "unsigned int". But that doesn't mean the value can + be negative. We throw an error is it is not positive. +-----------------------------------------------------------------------------*/ + unsigned int valueNum; + + parseHeaderUint(valueString, &valueNum, name); + + if ((int)valueNum != valueNum) + pm_error("Ridiculously large value for %s in " + "PAM file header: %u", name, valueNum); + else + *valueNumP = (int)valueNum; +} + + + +static void +processHeaderLine(char const buffer[], + struct pam * const pamP, + struct headerSeen * const headerSeenP) { /*---------------------------------------------------------------------------- Process a line from the PAM header. The line is buffer[], and it is not a comment or blank. @@ -497,67 +547,44 @@ process_header_line(char const buffer[], char label[MAX_LABEL_LENGTH+1]; char value[MAX_VALUE_LENGTH+1]; - parse_header_line(buffer, label, value); + parseHeaderLine(buffer, label, value); - if (strcmp(label, "ENDHDR") == 0) + if (streq(label, "ENDHDR")) headerSeenP->endhdr = TRUE; - else { - if (strcmp(label, "WIDTH") == 0 || - strcmp(label, "HEIGHT") == 0 || - strcmp(label, "DEPTH") == 0 || - strcmp(label, "MAXVAL") == 0) { - - if (strlen(value) == 0) - pm_error("Missing value for %s in PAM file header.", - label); + else if (streq(label, "WIDTH")) { + parseHeaderInt(value, &pamP->width, label); + headerSeenP->width = TRUE; + } else if (streq(label, "HEIGHT")) { + parseHeaderInt(value, &pamP->height, label); + headerSeenP->height = TRUE; + } else if (streq(label, "DEPTH")) { + parseHeaderUint(value, &pamP->depth, label); + headerSeenP->depth = TRUE; + } else if (streq(label, "MAXVAL")) { + unsigned int maxval; + parseHeaderUint(value, &maxval, label); + if (maxval >= (1<<16)) + pm_error("Maxval too large: %u. Max is 65535", maxval); + pamP->maxval = maxval; + headerSeenP->maxval = TRUE; + } else if (streq(label, "TUPLTYPE")) { + if (strlen(value) == 0) + pm_error("TUPLTYPE header does not have any tuple type text"); + else { + size_t const oldLen = strlen(pamP->tuple_type); + if (oldLen + strlen(value) + 1 > sizeof(pamP->tuple_type)-1) + pm_error("TUPLTYPE value too long in PAM header"); + if (oldLen == 0) + strcpy(pamP->tuple_type, value); else { - char *endptr; - long int numeric_value; - errno = 0; /* Clear errno so we can detect strtol() failure */ - numeric_value = strtol(value, &endptr, 10); - if (errno != 0) - pm_error("Too-large value for %s in " - "PAM file header: '%s'", label, value); - if (*endptr != '\0') - pm_error("Non-numeric value for %s in " - "PAM file header: '%s'", label, value); - else if (numeric_value < 0) - pm_error("Negative value for %s in " - "PAM file header: '%s'", label, value); + strcat(pamP->tuple_type, " "); + strcat(pamP->tuple_type, value); } + pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0'; } - - if (strcmp(label, "WIDTH") == 0) { - pamP->width = atoi(value); - headerSeenP->width = TRUE; - } else if (strcmp(label, "HEIGHT") == 0) { - pamP->height = atoi(value); - headerSeenP->height = TRUE; - } else if (strcmp(label, "DEPTH") == 0) { - pamP->depth = atoi(value); - headerSeenP->depth = TRUE; - } else if (strcmp(label, "MAXVAL") == 0) { - pamP->maxval = atoi(value); - headerSeenP->maxval = TRUE; - } else if (strcmp(label, "TUPLTYPE") == 0) { - if (strlen(value) == 0) - pm_error("TUPLTYPE header does not have any tuple type text"); - else { - size_t const oldLen = strlen(pamP->tuple_type); - if (oldLen + strlen(value) + 1 > sizeof(pamP->tuple_type)-1) - pm_error("TUPLTYPE value too long in PAM header"); - if (oldLen == 0) - strcpy(pamP->tuple_type, value); - else { - strcat(pamP->tuple_type, " "); - strcat(pamP->tuple_type, value); - } - pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0'; - } - } else - pm_error("Unrecognized header line: '%s'. " - "Possible missing ENDHDR line?", label); - } + } else + pm_error("Unrecognized header line type: '%s'. " + "Possible missing ENDHDR line?", label); } @@ -576,7 +603,7 @@ appendComment(char ** const commentsP, if (*commentsP == NULL) pm_error("Couldn't get storage for %u characters of comments from " - "the PAM header", commentLen); + "the PAM header", (unsigned)commentLen); strcat(*commentsP, commentLine); } @@ -592,7 +619,7 @@ disposeOfComments(const struct pam * const pamP, if (retP) *retP = comments; else - strfree(comments); + pm_strfree(comments); } @@ -635,10 +662,10 @@ readpaminitrest(struct pam * const pamP) { buffer[256-1-1] = '\n'; /* In case fgets() truncated */ if (buffer[0] == '#') appendComment(&comments, buffer); - else if (stripeq(buffer, "")); + else if (pm_stripeq(buffer, "")); /* Ignore it; it's a blank line */ else - process_header_line(buffer, pamP, &headerSeen); + processHeaderLine(buffer, pamP, &headerSeen); } } @@ -718,13 +745,127 @@ pnm_readpaminitrestaspnm(FILE * const fileP, unsigned int pnm_bytespersample(sample const maxval) { +/*---------------------------------------------------------------------------- + Return the number of bytes per sample in the PAM raster of a PAM image + with maxval 'maxval'. It's defined to be the minimum number of bytes + needed for that maxval, i.e. 1 for maxval < 256, 2 otherwise. +-----------------------------------------------------------------------------*/ + + /* The PAM format requires maxval to be greater than zero and less than + 1<<16, but since that is a largely arbitrary restriction, we don't want + to rely on it. + */ - assert(sizeof(maxval) * 8 <= 32); + unsigned int i; + sample a; + + for (i = 0, a = maxval; i <= sizeof(maxval); ++i) { + if (a == 0) + return i; + a >>= 8; + } + return 0; /* silence compiler warning */ +} + + + +static void +validateMinDepth(const struct pam * const pamP, + unsigned int const minDepth) { - if (maxval >> 8 == 0) return 1; - else if (maxval >> 16 == 0) return 2; - else if (maxval >> 24 == 0) return 3; - else return 4; + if (pamP->depth < minDepth) + pm_error("Depth %u is insufficient for tuple type '%s'. " + "Minimum depth is %u", + pamP->depth, pamP->tuple_type, minDepth); +} + + + +static void +interpretTupleType(struct pam * const pamP) { +/*---------------------------------------------------------------------------- + Fill in redundant convenience fields in *pamP with information the + pamP->tuple_type value implies: + + visual + colorDepth + haveOpacity + opacityPlane + + Validate the tuple type against the depth and maxval as well. +-----------------------------------------------------------------------------*/ + const char * const tupleType = + pamP->len >= PAM_STRUCT_SIZE(tuple_type) ? pamP->tuple_type : ""; + + bool visual; + unsigned int colorDepth; + bool haveOpacity; + unsigned int opacityPlane; + + assert(pamP->depth > 0); + + switch (PAM_FORMAT_TYPE(pamP->format)) { + case PAM_TYPE: { + if (streq(tupleType, "BLACKANDWHITE")) { + visual = true; + colorDepth = 1; + haveOpacity = false; + if (pamP->maxval != 1) + pm_error("maxval %u is not consistent with tuple type " + "BLACKANDWHITE (should be 1)", + (unsigned)pamP->maxval); + } else if (streq(tupleType, "GRAYSCALE")) { + visual = true; + colorDepth = 1; + haveOpacity = false; + } else if (streq(tupleType, "GRAYSCALE_ALPHA")) { + visual = true; + colorDepth = 1; + haveOpacity = true; + opacityPlane = PAM_GRAY_TRN_PLANE; + validateMinDepth(pamP, 2); + } else if (streq(tupleType, "RGB")) { + visual = true; + colorDepth = 3; + haveOpacity = false; + validateMinDepth(pamP, 3); + } else if (streq(tupleType, "RGB_ALPHA")) { + visual = true; + colorDepth = 3; + haveOpacity = true; + opacityPlane = PAM_TRN_PLANE; + validateMinDepth(pamP, 4); + } else { + visual = false; + } + } break; + case PPM_TYPE: + visual = true; + colorDepth = 3; + haveOpacity = false; + assert(pamP->depth == 3); + break; + case PGM_TYPE: + visual = true; + colorDepth = 1; + haveOpacity = false; + break; + case PBM_TYPE: + visual = true; + colorDepth = 1; + haveOpacity = false; + break; + default: + assert(false); + } + if (pamP->size >= PAM_STRUCT_SIZE(visual)) + pamP->visual = visual; + if (pamP->size >= PAM_STRUCT_SIZE(color_depth)) + pamP->color_depth = colorDepth; + if (pamP->size >= PAM_STRUCT_SIZE(have_opacity)) + pamP->have_opacity = haveOpacity; + if (pamP->size >= PAM_STRUCT_SIZE(opacity_plane)) + pamP->opacity_plane = opacityPlane; } @@ -736,9 +877,9 @@ pnm_readpaminit(FILE * const file, if (size < PAM_STRUCT_SIZE(tuple_type)) pm_error("pam object passed to pnm_readpaminit() is too small. " - "It must be large\n" + "It must be large " "enough to hold at least up to the " - "'tuple_type' member, but according\n" + "'tuple_type' member, but according " "to the 'size' argument, it is only %d bytes long.", size); @@ -794,6 +935,8 @@ pnm_readpaminit(FILE * const file, pamP->plainformat = FALSE; /* See below for complex explanation of why this is FALSE. */ + interpretTupleType(pamP); + validateComputableSize(pamP); } @@ -867,9 +1010,9 @@ pnm_writepaminit(struct pam * const pamP) { if (pamP->size < PAM_STRUCT_SIZE(bytes_per_sample)) pm_error("pam object passed to pnm_writepaminit() is too small. " - "It must be large\n" + "It must be large " "enough to hold at least up through the " - "'bytes_per_sample' member, but according\n" + "'bytes_per_sample' member, but according " "to its 'size' member, it is only %u bytes long.", pamP->size); if (pamP->len < PAM_STRUCT_SIZE(maxval)) @@ -881,27 +1024,37 @@ pnm_writepaminit(struct pam * const pamP) { pm_error("maxval (%lu) passed to pnm_writepaminit() " "is greater than %u", pamP->maxval, PAM_OVERALL_MAXVAL); - if (pamP->len < PAM_STRUCT_SIZE(tuple_type)) + if (pamP->len < PAM_STRUCT_SIZE(tuple_type)) { tupleType = ""; - else + if (pamP->size >= PAM_STRUCT_SIZE(tuple_type)) + pamP->tuple_type[0] = '\0'; + } else tupleType = pamP->tuple_type; - if (pamP->len < PAM_STRUCT_SIZE(bytes_per_sample)) - pamP->len = PAM_STRUCT_SIZE(bytes_per_sample); pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval); + + if (pamP->size >= PAM_STRUCT_SIZE(comment_p) && + pamP->len < PAM_STRUCT_SIZE(comment_p)) + pamP->comment_p = NULL; + + if (pamP->size >= PAM_STRUCT_SIZE(allocation_depth) && + pamP->len < PAM_STRUCT_SIZE(allocation_depth)) + pamP->allocation_depth = 0; + + interpretTupleType(pamP); + + pamP->len = MIN(pamP->size, PAM_STRUCT_SIZE(opacity_plane)); switch (PAM_FORMAT_TYPE(pamP->format)) { case PAM_TYPE: - if (pm_plain_output) - pm_error("There is no plain version of PAM. -plain option " - "is not allowed"); + /* See explanation below of why we ignore 'pm_plain_output' here. */ fprintf(pamP->file, "P7\n"); writeComments(pamP); fprintf(pamP->file, "WIDTH %u\n", (unsigned)pamP->width); fprintf(pamP->file, "HEIGHT %u\n", (unsigned)pamP->height); fprintf(pamP->file, "DEPTH %u\n", pamP->depth); fprintf(pamP->file, "MAXVAL %lu\n", pamP->maxval); - if (!stripeq(tupleType, "")) + if (!pm_stripeq(tupleType, "")) fprintf(pamP->file, "TUPLTYPE %s\n", pamP->tuple_type); fprintf(pamP->file, "ENDHDR\n"); break; @@ -954,6 +1107,23 @@ pnm_writepaminit(struct pam * const pamP) { +/* EFFECT OF -plain WHEN WRITING PAM FORMAT: + + Before Netpbm 10.63 (June 2013), pnm_writepaminit() did a pm_error() here + if 'pm_plain_output' was set (i.e. the user said -plain). But this isn't + really logical, because -plain is a global option for the program and here + we are just writing one image. As a global option, -plain must be defined + to have effect where it makes sense and have no effect where it doesn't. + Note that a program that generates GIF just ignores -plain. Note also that + a program could conceivably generate both a PPM image and a PAM image. + + Note also how we handle the other a user can request plain format: the + 'plainformat' member of the PAM struct. In the case of PAM, we ignore that + member. +*/ + + + void pnm_checkpam(const struct pam * const pamP, enum pm_check_type const checkType, @@ -1046,19 +1216,107 @@ pnm_makearrayrgb(const struct pam * const pamP, +void +pnm_makerowrgba(const struct pam * const pamP, + tuple * const tuplerow) { +/*---------------------------------------------------------------------------- + Make the tuples 'tuplerow' the RGBA equivalent of what they are now, + which is described by *pamP. + + This means afterward, *pamP no longer correctly describes these tuples; + Caller must be sure to update *pamP it or not use it anymore. + + We fail if Caller did not supply enough allocated space in 'tuplerow' for + the extra planes (tuple allocation depth). +-----------------------------------------------------------------------------*/ + if (pamP->len < PAM_STRUCT_SIZE(opacity_plane)) { + pm_message("struct pam length %u is too small for pnm_makerowrgba(). " + "This function requires struct pam fields through " + "'opacity_plane'", pamP->len); + abort(); + } else { + if (!pamP->visual) + pm_error("Non-visual tuples given to pnm_addopacityrow()"); + + if (pamP->color_depth >= 3 && pamP->have_opacity) { + /* It's already in RGBA format. Leave it alone. */ + } else { + unsigned int col; + + if (allocationDepth(pamP) < 4) + pm_error("allocation depth %u passed to pnm_makerowrgba(). " + "Must be at least 4.", allocationDepth(pamP)); + + for (col = 0; col < pamP->width; ++col) { + tuple const thisTuple = tuplerow[col]; + thisTuple[PAM_TRN_PLANE] = + pamP->have_opacity ? thisTuple[pamP->opacity_plane] : + pamP->maxval; + + assert(PAM_RED_PLANE == 0); + thisTuple[PAM_BLU_PLANE] = thisTuple[0]; + thisTuple[PAM_GRN_PLANE] = thisTuple[0]; + } + } + } +} + + + +void +pnm_addopacityrow(const struct pam * const pamP, + tuple * const tuplerow) { +/*---------------------------------------------------------------------------- + Add an opacity plane to the tuples in 'tuplerow', if one isn't already + there. + + This means afterward, *pamP no longer correctly describes these tuples; + Caller must be sure to update *pamP it or not use it anymore. + + We fail if Caller did not supply enough allocated space in 'tuplerow' for + the extra plane (tuple allocation depth). +-----------------------------------------------------------------------------*/ + if (pamP->len < PAM_STRUCT_SIZE(opacity_plane)) { + pm_message("struct pam length %u is too small for pnm_makerowrgba(). " + "This function requires struct pam fields through " + "'opacity_plane'", pamP->len); + abort(); + } else { + if (!pamP->visual) + pm_error("Non-visual tuples given to pnm_addopacityrow()"); + + if (pamP->have_opacity) { + /* It already has opacity. Leave it alone. */ + } else { + unsigned int const opacityPlane = pamP->color_depth; + + unsigned int col; + + if (allocationDepth(pamP) < opacityPlane + 1) + pm_error("allocation depth %u passed to pnm_addopacityrow(). " + "Must be at least %u.", + allocationDepth(pamP), opacityPlane + 1); + + for (col = 0; col < pamP->width; ++col) + tuplerow[col][opacityPlane] = pamP->maxval; + } + } +} + + + void pnm_getopacity(const struct pam * const pamP, - bool * const haveOpacityP, + int * const haveOpacityP, unsigned int * const opacityPlaneP) { - /* Design note; If use of this information proliferates, we should - probably add it to struct pam as convenience values analogous to - bytes_per_sample. + /* Usage note: this is obsolete since we added 'have_opacity', etc. + to struct pam. */ - if (strcmp(pamP->tuple_type, "RGB_ALPHA") == 0) { + if (streq(pamP->tuple_type, "RGB_ALPHA")) { *haveOpacityP = TRUE; *opacityPlaneP = PAM_TRN_PLANE; - } else if (strcmp(pamP->tuple_type, "GRAYSCALE_ALPHA") == 0) { + } else if (streq(pamP->tuple_type, "GRAYSCALE_ALPHA")) { *haveOpacityP = TRUE; *opacityPlaneP = PAM_GRAY_TRN_PLANE; } else @@ -1067,6 +1325,67 @@ pnm_getopacity(const struct pam * const pamP, +tuple +pnm_backgroundtuple(struct pam * const pamP, + tuple ** const tuples) { +/*-------------------------------------------------------------------- + This function was copied from libpnm3.c's pnm_backgroundxel() and + modified to use tuples instead of xels. +----------------------------------------------------------------------*/ + tuple tuplePtr, bgtuple, ul, ur, ll, lr; + + /* Guess a good background value. */ + ul = tuples[0][0]; + ur = tuples[0][pamP->width-1]; + ll = tuples[pamP->height-1][0]; + lr = tuples[pamP->height-1][pamP->width-1]; + bgtuple = NULL; + + /* We first recognize three corners equal. If not, we look for any + two. If not, we just average all four. + */ + if (pnm_tupleequal(pamP, ul, ur) && pnm_tupleequal(pamP, ur, ll)) + tuplePtr = ul; + else if (pnm_tupleequal(pamP, ul, ur) && + pnm_tupleequal(pamP, ur, lr)) + tuplePtr = ul; + else if (pnm_tupleequal(pamP, ul, ll) && + pnm_tupleequal(pamP, ll, lr)) + tuplePtr = ul; + else if (pnm_tupleequal(pamP, ur, ll) && + pnm_tupleequal(pamP, ll, lr)) + tuplePtr = ur; + else if (pnm_tupleequal(pamP, ul, ur)) + tuplePtr = ul; + else if (pnm_tupleequal(pamP, ul, ll)) + tuplePtr = ul; + else if (pnm_tupleequal(pamP, ul, lr)) + tuplePtr = ul; + else if (pnm_tupleequal(pamP, ur, ll)) + tuplePtr = ur; + else if (pnm_tupleequal(pamP, ur, lr)) + tuplePtr = ur; + else if (pnm_tupleequal(pamP, ll, lr)) + tuplePtr = ll; + else { + /* Reimplement libpnm3.c's mean4() but for tuples. */ + unsigned int plane; + bgtuple = pnm_allocpamtuple(pamP); + for (plane = 0; plane < pamP->depth; ++plane) + bgtuple[plane] = (ul[plane] + ur[plane] + ll[plane] + lr[plane]) / 4; + } + if (!bgtuple) { + unsigned int plane; + bgtuple = pnm_allocpamtuple(pamP); + for (plane = 0; plane < pamP->depth; ++plane) + bgtuple[plane] = tuplePtr[plane]; + } + + return bgtuple; +} + + + /*============================================================================= pm_system() Standard Input feeder and Standard Output accepter functions. =============================================================================*/ diff --git a/lib/libpamcolor.c b/lib/libpamcolor.c index b64f8963..f3ca9a86 100644 --- a/lib/libpamcolor.c +++ b/lib/libpamcolor.c @@ -1,9 +1,12 @@ -/*---------------------------------------------------------------------------- +/*============================================================================ libpamcolor.c ------------------------------------------------------------------------------- - These are the library functions, which belong in the libnetpbm library, - that deal with colors in the PAM image format. ------------------------------------------------------------------------------*/ +============================================================================== + These are the library functions, which belong in the libnetpbm library, + that deal with colors in the PAM image format. + + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ /* See pmfileio.c for the complicated explanation of this 32/64 bit file offset stuff. @@ -17,11 +20,13 @@ #include <string.h> #include <limits.h> -#include "pm_c_util.h" +#include "netpbm/pm_c_util.h" + #include "pam.h" #include "ppm.h" + tuple pnm_parsecolor(const char * const colorname, sample const maxval) { diff --git a/lib/libpamd.c b/lib/libpamd.c new file mode 100644 index 00000000..952150b4 --- /dev/null +++ b/lib/libpamd.c @@ -0,0 +1,1524 @@ +/* +** +** This library module contains the pamdraw routines. +** +** Copyright (C) 1989, 1991 by Jef Poskanzer. +** +** Modified from ppm to pam by Willem van Schaik, Feb 2011 +** +** Permission to use, copy, modify, and distribute this software and its +** documentation for any purpose and without fee is hereby granted, provided +** that the above copyright notice appear in all copies and that both that +** copyright notice and this permission notice appear in supporting +** documentation. This software is provided "as is" without express or +** implied warranty. +** +** The character drawing routines are by John Walker +** Copyright (C) 1994 by John Walker, kelvin@fourmilab.ch +*/ + +#include <assert.h> +#include <stdlib.h> + +#include "netpbm/pm_config.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" + +#include "pam.h" +#include "ppmdfont.h" + +#include "pamdraw.h" + + +struct penpos { + int x; + int y; +}; + +struct rectangle { + /* ((0,0),(0,0)) means empty. */ + /* 'lr' is guaranteed not to be left of or above 'ul' */ + struct penpos ul; + struct penpos lr; +}; + +static struct rectangle const emptyRectangle = { + {0, 0}, + {0, 0}, +}; + + + +static pamd_point +makePoint(int const x, + int const y) { + + return pamd_makePoint(x, y); +} + + + +static pamd_point +middlePoint(pamd_point const a, + pamd_point const b) { + + pamd_point retval; + + retval.x = (a.x + b.x) / 2; + retval.y = (a.y + b.y) / 2; + + return retval; +} + + + +static bool +pointsEqual(pamd_point const a, + pamd_point const b) { + + return a.x == b.x && a.y == b.y; +} + + + +static bool +pointIsWithinBounds(pamd_point const p, + unsigned int const cols, + unsigned int const rows) { + + return (p.x >= 0 && p.x < cols && p.y >= 0 && p.y < rows); +} + + + +static pamd_point +vectorSum(pamd_point const a, + pamd_point const b) { + + return makePoint(a.x + b.x, a.y + b.y); +} + + + +static long int const DDA_SCALE = 8192; + +#define PAMD_MAXCOORD 32767 +/* + Several factors govern the limit of x, y coordination values. + + The limit must be representable as (signed) int for coordinates to + be carried in struct penpos (immediately above). + + The following calculation, done with long ints, must not overflow: + cy0 = cy0 + (y1 - cy0) * (cols - 1 - cx0) / (x1 - cx0); + + The following must not overflow when DDA_SCALE is set to 8092: + dy = (y1 - y0) * DDA_SCALE / abs(x1 - x0); + + Overflow conditions for pamd_text are rather complicated, for commands + come from an external PPMD font file. See comments below. +*/ + + + +void +pamd_validateCoord(int const c) { + + if (c < -PAMD_MAXCOORD || c > PAMD_MAXCOORD) + pm_error("Coordinate out of bounds: %d", c); +} + + + +void +pamd_validatePoint(pamd_point const p) { + + if (p.x < -PAMD_MAXCOORD || p.x > PAMD_MAXCOORD) + pm_error("x coordinate of (%d, %d) out of bounds", p.x, p.y); + + if (p.y < -PAMD_MAXCOORD || p.y > PAMD_MAXCOORD) + pm_error("y coordinate of (%d, %d) out of bounds", p.x, p.y); +} + + + +static void +drawPoint(pamd_drawproc drawproc, + const void * const clientdata, + tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p) { +/*---------------------------------------------------------------------------- + Draw a single point, assuming that it is within the bounds of the + image. +-----------------------------------------------------------------------------*/ + int i; + + if (drawproc == PAMD_NULLDRAWPROC) { + assert(p.x >= 0); assert(p.x < cols); + assert(p.y >= 0); assert(p.y < rows); + + for (i = 0; i < depth; i++) + tuples[p.y][p.x][i] = (sample) *((tuple *) clientdata + i); + } else { + drawproc(tuples, cols, rows, depth, maxval, p, clientdata); + } +} + + + +void +pamd_point_drawproc(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const p, + const void * const clientdata) { + + unsigned int i; + + if ((p.x >= 0) && (p.x < cols) && (p.y >= 0) && (p.y < rows)) + for (i = 0; i < depth; ++i) + tuples[p.y][p.x][i] = (sample) *((tuple *) clientdata + i); +} + + + +static void +findRectangleIntersection(struct rectangle const rect1, + struct rectangle const rect2, + struct rectangle * const intersectionP) { +/*---------------------------------------------------------------------------- + Find the intersection between rectangles 'rect1' and 'rect2'. + Return it as *intersectionP. +-----------------------------------------------------------------------------*/ + struct penpos tentativeUl, tentativeLr; + + tentativeUl.x = MAX(rect1.ul.x, rect2.ul.x); + tentativeUl.y = MAX(rect1.ul.y, rect2.ul.y); + tentativeLr.x = MIN(rect1.lr.x, rect2.lr.x); + tentativeLr.y = MIN(rect1.lr.y, rect2.lr.y); + + if (tentativeLr.x <= tentativeUl.x || + tentativeLr.y <= tentativeUl.y) { + /* No intersection */ + *intersectionP = emptyRectangle; + } else { + intersectionP->ul = tentativeUl; + intersectionP->lr = tentativeLr; + } +} + + + +void +pamd_filledrectangle(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + int const left, + int const top, + int const width, + int const height, + pamd_drawproc drawProc, + const void * const clientdata) { + + struct rectangle image, request, intersection; + unsigned int row; + + if (width < 0) + pm_error("negative width %d passed to pamd_filledrectanglep", width); + if (height < 0) + pm_error("negative height %d passed to pamd_filledrectanglep", height); + if (cols < 0) + pm_error("negative image width %d passed to pamd_filledrectanglep", + cols); + if (rows < 0) + pm_error("negative image height %d passed to pamd_filledrectanglep", + rows); + + request.ul.x = left; + request.ul.y = top; + request.lr.x = left + width; + request.lr.y = top + height; + + image.ul.x = 0; + image.ul.y = 0; + image.lr.x = cols; + image.lr.y = rows; + + findRectangleIntersection(image, request, &intersection); + + /* Draw. */ + for (row = intersection.ul.y; row < intersection.lr.y; ++row) { + unsigned int col; + for (col = intersection.ul.x; col < intersection.lr.x; ++col) + drawPoint(drawProc, clientdata, + tuples, cols, rows, depth, maxval, makePoint(col, row)); + } +} + + + +/* Outline drawing stuff. */ + +static int linetype = PAMD_LINETYPE_NORMAL; + + + +int +pamd_setlinetype(int const type) { + + int old; + + old = linetype; + linetype = type; + return old; +} + + +static bool lineclip = TRUE; + + + +int +pamd_setlineclip(int const newSetting) { + + bool previousSetting; + + previousSetting = lineclip; + + lineclip = newSetting; + + return previousSetting; +} + + + +static void +clipEnd0(pamd_point const p0, + pamd_point const p1, + int const cols, + int const rows, + pamd_point * const c0P, + bool * const noLineP) { +/*---------------------------------------------------------------------------- + Given a line that goes from p0 to p1, where any of these coordinates may be + anywhere in space -- not just in the frame, clip the p0 end to bring it + into the frame. Return the clipped-to location as *c0P. + + Iff this is not possible because the entire line described is + outside the frame, return *nolineP == true. + + The frame is 'cols' columns starting at 0, by 'rows' rows starting + at 0. +-----------------------------------------------------------------------------*/ + pamd_point c0; + bool noLine; + + c0 = p0; /* initial value */ + noLine = FALSE; /* initial value */ + + /* Clip End 0 of the line horizontally */ + if (c0.x < 0) { + if (p1.x < 0) + noLine = TRUE; + else { + c0.y = c0.y + (p1.y - c0.y) * (-c0.x) / (p1.x - c0.x); + c0.x = 0; + } + } else if (c0.x >= cols) { + if (p1.x >= cols) + noLine = TRUE; + else { + c0.y = c0.y + (p1.y - c0.y) * (cols - 1 - c0.x) / (p1.x - c0.x); + c0.x = cols - 1; + } + } + + /* Clip End 0 of the line vertically */ + if (c0.y < 0) { + if (p1.y < 0) + noLine = TRUE; + else { + c0.x = c0.x + (p1.x - c0.x) * (-c0.y) / (p1.y - c0.y); + c0.y = 0; + } + } else if (c0.y >= rows) { + if (p1.y >= rows) + noLine = TRUE; + else { + c0.x = c0.x + (p1.x - c0.x) * (rows - 1 - c0.y) / (p1.y - c0.y); + c0.y = rows - 1; + } + } + + /* Clipping vertically may have moved the endpoint out of frame + horizontally. If so, we know the other endpoint is also out of + frame horizontally and the line misses the frame entirely. + */ + if (c0.x < 0 || c0.x >= cols) { + assert(p1.x < 0 || p1.x >= cols); + noLine = TRUE; + } + *c0P = c0; + *noLineP = noLine; +} + + + +static void +clipEnd1(pamd_point const p0, + pamd_point const p1, + int const cols, + int const rows, + pamd_point * const c1P) { +/*---------------------------------------------------------------------------- + Given a line that goes from p0 to p1, where p0 is within the frame, but p1 + can be anywhere in space, clip the p1 end to bring it into the frame. + Return the clipped-to location as *c1P. + + This is guaranteed to be possible, since we already know at least one point + (i.e. p0) is in the frame. + + The frame is 'cols' columns starting at 0, by 'rows' rows starting + at 0. +-----------------------------------------------------------------------------*/ + pamd_point c1; + /* The current clipped location of p1; we clip it multile times + to get the final location. + */ + /* p0 is in the frame: */ + assert(p0.x >= 0 && p0.x < cols); + assert(p0.y >= 0 && p0.y < rows); + + /* Clip End 1 of the line horizontally */ + c1 = p1; /* initial value */ + + if (c1.x < 0) { + /* We know the line isn't vertical, since End 0 is in the frame + and End 1 is left of frame. + */ + c1.y = c1.y + (p0.y - c1.y) * (-c1.x) / (p0.x - c1.x); + c1.x = 0; + } else if (c1.x >= cols) { + /* We know the line isn't vertical, since End 0 is in the frame + and End 1 is right of frame. + */ + c1.y = c1.y + (p0.y - c1.y) * (cols - 1 - c1.x) / (p0.x - c1.x); + c1.x = cols - 1; + } + + /* Clip End 1 of the line vertically */ + if (c1.y < 0) { + /* We know the line isn't horizontal, since End 0 is in the frame + and End 1 is above frame. + */ + c1.x = c1.x + (p0.x - c1.x) * (-c1.y) / (p0.y - c1.y); + c1.y = 0; + } else if (c1.y >= rows) { + /* We know the line isn't horizontal, since End 0 is in the frame + and End 1 is below frame. + */ + c1.x = c1.x + (p0.x - c1.x) * (rows - 1 - c1.y) / (p0.y - c1.y); + c1.y = rows - 1; + } + + *c1P = c1; +} + + + +static void +clipLine(pamd_point const p0, + pamd_point const p1, + int const cols, + int const rows, + pamd_point * const c0P, + pamd_point * const c1P, + bool * const noLineP) { +/*---------------------------------------------------------------------------- + Clip the line that goes from p0 to p1 so that none of it is outside the + boundaries of the raster with width 'cols' and height 'rows' + + The clipped line goes from *c0P to *c1P. + + But if the entire line is outside the boundaries (i.e. we clip the + entire line), return *noLineP true and the other values undefined. +-----------------------------------------------------------------------------*/ + pamd_point c0, c1; + /* The line we successively modify. Starts out as the input + line and ends up as the output line. + */ + bool noLine; + + clipEnd0(p0, p1, cols, rows, &c0, &noLine); + + if (!noLine) { + /* p0 is in the frame: */ + assert(c0.x >= 0 && c0.x < cols); + assert(c0.y >= 0 && c0.y < rows); + + clipEnd1(c0, p1, cols, rows, &c1); + } + + *c0P = c0; + *c1P = c1; + *noLineP = noLine; +} + + + +static void +drawShallowLine(pamd_drawproc drawProc, + const void * const clientdata, + tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p0, + pamd_point const p1) { +/*---------------------------------------------------------------------------- + Draw a line that is more horizontal than vertical. + + Don't clip. + + Assume the line has distinct start and end points (i.e. it's at least + two points). +-----------------------------------------------------------------------------*/ + /* Loop over X domain. */ + long dy, srow; + int dx, col, row, prevrow; + + if (p1.x > p0.x) + dx = 1; + else + dx = -1; + dy = (p1.y - p0.y) * DDA_SCALE / abs(p1.x - p0.x); + prevrow = row = p0.y; + srow = row * DDA_SCALE + DDA_SCALE / 2; + col = p0.x; + for ( ; ; ) { + if (linetype == PAMD_LINETYPE_NODIAGS && row != prevrow) { + drawPoint(drawProc, clientdata, + tuples, cols, rows, depth, maxval, + makePoint(col, prevrow)); + prevrow = row; + } + drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval, + makePoint(col, row)); + if (col == p1.x) + break; + srow += dy; + row = srow / DDA_SCALE; + col += dx; + } +} + + + +static void +drawSteepLine(pamd_drawproc drawProc, + const void * const clientdata, + tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p0, + pamd_point const p1) { +/*---------------------------------------------------------------------------- + Draw a line that is more vertical than horizontal. + + Don't clip. + + Assume the line has distinct start and end points (i.e. it's at least + two points). +-----------------------------------------------------------------------------*/ + /* Loop over Y domain. */ + + long dx, scol; + int dy, col, row, prevcol; + + if (p1.y > p0.y) + dy = 1; + else + dy = -1; + dx = (p1.x - p0.x) * DDA_SCALE / abs(p1.y - p0.y); + row = p0.y; + prevcol = col = p0.x; + scol = col * DDA_SCALE + DDA_SCALE / 2; + for ( ; ; ) { + if (linetype == PAMD_LINETYPE_NODIAGS && col != prevcol) { + drawPoint(drawProc, clientdata, + tuples, cols, rows, depth, maxval, + makePoint(prevcol, row)); + prevcol = col; + } + drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval, + makePoint(col, row)); + if (row == p1.y) + break; + row += dy; + scol += dx; + col = scol / DDA_SCALE; + } +} + + + +void +pamd_line(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p0, + pamd_point const p1, + pamd_drawproc drawProc, + const void * const clientdata) { + + pamd_point c0, c1; + bool noLine; /* There's no line left after clipping */ + + pamd_validateCoord(cols); + pamd_validateCoord(rows); + pamd_validatePoint(p0); + pamd_validatePoint(p1); + + if (lineclip) { + clipLine(p0, p1, cols, rows, &c0, &c1, &noLine); + } else { + c0 = p0; + c1 = p1; + noLine = FALSE; + } + + if (noLine) { + /* Nothing to draw */ + } else if (pointsEqual(c0, c1)) { + /* This line is just a point. Because there aren't two + distinct endpoints, we have a special case. + */ + drawPoint(drawProc, clientdata, tuples, cols, rows, depth, maxval, c0); + } else { + /* Draw, using a simple DDA. */ + if (abs(c1.x - c0.x) > abs(c1.y - c0.y)) + drawShallowLine(drawProc, clientdata, tuples, cols, rows, + depth, maxval, c0, c1); + else + drawSteepLine(drawProc, clientdata, tuples, cols, rows, + depth, maxval, c0, c1); + } +} + + + +static unsigned int +distanceFromLine(pamd_point const p, + pamd_point const l0, + pamd_point const l1) { +/*---------------------------------------------------------------------------- + Compute, sort of, the distance between point 'p' and the line through + 'l0' and 'l1'. + + I don't really know the signficance of this measurement. +-----------------------------------------------------------------------------*/ + + pamd_point const middle = middlePoint(l0, l1); + + return (abs(p.x - middle.x) + abs(p.y - middle.y)); +} + + + +void +pamd_spline3(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p0, + pamd_point const ctl, + pamd_point const p1, + pamd_drawproc drawProc, + const void * const clientdata) { + + static unsigned int const splineThresh = 3; + /* The limit of recursion */ + + if (distanceFromLine(ctl, p0, p1) <= splineThresh) { + /* The control point is pretty close to the straight line that + joins the endpoints, so we'll just draw a straight line. + */ + pamd_line( + tuples, cols, rows, depth, maxval, p0, p1, drawProc, clientdata); + } else { + /* We want some curvature, so pick a point (b) sort of between the + two endpoints and the control point and then draw a spline + between each of the endpoints and (b): + */ + pamd_point const a = middlePoint(p0, ctl); + pamd_point const c = middlePoint(ctl, p1); + pamd_point const b = middlePoint(a, c); + + pamd_spline3( + tuples, cols, rows, depth, maxval, p0, a, b, drawProc, clientdata); + + pamd_spline3( + tuples, cols, rows, depth, maxval, b, c, p1, drawProc, clientdata); + } +} + + + +void +pamd_polyspline(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const p0, + unsigned int const nc, + pamd_point * const c, + pamd_point const p1, + pamd_drawproc drawProc, + const void * const clientdata) { + + pamd_point p; + + unsigned int i; + + assert(nc > 0); + + p = p0; + for (i = 0; i < nc - 1; ++i) { + pamd_point const n = middlePoint(c[i], c[i+1]); + pamd_spline3( + tuples, cols, rows, depth, maxval, p, c[i], n, + drawProc, clientdata); + p = n; + } + pamd_spline3( + tuples, cols, rows, depth, maxval, p, c[nc - 1], p1, + drawProc, clientdata); +} + + + +void +pamd_spline4(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const endPt0, + pamd_point const endPt1, + pamd_point const ctlPt0, + pamd_point const ctlPt1, + pamd_drawproc drawproc, + const void * const clientdata) { +/*---------------------------------------------------------------------------- + Draw a cubic spline from 'endPt0' to 'endPt1', using 'ctlPt0' and + 'ctlPt1' as control points in the classic way: a line through + 'endPt0' and 'ctlPt0' is tangent to the curve at 'entPt0' and the + length of that line controls "enthusiasm," whatever that is. + Same for 'endPt1' and 'ctlPt1'. +-----------------------------------------------------------------------------*/ + + pm_error("pamd_spline4() has not been written yet!"); + +} + + + +void +pamd_circle(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const center, + unsigned int const radius, + pamd_drawproc drawProc, + const void * const clientData) { +/*---------------------------------------------------------------------------- + If lineclip mode is on, draw only points within the image. + If lineclip is off, "draw" all points (by designated drawproc). Note + that the drawproc can't actually draw a point outside the image, but + it might maintain state that is affected by imaginary points outside + the image. + + Initial point is 3 o'clock. +-----------------------------------------------------------------------------*/ + if (radius >= DDA_SCALE) + pm_error("Error drawing circle. Radius %d is too large.", radius); + + pamd_validateCoord(center.x + radius); + pamd_validateCoord(center.y + radius); + pamd_validateCoord(center.x - radius); + pamd_validateCoord(center.y - radius); + + if (radius > 0) { + long const e = DDA_SCALE / radius; + + pamd_point const p0 = makePoint(radius, 0); /* 3 o'clock */ + /* The starting point around the circle, assuming (0, 0) center */ + pamd_point p; + /* Current drawing position in the circle, assuming (0,0) center */ + bool onFirstPoint; + bool prevPointExists; + pamd_point prevPoint; + /* Previous drawing position, assuming (0, 0) center*/ + long sx, sy; /* 'p', scaled by DDA_SCALE */ + + p = p0; + + sx = p.x * DDA_SCALE + DDA_SCALE / 2; + sy = p.y * DDA_SCALE + DDA_SCALE / 2; + + onFirstPoint = TRUE; + prevPointExists = FALSE; + + while (onFirstPoint || !pointsEqual(p, p0)) { + if (prevPointExists && pointsEqual(p, prevPoint)) { + /* We're on the same point we were on last time (we moved less + than a point's worth). Just keep moving. + */ + } else { + pamd_point const imagePoint = vectorSum(center,p); + if (!lineclip || pointIsWithinBounds(imagePoint, cols, rows)) + drawPoint(drawProc, clientData, + tuples, cols, rows, depth, maxval, imagePoint); + + prevPoint = p; + prevPointExists = TRUE; + } + + if (!pointsEqual(p, p0)) + onFirstPoint = FALSE; + + sx += e * sy / DDA_SCALE; + sy -= e * sx / DDA_SCALE; + p = makePoint(sx / DDA_SCALE, sy / DDA_SCALE); + } + } +} + + + +/* Arbitrary fill stuff. */ + +typedef struct { + pamd_point point; + int edge; +} coord; + +typedef struct fillState { + int n; + /* Number of elements in 'coords' */ + int size; + int curedge; + int segstart; + int ydir; + int startydir; + coord * coords; +} fillState; + +typedef struct fillobj { + + /* The only reason we have a struct fillState separate from + struct fillobj is that the drawproc interface is defined to + have drawing not modify the fillobj, i.e. it passed + const fillobj * to the drawing program. + */ + struct fillState * stateP; +} fillobj; + +#define SOME 1000 + +static int oldclip; + + + +struct fillobj * +pamd_fill_create(void) { + + fillobj * fillObjP; + struct fillState * stateP; + + MALLOCVAR(fillObjP); + if (fillObjP == NULL) + pm_error("out of memory allocating a fillhandle"); + + MALLOCVAR(stateP); + if (stateP == NULL) + pm_error("out of memory allocating a fillhandle"); + + stateP->n = 0; + stateP->size = SOME; + MALLOCARRAY(stateP->coords, stateP->size); + if (stateP->coords == NULL) + pm_error("out of memory allocating a fillhandle"); + stateP->curedge = 0; + + fillObjP->stateP = stateP; + + /* Turn off line clipping. */ + /* UGGH! We must eliminate this global variable */ + oldclip = pamd_setlineclip(0); + + return fillObjP; +} + + + +void +pamd_fill_destroy(struct fillobj * const fillObjP) { + + free(fillObjP->stateP->coords); + free(fillObjP->stateP); + free(fillObjP); +} + + + +static void +addCoord(struct fillState * const stateP, + pamd_point const point) { + + stateP->coords[stateP->n].point = point; + stateP->coords[stateP->n].edge = stateP->curedge; + + ++stateP->n; +} + + + +static void +startNewSegment(struct fillState * const stateP) { +/*---------------------------------------------------------------------------- + Close off the segment we're currently building and start a new one. +-----------------------------------------------------------------------------*/ + if (stateP->startydir != 0 && stateP->ydir != 0) { + /* There's stuff in the current segment. */ + if (stateP->startydir == stateP->ydir) { + /* Oops, first edge and last edge of current segment are the same. + Change all points in the first edge to be in the last. + */ + int const firstEdge = stateP->coords[stateP->segstart].edge; + int const lastEdge = stateP->coords[stateP->n - 1].edge; + coord * const segStartCoordP = &stateP->coords[stateP->segstart]; + coord * const segEndCoordP = &stateP->coords[stateP->n]; + + coord * fcP; + + for (fcP = segStartCoordP; + fcP < segEndCoordP && fcP->edge == firstEdge; + ++fcP) + fcP->edge = lastEdge; + } + } + /* And start new segment. */ + ++stateP->curedge; + stateP->segstart = stateP->n; + stateP->ydir = 0; + stateP->startydir = 0; +} + + + +static void +continueSegment(struct fillState * const stateP, + int const dy) { +/*---------------------------------------------------------------------------- + 'dy' is how much the current point is above the previous one. +-----------------------------------------------------------------------------*/ + if (dy != 0) { + if (stateP->ydir != 0 && stateP->ydir != dy) { + /* Direction changed. Insert a fake coord, old + position but new edge number. + */ + ++stateP->curedge; + addCoord(stateP, stateP->coords[stateP->n - 1].point); + } + stateP->ydir = dy; + if (stateP->startydir == 0) + stateP->startydir = dy; + } +} + + + +/* pamd_fill_drawproc() is a drawproc that turns an outline drawing function + into a filled shape function. This is a somewhat off-label application of + a drawproc: A drawproc is intended just to draw a point. So e.g. you + might draw a circle with a fat brush by calling pamd_circle with a drawproc + that draws a point as a 10-tuple disk. + + But pamd_fill_drawproc() just draws a point the trivial way: as one tuple. + However, it tracks every point that is drawn in a form that a subsequent + pamd_fill() call can use to to fill in the shape drawn, assuming it turns + out to be a closed shape. +*/ + +void +pamd_fill_drawproc(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const p, + const void * const clientdata) { + + const fillobj * const fillObjP = clientdata; + struct fillState * const stateP = fillObjP->stateP; + + /* Make room for two more coords, the max we might add. */ + if (stateP->n + 2 > stateP->size) { + stateP->size += SOME; + REALLOCARRAY(stateP->coords, stateP->size); + if (stateP->coords == NULL) + pm_error("out of memory enlarging a fillhandle"); + } + + if (stateP->n == 0) { + /* Start first segment. */ + stateP->segstart = stateP->n; + stateP->ydir = 0; + stateP->startydir = 0; + addCoord(stateP, p); + } else { + pamd_point const prevPoint = stateP->coords[stateP->n - 1].point; + int const dx = p.x - prevPoint.x; + int const dy = p.y - prevPoint.y; + + if (dx == 0 && dy == 0) { + /* These are the same coords we had last time; don't bother */ + } else { + if (abs(dx) > 1 || abs(dy) > 1) + startNewSegment(stateP); + else + continueSegment(stateP, dy); + + addCoord(stateP, p); + } + } +} + + + +#ifndef LITERAL_FN_DEF_MATCH +static qsort_comparison_fn yxCompare; +#endif + +static int +yxCompare(const void * const c1Arg, + const void * const c2Arg) { + + const coord * const c1P = c1Arg; + const coord * const c2P = c2Arg; + + pamd_point const p1 = c1P->point; + pamd_point const p2 = c2P->point; + + int retval; + + if (p1.y > p2.y) + retval = 1; + else if (p1.y < p2.y) + retval = -1; + else if (p1.x > p2.x) + retval = 1; + else if (p1.x < p2.x) + retval = -1; + else + retval = 0; + + return retval; +} + + + +void +pamd_fill(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + struct fillobj * const fillObjP, + pamd_drawproc drawProc, + const void * const clientdata) { + + struct fillState * const fh = fillObjP->stateP; + + int pedge; + int i, edge, lx, rx, py; + coord * cp; + bool eq; + bool leftside; + + /* Close off final segment. */ + if (fh->n > 0 && fh->startydir != 0 && fh->ydir != 0) { + if (fh->startydir == fh->ydir) { + /* Oops, first edge and last edge are the same. */ + coord * fcp; + const coord * const fcpLast = & (fh->coords[fh->n - 1]); + int lastedge, oldedge; + + lastedge = fh->coords[fh->n - 1].edge; + fcp = &(fh->coords[fh->segstart]); + oldedge = fcp->edge; + for ( ; fcp<=fcpLast && fcp->edge == oldedge; ++fcp ) + fcp->edge = lastedge; + } + } + /* Restore clipping now. */ + pamd_setlineclip(oldclip); + + /* Sort the coords by Y, secondarily by X. */ + qsort((char*) fh->coords, fh->n, sizeof(coord), yxCompare); + + /* Find equal coords with different edge numbers, and swap if necessary. */ + edge = -1; + for (i = 0; i < fh->n; ++i) { + cp = &fh->coords[i]; + if (i > 1 && eq && cp->edge != edge && cp->edge == pedge) { + /* Swap .-1 and .-2. */ + coord t; + + t = fh->coords[i-1]; + fh->coords[i-1] = fh->coords[i-2]; + fh->coords[i-2] = t; + } + if (i > 0) { + if (cp->point.x == lx && cp->point.y == py) { + eq = TRUE; + if (cp->edge != edge && cp->edge == pedge) { + /* Swap . and .-1. */ + coord t; + + t = *cp; + *cp = fh->coords[i-1]; + fh->coords[i-1] = t; + } + } else + eq = FALSE; + } + lx = cp->point.x; + py = cp->point.y; + pedge = edge; + edge = cp->edge; + } + + /* Ok, now run through the coords filling spans. */ + for (i = 0; i < fh->n; ++i) { + cp = &fh->coords[i]; + if (i == 0) { + lx = rx = cp->point.x; + py = cp->point.y; + edge = cp->edge; + leftside = TRUE; + } else { + if (cp->point.y != py) { + /* Row changed. Emit old span and start a new one. */ + pamd_filledrectangle( + tuples, cols, rows, depth, maxval, lx, py, rx - lx + 1, 1, + drawProc, clientdata); + lx = rx = cp->point.x; + py = cp->point.y; + edge = cp->edge; + leftside = TRUE; + } else { + if (cp->edge == edge) { + /* Continuation of side. */ + rx = cp->point.x; + } else { + /* Edge changed. Is it a span? */ + if (leftside) { + rx = cp->point.x; + leftside = FALSE; + } else { + /* Got a span to fill. */ + pamd_filledrectangle( + tuples, cols, rows, depth, maxval, + lx, py, rx - lx + 1, 1, drawProc, clientdata); + lx = rx = cp->point.x; + leftside = TRUE; + } + edge = cp->edge; + } + } + } + } +} + + + +/* Table used to look up sine of angles from 0 through 90 degrees. + The value returned is the sine * 65536. Symmetry is used to + obtain sine and cosine for arbitrary angles using this table. */ + +static long sintab[] = { + 0, 1143, 2287, 3429, 4571, 5711, 6850, 7986, 9120, 10252, 11380, + 12504, 13625, 14742, 15854, 16961, 18064, 19160, 20251, 21336, + 22414, 23486, 24550, 25606, 26655, 27696, 28729, 29752, 30767, + 31772, 32768, 33753, 34728, 35693, 36647, 37589, 38521, 39440, + 40347, 41243, 42125, 42995, 43852, 44695, 45525, 46340, 47142, + 47929, 48702, 49460, 50203, 50931, 51643, 52339, 53019, 53683, + 54331, 54963, 55577, 56175, 56755, 57319, 57864, 58393, 58903, + 59395, 59870, 60326, 60763, 61183, 61583, 61965, 62328, 62672, + 62997, 63302, 63589, 63856, 64103, 64331, 64540, 64729, 64898, + 65047, 65176, 65286, 65376, 65446, 65496, 65526, 65536 +}; + +static int extleft, exttop, extright, extbottom; /* To accumulate extents */ + + + +/* LINTLIBRARY */ + +static long +isin(int const argDeg) { +/*---------------------------------------------------------------------------- + Return sine of an angle in integral degrees. The value returned is 65536 + times the sine. +-----------------------------------------------------------------------------*/ + + int deg360; + /* The argument reduced to the range 0-360 degrees */ + + if (argDeg < 0) { + deg360 = (360 - ((- argDeg) % 360)) % 360; + } else if (argDeg >= 360) { + deg360 = argDeg % 360; + } else + deg360 = argDeg; + + /* Now look up from table according to quadrant. */ + + if (deg360 <= 90) { + return sintab[deg360]; + } else if (deg360 <= 180) { + return sintab[180 - deg360]; + } else if (deg360 <= 270) { + return -sintab[deg360 - 180]; + } + return -sintab[360 - deg360]; +} + + + +static long +icos(int const deg) { +/*---------------------------------------------------------------------------- + Return cosine of an angle in integral degrees. The value returned is 65536 + times the cosine +-----------------------------------------------------------------------------*/ + return isin(deg + 90); +} + + + +static int +twosCompByteValue(unsigned char const c) { +/*---------------------------------------------------------------------------- + E.g. if 'c' is 0x5, return 5. If 'c' is 0xF0, return -16. +-----------------------------------------------------------------------------*/ + return (char)c; +} + + + +static int +glyphSkipBefore(const struct ppmd_glyph * const glyphP) { + + return twosCompByteValue(glyphP->header.skipBefore); +} + + + +static int +glyphWidth(const struct ppmd_glyph * const glyphP) { + + return twosCompByteValue(glyphP->header.skipAfter) - + twosCompByteValue(glyphP->header.skipBefore); +} + + +static pamd_point +commandPoint(const struct ppmd_glyphCommand * const commandP) { +/*---------------------------------------------------------------------------- + Return the point which is the argument of glyph drawing command + *commandP. The origin of the coordinate system for this point + is the center of the glyph cell and the scale is the scale of the + font, so (-10, -10) means the upper left corner of the glyph cell. +-----------------------------------------------------------------------------*/ + return makePoint(twosCompByteValue(commandP->x), + twosCompByteValue(commandP->y)); +} + +#define Scalef 21 /* Font design size */ +#define Descend 9 /* Descender offset */ + + +static pamd_point +textPosFromFontPos(pamd_point const fontPos, + pamd_point const textBoxOrigin, + pamd_point const center, + pamd_point const glyphOrigin, + unsigned int const height, + long const rotcos, + long const rotsin) { +/*---------------------------------------------------------------------------- + 'fontPos' is a position within a glyph as told by the font definition. + It is relative to the center of the glyph, in units of font tuples + (1/21 of a glyph cell). + + We return the position on the canvas of that point. + + That takes into account where in the text box we are, where the text box + is on the canvas, the size of the characters, and the rotation of the + text box. +-----------------------------------------------------------------------------*/ + pamd_point const ptl = vectorSum(center, fontPos); + /* Position relative to the top left of the standard glyph cell */ + + pamd_point const pl = vectorSum(glyphOrigin, ptl); + /* Position relative to the top left of the whole text box, + assuming the text box is horizontal and has font scale. + */ + + pamd_point const ps = makePoint((pl.x * (int)height) / Scalef, + (pl.y * (int)height) / Scalef); + /* Same as above, but with the text box its actual size */ + + pamd_point const retval = + makePoint(textBoxOrigin.x + + (ps.x * rotcos - (ps.y-(int)height) * rotsin) / 65536, + textBoxOrigin.y + + (ps.x * rotsin + (ps.y-(int)height) * rotcos) / 65536); + + pamd_validatePoint(retval); + + return retval; +} + + + +static void +drawGlyph(const struct ppmd_glyph * const glyphP, + pamd_point const glyphOrigin, + tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + int const height, + pamd_point const textBoxOrigin, + long const rotcos, + long const rotsin, + pamd_drawproc drawProc, + const void * const clientdata, + unsigned int * const cursorAdvanceP + ) { +/*---------------------------------------------------------------------------- + 'glyphOrigin' is the position relative to the upper left corner of the text + box of the upper left corner of this glyph cell. It is in units of font + tuples (so you have to scale it by the font size to actual distance on + the canvas). + + We return as *cursorAdvanceP the amount to the right of this glyph cell + the next glyph cell on the line (if any) should be. + + The actual glyph cell may be a little to the left of the nominal position + because of kerning. The font says how much to shift the cell left. + + 'textBoxOrigin' is the left end of the baseline of the top line in the + text box, in the coordinate system of the canvas. 'rotcos' and 'rotsin' + tell how that text box is rotated with respect to the horizontal on the + canvas. + + 'height' is the height in canvas tuples of a glyph. This is a scale factor + to convert font coordinates to canvas coordinates. +-----------------------------------------------------------------------------*/ + pamd_point const center = makePoint(-glyphSkipBefore(glyphP), Scalef/2); + /* This is what you have to add to the coordinates in a glyph + command, which are relative to the center of the glyph, to get + coordinates relative to the upper left corner of the glyph + */ + pamd_point p; + /* Current drawing position within the glyph. Origin is the top + left of the glyph cell. Units are font tuples. + */ + unsigned int commandNum; + + p = textPosFromFontPos(makePoint(0, 0), + textBoxOrigin, + center, + glyphOrigin, + height, + rotcos, rotsin); /* initial value */ + + for (commandNum = 0; + commandNum < glyphP->header.commandCount; + ++commandNum) { + + const struct ppmd_glyphCommand * const commandP = + &glyphP->commandList[commandNum]; + + switch (commandP->verb) { + case CMD_NOOP: + break; + case CMD_DRAWLINE: + { + pamd_point const n = textPosFromFontPos(commandPoint(commandP), + textBoxOrigin, + center, + glyphOrigin, + height, + rotcos, rotsin); + + pamd_line(tuples, cols, rows, depth, maxval, p, n, + drawProc, clientdata); + + p = n; + } + break; + case CMD_MOVEPEN: + p = textPosFromFontPos(commandPoint(commandP), + textBoxOrigin, + center, + glyphOrigin, + height, + rotcos, rotsin); + break; + } + } + *cursorAdvanceP = glyphWidth(glyphP); +} + + + +void +pamd_text(tuple** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const pos, + int const height, + int const angle, + const char * const sArg, + pamd_drawproc drawProc, + const void * const clientdata) { +/*---------------------------------------------------------------------------- + Draw the zero-terminated string 'sArg', with its baseline starting at point + 'pos', inclined by 'angle' degrees to the X axis, with letters 'height' + tuples high (descenders will extend below the baseline). We pass the + supplied drawproc and clientdata to pamd_linep, which performs the actual + drawing. + + There may be multiple lines of text. The baseline of the topmost line + starts at 'pos'. +-----------------------------------------------------------------------------*/ + const struct ppmd_font * const fontP = ppmd_get_font(); + + long rotsin, rotcos; + pamd_point p; + const char * s; + + pamd_validatePoint(pos); + + p = makePoint(0, 0); + rotsin = isin(-angle); + rotcos = icos(-angle); + + for (s = &sArg[0]; *s; ) { + unsigned char const ch = *s++; + + if (ch >= fontP->header.firstCodePoint && + ch < fontP->header.firstCodePoint + fontP->header.characterCount) { + + const struct ppmd_glyph * const glyphP = + &fontP->glyphTable[ch - fontP->header.firstCodePoint]; + + unsigned int cursorAdvance; + + pamd_validatePoint(p); + + drawGlyph(glyphP, p, tuples, cols, rows, depth, maxval, + height, pos, rotcos, rotsin, + drawProc, clientdata, &cursorAdvance); + p.x += cursorAdvance; + } else if (ch == '\n') { + /* Move to the left edge of the next line down */ + p.y += Scalef + Descend; + p.x = 0; + } + } +} + + + +#ifndef LITERAL_FN_DEF_MATCH +static pamd_drawproc extetnsDrawproc; +#endif + +static void +extentsDrawproc(tuple** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const p, + const void * const clientdata) { +/*---------------------------------------------------------------------------- + Drawproc which just accumulates the extents rectangle bounding the + text. +-----------------------------------------------------------------------------*/ + extleft = MIN(extleft, p.x); + exttop = MIN(exttop, p.y); + extright = MAX(extright, p.x); + extbottom = MAX(extbottom, p.y); +} + + + +void +pamd_text_box(int const height, + int const angle, + const char * const s, + int * const leftP, + int * const topP, + int * const rightP, + int * const bottomP) { +/*---------------------------------------------------------------------------- + Calculate extents rectangle for a given piece of text. For most + applications where extents are needed, angle should be zero to obtain the + unrotated extents. If you need the extents box for post-rotation text, + however, you can set angle nonzero and it will be calculated correctly. +-----------------------------------------------------------------------------*/ + extleft = 32767; + exttop = 32767; + extright = -32767; + extbottom = -32767; + + pamd_text(NULL, 32767, 32767, 255, 255, makePoint(1000, 1000), + height, angle, s, + extentsDrawproc, NULL); + + *leftP = extleft - 1000; + *topP = exttop - 1000; + *rightP = extright - 1000; + *bottomP = extbottom - 1000; +} + + + diff --git a/lib/libpammap.c b/lib/libpammap.c index 6fea0eb9..22224913 100644 --- a/lib/libpammap.c +++ b/lib/libpammap.c @@ -1,6 +1,6 @@ -/****************************************************************************** +/*============================================================================= libpammap.c -******************************************************************************* +=============================================================================== These are functions that deal with tuple hashes and tuple tables. @@ -11,13 +11,16 @@ A tuple table lets you scan all the values, being a table of elements that consist of an ordered pair of a tuple value and integer. -******************************************************************************/ + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ #include <assert.h> -#include "pm_c_util.h" -#include "mallocvar.h" -#include "nstring.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + #include "pam.h" #include "pammap.h" @@ -31,13 +34,14 @@ pnm_hashtuple(struct pam * const pamP, Return the hash value of the tuple 'tuple' -- i.e. an index into a hash table. -----------------------------------------------------------------------------*/ + unsigned int const hash_factor[] = {1, 33, 33*33}; + unsigned int i; unsigned int hash; - const unsigned int hash_factor[] = {33023, 30013, 27011}; hash = 0; /* initial value */ for (i = 0; i < MIN(pamP->depth, 3); ++i) { - hash += tuple[i] * hash_factor[i]; /* May overflow */ + hash += tuple[i] * hash_factor[i]; } hash %= HASH_SIZE; return hash; @@ -71,7 +75,7 @@ pnm_createtuplehash(void) { void pnm_destroytuplehash(tuplehash const tuplehash) { - int i; + unsigned int i; /* Free the chains */ @@ -152,7 +156,13 @@ pnm_lookuptuple(struct pam * const pamP, const tuple searchval, int * const foundP, int * const retvalP) { - +/*---------------------------------------------------------------------------- + Return as *revtvalP the index of the tuple value 'searchval' in the + tuple hash 'tuplehash'. + + But iff the tuple value isn't in the hash, return *foundP == false + and nothing as *retvalP. +-----------------------------------------------------------------------------*/ unsigned int const hashvalue = pnm_hashtuple(pamP, searchval); struct tupleint_list_item * p; struct tupleint_list_item * found; @@ -189,7 +199,7 @@ addColorOccurrenceToHash(tuple const color, p = p->next); if (p) { - /* It's in the hash; just tally one more occurence */ + /* It's in the hash; just tally one more occurrence */ ++p->tupleint.value; *fullP = FALSE; } else { @@ -217,7 +227,16 @@ pnm_addtuplefreqoccurrence(struct pam * const pamP, tuple const value, tuplehash const tuplefreqhash, int * const firstOccurrenceP) { +/*---------------------------------------------------------------------------- + Tally one more occurence of the tuple value 'value' to the tuple frequencey + hash 'tuplefreqhash', adding the tuple to the hash if it isn't there + already. + + Allocate new space for the tuple value and the hash chain element. + If we can't allocate space for the new hash chain element, abort the + program. +-----------------------------------------------------------------------------*/ unsigned int const hashvalue = pnm_hashtuple(pamP, value); struct tupleint_list_item * p; @@ -227,7 +246,7 @@ pnm_addtuplefreqoccurrence(struct pam * const pamP, p = p->next); if (p) { - /* It's in the hash; just tally one more occurence */ + /* It's in the hash; just tally one more occurrence */ ++p->tupleint.value; *firstOccurrenceP = FALSE; } else { @@ -407,7 +426,7 @@ alloctupletable(const struct pam * const pamP, const char ** const errorP) { if (UINT_MAX / sizeof(struct tupleint) < size) - asprintfN(errorP, "size %u is too big for arithmetic", size); + pm_asprintf(errorP, "size %u is too big for arithmetic", size); else { unsigned int const mainTableSize = size * sizeof(struct tupleint *); unsigned int const tupleIntSize = @@ -419,7 +438,7 @@ alloctupletable(const struct pam * const pamP, as a single malloc block and suballocate internally. */ if ((UINT_MAX - mainTableSize) / tupleIntSize < size) - asprintfN(errorP, "size %u is too big for arithmetic", size); + pm_asprintf(errorP, "size %u is too big for arithmetic", size); else { unsigned int const allocSize = mainTableSize + size * tupleIntSize; void * pool; @@ -427,8 +446,9 @@ alloctupletable(const struct pam * const pamP, pool = malloc(allocSize); if (!pool) - asprintfN(errorP, "Unable to allocate %u bytes for a %u-entry " - "tuple table", allocSize, size); + pm_asprintf(errorP, + "Unable to allocate %u bytes for a %u-entry " + "tuple table", allocSize, size); else { tupletable const tbl = (tupletable) pool; @@ -459,7 +479,7 @@ pnm_alloctupletable(const struct pam * const pamP, if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } return retval; @@ -514,7 +534,7 @@ tuplehashtotable(const struct pam * const pamP, if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } else { unsigned int i, j; @@ -570,7 +590,7 @@ pnm_computetupletablehash(struct pam * const pamP, -----------------------------------------------------------------------------*/ tuplehash tupletablehash; unsigned int i; - bool fits; + int fits; tupletablehash = pnm_createtuplehash(); @@ -730,3 +750,5 @@ pam_colorname(struct pam * const pamP, sprintf(colorname, "#%02x%02x%02x", r, g, b); return colorname; } + + diff --git a/lib/libpamn.c b/lib/libpamn.c index fe004e91..26dbbfe3 100644 --- a/lib/libpamn.c +++ b/lib/libpamn.c @@ -1,16 +1,20 @@ -/*---------------------------------------------------------------------------- +/*============================================================================= libpamn.c ------------------------------------------------------------------------------- +=============================================================================== These are the library functions, which belong in the libnetpbm library, that deal with the PAM image format via maxval-normalized, floating point sample values. ------------------------------------------------------------------------------*/ + + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ #include <assert.h> -#include "pm_c_util.h" -#include "mallocvar.h" -#include "nstring.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + #include "pam.h" #include "fileio.h" #include "pm_gamma.h" @@ -40,9 +44,10 @@ allocpamrown(const struct pam * const pamP, tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple)); if (tuplerown == NULL) - asprintfN(&error, "Out of memory allocating space for a tuple row of" - "%u tuples by %u samples per tuple by %u bytes per sample.", - pamP->width, pamP->depth, sizeof(samplen)); + pm_asprintf(&error, "Out of memory allocating space for a tuple row of" + "%u tuples by %u samples per tuple " + "by %u bytes per sample.", + pamP->width, pamP->depth, (unsigned)sizeof(samplen)); else { /* Now we initialize the pointers to the individual tuples to make this a regulation C two dimensional array. @@ -78,7 +83,7 @@ pnm_allocpamrown(const struct pam * const pamP) { if (error) { pm_errormsg("pnm_allocpamrown() failed. %s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } @@ -279,9 +284,9 @@ pnm_allocpamarrayn(const struct pam * const pamP) { MALLOCARRAY(tuplenarray, pamP->height); if (tuplenarray == NULL) - asprintfN(&error, - "Out of memory allocating the row pointer section of " - "a %u row array", pamP->height); + pm_asprintf(&error, + "Out of memory allocating the row pointer section of " + "a %u row array", pamP->height); else { unsigned int rowsDone; @@ -302,7 +307,7 @@ pnm_allocpamarrayn(const struct pam * const pamP) { } if (error) { pm_errormsg("pnm_allocpamarrayn() failed. %s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } @@ -494,7 +499,7 @@ gammaCommon(struct pam * const pamP, unsigned int plane; unsigned int opacityPlane; - bool haveOpacity; + int haveOpacity; pnm_getopacity(pamP, &haveOpacity, &opacityPlane); @@ -537,9 +542,14 @@ static void applyopacityCommon(enum applyUnapply const applyUnapply, struct pam * const pamP, tuplen * const tuplenrow) { - +/*---------------------------------------------------------------------------- + Either apply or unapply opacity to the row tuplenrow[], per + 'applyUnapply'. Apply means to multiply each foreground sample by + the opacity value for that pixel; Unapply means to do the inverse, as + if the foreground values had already been so multiplied. +-----------------------------------------------------------------------------*/ unsigned int opacityPlane; - bool haveOpacity; + int haveOpacity; pnm_getopacity(pamP, &haveOpacity, &opacityPlane); @@ -636,7 +646,7 @@ createUngammaMapOffset(const struct pam * const pamP, MALLOCARRAY(ungammaTransformMap, pamP->maxval+1); if (ungammaTransformMap != NULL) { - bool haveOpacity; + int haveOpacity; unsigned int opacityPlane; unsigned int plane; diff --git a/lib/libpamread.c b/lib/libpamread.c index 0506d020..74b1ab83 100644 --- a/lib/libpamread.c +++ b/lib/libpamread.c @@ -1,10 +1,13 @@ -/*---------------------------------------------------------------------------- +/*============================================================================= libpamread.c ------------------------------------------------------------------------------- +=============================================================================== These are the library functions, which belong in the libnetpbm library, that deal with reading the PAM (Portable Arbitrary Format) image format raster (not the header). ------------------------------------------------------------------------------*/ + + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ /* See pmfileio.c for the complicated explanation of this 32/64 bit file offset stuff. @@ -16,8 +19,10 @@ #include <limits.h> #include <assert.h> +#include "netpbm/pm_config.h" +#include "netpbm/nstring.h" + #include "fileio.h" -#include "nstring.h" #include "pam.h" @@ -198,6 +203,51 @@ parse4BpsRow(const struct pam * const pamP, static void +validatePamRow(const struct pam * const pamP, + tuple * const tuplerow, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Check for sample values above maxval in input. + + Note: a program that wants to deal with invalid sample values itself can + simply make sure it sets pamP->maxval sufficiently high, so this validation + never fails. +-----------------------------------------------------------------------------*/ + /* To save time, skip the test for if the maxval is a saturated value + (255, 65535) or format is PBM. + + This is an expensive test, but is skipped in most cases: in practice + maxvals other than 255 or 65535 are uncommon. Thus we do this in a + separate pass through the row rather than while reading in the row. + */ + + if (pamP->maxval == (((sample) 0x1) << pamP->bytes_per_sample*8) - 1 || + PAM_FORMAT_TYPE(pamP->format) == PBM_FORMAT) { + /* There's no way a sample can be invalid, so we don't need to + look at the samples individually. + */ + *errorP = NULL; + } else { + unsigned int col; + for (col = 0; col < pamP->width; ++col) { + unsigned int plane; + for (plane = 0; plane < pamP->depth; ++plane) { + if (tuplerow[col][plane] > pamP->maxval) { + pm_asprintf(errorP, + "Plane %u sample value %lu exceeds the " + "image maxval of %lu", + plane, tuplerow[col][plane], pamP->maxval); + return; + } + } + } + *errorP = NULL; + } +} + + + +static void readRawNonPbmRow(const struct pam * const pamP, tuple * const tuplerow) { @@ -214,13 +264,12 @@ readRawNonPbmRow(const struct pam * const pamP, if (bytesRead != rowImageSize) { if (feof(pamP->file)) - asprintfN(&error, - "End of file encountered when trying to read a row from " - "input file."); + pm_asprintf(&error, "End of file encountered " + "when trying to read a row from input file."); else - asprintfN(&error, "Error reading a row from input file. " - "fread() fails with errno=%d (%s)", - errno, strerror(errno)); + pm_asprintf(&error, "Error reading a row from input file. " + "fread() fails with errno=%d (%s)", + errno, strerror(errno)); } else { error = NULL; /* initial assumption */ if (tuplerow) { @@ -230,16 +279,18 @@ readRawNonPbmRow(const struct pam * const pamP, case 3: parse3BpsRow(pamP, tuplerow, inbuf); break; case 4: parse4BpsRow(pamP, tuplerow, inbuf); break; default: - asprintfN(&error, "invalid bytes per sample passed to " - "pnm_formatpamrow(): %u", pamP->bytes_per_sample); + pm_asprintf(&error, "invalid bytes per sample passed to " + "pnm_formatpamrow(): %u", pamP->bytes_per_sample); } + if (error == NULL) + validatePamRow(pamP, tuplerow, &error); } } pnm_freerowimage(inbuf); if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } } @@ -261,7 +312,7 @@ pnm_readpamrow(const struct pam * const pamP, /* For speed, we don't check any of the inputs for consistency here (unless it's necessary to avoid crashing). Any consistency checking should have been done by a prior call to - pnm_writepaminit(). + pnm_readpaminit(). */ /* Need a special case for raw PBM because it has multiple tuples (8) diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c index dd319d4a..29ddeaa2 100644 --- a/lib/libpamwrite.c +++ b/lib/libpamwrite.c @@ -1,10 +1,13 @@ -/*---------------------------------------------------------------------------- +/*============================================================================ libpamwrite.c ------------------------------------------------------------------------------- +============================================================================== These are the library functions, which belong in the libnetpbm library, that deal with writing the PAM (Portable Arbitrary Format) image format raster (not the header). ------------------------------------------------------------------------------*/ + + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ /* See pmfileio.c for the complicated explanation of this 32/64 bit file offset stuff. @@ -16,9 +19,11 @@ #include <stdio.h> #include <limits.h> #include <assert.h> - #include <math.h> +#include "netpbm/pm_config.h" +#include "netpbm/pm_c_util.h" + #include "pam.h" @@ -351,7 +356,9 @@ pnm_writepamrow(const struct pam * const pamP, pnm_writepaminit(). */ - if (pm_plain_output || pamP->plainformat) { + if (pamP->format == PAM_FORMAT || !(pm_plain_output || pamP->plainformat)) + writePamRawRow(pamP, tuplerow, 1); + else { switch (PAM_FORMAT_TYPE(pamP->format)) { case PBM_TYPE: writePamPlainPbmRow(pamP, tuplerow); @@ -361,18 +368,13 @@ pnm_writepamrow(const struct pam * const pamP, writePamPlainRow(pamP, tuplerow); break; case PAM_TYPE: - /* pm_plain_output is impossible here due to assumption stated - above about pnm_writepaminit() having checked it. The - pamP->plainformat is meaningless for PAM. - */ - writePamRawRow(pamP, tuplerow, 1); + assert(false); break; default: pm_error("Invalid 'format' value %u in pam structure", pamP->format); } - } else - writePamRawRow(pamP, tuplerow, 1); + } } diff --git a/lib/libpbm1.c b/lib/libpbm1.c index fc20071c..49ab7fdf 100644 --- a/lib/libpbm1.c +++ b/lib/libpbm1.c @@ -18,9 +18,10 @@ #include <stdio.h> -#include "pm_c_util.h" -#include "mallocvar.h" -#include "shhopt.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/shhopt.h" + #include "pbm.h" @@ -57,27 +58,28 @@ pbm_nextimage(FILE *file, int * const eofP) { void -pbm_check(FILE * file, const enum pm_check_type check_type, - const int format, const int cols, const int rows, - enum pm_check_code * const retval_p) { +pbm_check(FILE * const fileP, + enum pm_check_type const checkType, + int const format, + int const cols, + int const rows, + enum pm_check_code * const retvalP) { if (rows < 0) pm_error("Invalid number of rows passed to pbm_check(): %d", rows); if (cols < 0) pm_error("Invalid number of columns passed to pbm_check(): %d", cols); - if (check_type != PM_CHECK_BASIC) { - if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE; + if (checkType != PM_CHECK_BASIC) { + if (retvalP) + *retvalP = PM_CHECK_UNKNOWN_TYPE; } else if (format != RPBM_FORMAT) { - if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE; + if (retvalP) + *retvalP = PM_CHECK_UNCHECKABLE; } else { - pm_filepos const bytes_per_row = (cols+7)/8; - pm_filepos const need_raster_size = rows * bytes_per_row; -#ifdef LARGEFILEDEBUG - pm_message("pm_filepos passed to pm_check() is %u bytes", - sizeof(pm_filepos)); -#endif - pm_check(file, check_type, need_raster_size, retval_p); + pm_filepos const bytesPerRow = (cols+7)/8; + pm_filepos const needRasterSize = rows * bytesPerRow; + pm_check(fileP, checkType, needRasterSize, retvalP); } } @@ -113,23 +115,36 @@ static unsigned int const p[256] = { static int bitpop(const unsigned char * const packedRow, - unsigned int const cols) { + unsigned int const cols, + unsigned int const offset) { /*---------------------------------------------------------------------------- - Return the number of 1 bits in 'packedRow'. + Return the number of 1 bits in 'packedRow', ignoring 0 to 7 bits + at the row start (= on the left edge), indicated by offset. -----------------------------------------------------------------------------*/ - unsigned int const colByteCnt = pbm_packed_bytes(cols); - unsigned int const fullByteCnt = cols/8; + unsigned int const fullLength = cols + offset; - unsigned int i; unsigned int sum; - sum = 0; /* initial value */ + if (fullLength <= 8) { + /* All bits are in a single byte */ + sum = bitpop8((packedRow[0] << offset ) & (0xff << (8 - cols))); + } else { + unsigned int const colByteCnt = pbm_packed_bytes(fullLength); + unsigned int const fullByteCnt = fullLength/8; + + unsigned int i; + + /* First byte, whether it is full or not */ + sum = bitpop8(packedRow[0] << offset ); - for (i = 0; i < fullByteCnt; ++i) - sum += bitpop8(packedRow[i]); + /* Second byte to last full byte */ + for (i = 1; i < fullByteCnt; ++i) + sum += bitpop8(packedRow[i]); - if (colByteCnt > fullByteCnt) - sum += bitpop8(packedRow[i] >> (8-cols%8)); + /* Partial byte at the right end */ + if (colByteCnt > fullByteCnt) + sum += bitpop8(packedRow[i] >> (8 - fullLength%8)); + } return sum; } @@ -151,19 +166,13 @@ pbm_backgroundbitrow(unsigned const char * const packedBits, unsigned int retval; - unsigned int firstbit, lastbit; - unsigned int totalBitpop, headBitpop; - - firstbit = (row[0] >> (7-rs)) & 0x01; - lastbit = (row[last] >> (7- (cols+rs-1)%8)) & 0x01; + bool const firstbit = (row[0] >> (7-rs)) & 0x01; + bool const lastbit = (row[last] >> (7- (cols+rs-1)%8)) & 0x01; if (firstbit == lastbit) retval = firstbit; else { - totalBitpop = bitpop(row, cols + rs); - headBitpop = (rs == 0) ? 0 : bitpop(row, rs); - - if (totalBitpop - headBitpop >= cols/2) + if (bitpop(row, cols, rs) >= cols/2) retval = PBM_BLACK; else retval = PBM_WHITE; diff --git a/lib/libpbm2.c b/lib/libpbm2.c index a8e4b0f6..f199c51a 100644 --- a/lib/libpbm2.c +++ b/lib/libpbm2.c @@ -10,6 +10,7 @@ ** implied warranty. */ +#include <assert.h> #include <limits.h> #include "pbm.h" @@ -34,11 +35,9 @@ getbit (FILE * const file) { void -pbm_readpbminitrest( file, colsP, rowsP ) - FILE* file; - int* colsP; - int* rowsP; - { +pbm_readpbminitrest( FILE * const file, + int * const colsP, + int * const rowsP ) { /* Read size. */ *colsP = (int)pm_getuint( file ); *rowsP = (int)pm_getuint( file ); @@ -55,7 +54,7 @@ pbm_readpbminitrest( file, colsP, rowsP ) pm_error("Number of columns in header is too large."); if (*rowsP < 0) pm_error("Number of columns in header is too large."); - } +} @@ -87,10 +86,13 @@ pbm_readpbminit(FILE * const ifP, int * const rowsP, int * const formatP) { - *formatP = pm_readmagicnumber(ifP); + int realFormat; - switch (PAM_FORMAT_TYPE(*formatP)) { + realFormat = pm_readmagicnumber(ifP); + + switch (PAM_FORMAT_TYPE(realFormat)) { case PBM_TYPE: + *formatP = realFormat; pbm_readpbminitrest(ifP, colsP, rowsP); break; @@ -110,7 +112,8 @@ pbm_readpbminit(FILE * const ifP, "to PBM with 'pamtopnm'"); break; default: - pm_error("bad magic number - not a Netpbm file"); + pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file", + realFormat); } validateComputableSize(*colsP, *rowsP); } @@ -118,32 +121,29 @@ pbm_readpbminit(FILE * const ifP, void -pbm_readpbmrow( file, bitrow, cols, format ) - FILE* file; - bit* bitrow; - int cols, format; - { - register int col, bitshift; - register bit* bP; +pbm_readpbmrow( FILE * const file, + bit * const bitrow, + int const cols, + int const format) { + + int col, bitshift; switch ( format ) { case PBM_FORMAT: - for ( col = 0, bP = bitrow; col < cols; ++col, ++bP ) - *bP = getbit( file ); + for ( col = 0; col < cols; ++col ) + bitrow[col] = getbit( file ); break; case RPBM_FORMAT: { - register unsigned char item; + unsigned char item; bitshift = -1; item = 0; /* item's value is meaningless here */ - for ( col = 0, bP = bitrow; col < cols; ++col, ++bP ) - { - if ( bitshift == -1 ) - { + for ( col = 0; col < cols; ++col ) { + if ( bitshift == -1 ) { item = pm_getrawbyte( file ); bitshift = 7; } - *bP = ( item >> bitshift ) & 1; + bitrow[col] = ( item >> bitshift ) & 1; --bitshift; } } @@ -152,7 +152,7 @@ pbm_readpbmrow( file, bitrow, cols, format ) default: pm_error( "can't happen" ); } - } +} @@ -180,12 +180,12 @@ pbm_readpbmrow_packed(FILE * const fileP, break; case RPBM_FORMAT: { - int bytes_read; - bytes_read = fread(packedBits, 1, pbm_packed_bytes(cols), fileP); + unsigned int bytesReadCt; + bytesReadCt = fread(packedBits, 1, pbm_packed_bytes(cols), fileP); - if (bytes_read < pbm_packed_bytes(cols)) { + if (bytesReadCt < pbm_packed_bytes(cols)) { if (feof(fileP)) - if (bytes_read == 0) + if (bytesReadCt == 0) pm_error("Attempt to read a raw PBM image row, but " "no more rows left in file."); else @@ -197,7 +197,7 @@ pbm_readpbmrow_packed(FILE * const fileP, break; default: - pm_error( "Internal error in pbm_readpbmrow_packed." ); + pm_error("Internal error in pbm_readpbmrow_packed."); } } @@ -258,17 +258,36 @@ pbm_readpbmrow_bitoffset(FILE * const ifP, window[last] = leftBits | rightBits; } -} +} + + + +void +pbm_cleanrowend_packed(unsigned char * const packedBits, + unsigned int const cols) { +/*---------------------------------------------------------------------------- + Set fractional "don't care" bits at end of row to zero. +----------------------------------------------------------------------------*/ + unsigned int const bitsPerChar = 8; + + if (cols % bitsPerChar > 0) { + unsigned int const last = pbm_packed_bytes(cols) - 1; + + assert(pbm_packed_bytes(cols) > 0); + + packedBits[last] >>= bitsPerChar - cols % bitsPerChar; + packedBits[last] <<= bitsPerChar - cols % bitsPerChar; + } +} bit** -pbm_readpbm( file, colsP, rowsP ) - FILE* file; - int* colsP; - int* rowsP; - { - register bit** bits; +pbm_readpbm( FILE * const file, + int * const colsP, + int * const rowsP) { + + bit ** bits; int format, row; pbm_readpbminit( file, colsP, rowsP, &format ); @@ -279,4 +298,4 @@ pbm_readpbm( file, colsP, rowsP ) pbm_readpbmrow( file, bits[row], *colsP, format ); return bits; - } +} diff --git a/lib/libpbm3.c b/lib/libpbm3.c index 9200d30e..c8389824 100644 --- a/lib/libpbm3.c +++ b/lib/libpbm3.c @@ -12,21 +12,33 @@ #include <assert.h> -#include "pm_c_util.h" +#include "netpbm/pm_c_util.h" + #include "pbm.h" -#if HAVE_GCC_MMXSSE -#include "bitreverse.h" +#ifndef PACKBITS_SSE +#if WANT_SSE && defined(__SSE2__) && HAVE_GCC_BSWAP + #define PACKBITS_SSE 2 +#else + #define PACKBITS_SSE 0 +#endif #endif -/* HAVE_GCC_MMXSSE means we have the means to use MMX and SSE CPU facilities - to make PBM raster processing faster. GCC only. +/* WANT_SSE means we want to use SSE CPU facilities to make PBM raster + processing faster. This implies it's actually possible - i.e. the + build environment has <emmintrin.h>. - The GNU Compiler -msse option makes SSE available. - For x86-32 with MMX/SSE, "-msse" must be explicitly given. - For x86-64 and AMD64, "-msse" is on by default. + The GNU Compiler -msse2 option makes SSE/SSE2 available, and is + evidenced by __SSE2__. + For x86-32 with SSE, "-msse2" must be explicitly given. + For x86-64 and AMD64, "-msse2" is the default (from Gcc v.4.) */ +#if PACKBITS_SSE == 2 + #include <emmintrin.h> +#endif + + void pbm_writepbminit(FILE * const fileP, int const cols, @@ -35,9 +47,6 @@ pbm_writepbminit(FILE * const fileP, if (!forceplain && !pm_plain_output) { fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, RPBM_MAGIC2, cols, rows); -#ifdef VMS - set_outfile_binary(); -#endif } else fprintf(fileP, "%c%c\n%d %d\n", PBM_MAGIC1, PBM_MAGIC2, cols, rows); } @@ -56,80 +65,107 @@ writePackedRawRow(FILE * const fileP, } -#if HAVE_GCC_MMXSSE + +#if PACKBITS_SSE == 2 static void -packBitsWithMmxSse(FILE * const fileP, +packBitsWithSse2( FILE * const fileP, const bit * const bitrow, unsigned char * const packedBits, - unsigned int const cols, - unsigned int * const nextColP) { + unsigned int const cols) { /*---------------------------------------------------------------------------- - Pack the bits of bitrow[] into bytes at 'packedBits'. Going left to right, - stop when there aren't enough bits left to fill a whole byte. Return - as *nextColP the number of the next column after the rightmost one we - packed. + Pack the bits of bitrow[] into bytes at 'packedBits'. + + Use the SSE2 facilities to pack the bits quickly, but + perform the exact same function as the simpler + packBitsGeneric() + packPartialBytes() - Use the Pentium MMX and SSE facilities to pack the bits quickly, but - perform the exact same function as the simpler packBitsGeneric(). + Unlike packBitsGeneric(), the whole row is converted. -----------------------------------------------------------------------------*/ /* - We use MMX/SSE facilities that operate on 8 bytes at once to pack - the bits quickly. - - We use 2 MMX registers (no SSE registers). + We use 2 SSE registers. The key machine instructions are: - - - PCMPGTB Packed CoMPare Greater Than Byte - - Compares 8 bytes in parallel - Result is x00 if greater than, xFF if not for each byte - - PMOVMSKB Packed MOVe MaSK Byte - - Result is a byte of the MSBs of 8 bytes - x00 xFF x00 xFF xFF xFF x00 x00 --> 01011100B = 0x5C - The result is actually a 32 bit int, but the higher bits are - always 0. (0x0000005C in the above case) + PCMPGTB128 Packed CoMPare Greater Than Byte - EMMS Empty MMx State + Compares 16 bytes in parallel + Result is x00 if greater than, xFF if not for each byte + - Free MMX registers + PMOVMSKB128 Packed MOVe MaSK Byte - */ - + Result is 16 bits, the MSBs of 16 bytes + x00 xFF x00 xFF xFF xFF x00 x00 xFF xFF xFF xFF x00 x00 x00 x00 + --> 0101110011110000B = 0x5CF0 + + The result is actually a 64 bit int, but the higher bits are + always 0. - typedef char v8qi __attribute__ ((vector_size(8))); - typedef int di __attribute__ ((mode(DI))); + We use SSE instructions in "_mm_" form in favor of "__builtin_". + In GCC the "__builtin_" form is documented but "_mm_" is not. + Former versions of this source file used "__builtin_". This was + changed to make possible compilation with clang, which does not + implement some "__builtin_" forms. - unsigned int col; - v8qi const zero64 =(v8qi)((di)0); /* clear to zero */ + __builtin_ia32_pcmpgtb128 : _mm_cmpgt_epi8 + __builtin_ia32_pmovmskb128 : _mm_movemask_epi8 - for (col = 0; col + 7 < cols; col += 8) { + The conversion requires <emmintrin.h> . + */ - v8qi const compare = - __builtin_ia32_pcmpgtb(*(v8qi*) (&bitrow[col]), (v8qi) zero64); - uint32_t const backwardBlackMask = __builtin_ia32_pmovmskb(compare); - unsigned char const blackMask = bitreverse[backwardBlackMask]; + typedef char v16qi __attribute__ ((vector_size(16))); - packedBits[col/8] = blackMask; + unsigned int col; + union { + v16qi v16; + uint64_t i64[2]; + unsigned char byte[16]; + } bit128; + + v16qi zero128; + zero128 = zero128 ^ zero128; /* clear to zero */ + + for (col = 0; col + 15 < cols; col += 16) { + bit128.i64[0]=__builtin_bswap64( *(uint64_t*) &bitrow[col]); + bit128.i64[1]=__builtin_bswap64( *(uint64_t*) &bitrow[col+8]); + + { + v16qi const compare = (v16qi) + _mm_cmpgt_epi8((__m128i)bit128.v16, (__m128i) zero128); + uint16_t const blackMask = _mm_movemask_epi8 ((__m128i)compare); + + *(uint16_t *) & packedBits[col/8] = blackMask; + } } - *nextColP = col; - __builtin_ia32_emms(); + if (cols % 16 > 0) { + unsigned int i, j; + bit128.v16 = bit128.v16 ^ bit128.v16; + + for (i = 0, j = col ; j < cols; ++i, ++j) + bit128.byte[ (i&8) + 7-(i&7) ] = bitrow[j]; + + { + v16qi const compare = (v16qi) + _mm_cmpgt_epi8((__m128i)bit128.v16, (__m128i) zero128); + uint16_t const blackMask = _mm_movemask_epi8 ((__m128i)compare); + + if ( cols%16 >8 ) /* Two partial bytes */ + *(uint16_t *) & packedBits[col/8] = blackMask; + else /* One partial byte */ + packedBits[col/8] = (unsigned char) blackMask ; + } + } } #else /* Avoid undefined function warning; never actually called */ -#define packBitsWithMmxSse(a,b,c,d,e) packBitsGeneric(a,b,c,d,e) +#define packBitsWithSse2(a,b,c,d) packBitsGeneric((a),(b),(c),(d),NULL) #endif - static unsigned int bitValue(unsigned char const byteValue) { @@ -212,18 +248,20 @@ writePbmRowRaw(FILE * const fileP, pm_setjmpbuf(origJmpbufP); pm_longjmp(); } else { - unsigned int nextCol; pm_setjmpbufsave(&jmpbuf, &origJmpbufP); - if (HAVE_GCC_MMXSSE) - packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol); - else + switch (PACKBITS_SSE) { + case 2: + packBitsWithSse2(fileP, bitrow, packedBits, cols); + break; + default: { + unsigned int nextCol; packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol); - - if (cols % 8 > 0) - packPartialBytes(bitrow, cols, nextCol, packedBits); - + if (cols % 8 > 0) + packPartialBytes(bitrow, cols, nextCol, packedBits); + } + } writePackedRawRow(fileP, packedBits, cols); pm_setjmpbuf(origJmpbufP); diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c index 4ba812cd..d3551e78 100644 --- a/lib/libpbmfont.c +++ b/lib/libpbmfont.c @@ -16,12 +16,14 @@ #include <assert.h> #include <string.h> +#include <ctype.h> + +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" -#include "pm_c_util.h" -#include "nstring.h" -#include "pbm.h" #include "pbmfont.h" -#include "mallocvar.h" +#include "pbm.h" static unsigned int const firstCodePoint = 32; /* This is the code point of the first character in a pbmfont font. @@ -1072,12 +1074,48 @@ pbm_dumpfont( fn ) /* Routines for loading a BDF font file */ -static unsigned int -mk_argvn(char * const s, - const char ** const vec, - unsigned int const mk_max) { +typedef struct { +/*---------------------------------------------------------------------------- + This is an object for reading lines of a font file. It reads and tokenizes + them into words. +-----------------------------------------------------------------------------*/ + FILE * ifP; - int n; + char line[1024]; + /* This is the storage space for the words of the line. The + words go in here, one after another, separated by NULs. + + It also functions as a work area for readline_read(). + */ + const char * arg[32]; + /* These are the words; each entry is a pointer into line[] (above) */ +} readline; + + + +static void +readline_init(readline * const readlineP, + FILE * const ifP) { + + readlineP->ifP = ifP; + + readlineP->arg[0] = NULL; +} + + + +static void +tokenize(char * const s, + const char ** const words, + unsigned int const maxWordCt) { +/*---------------------------------------------------------------------------- + Chop up 's' into words by changing space characters to NUL. Return + as 'words' pointer to the beginning of those words in 's'. + + If there are more than 'maxWordCt' words in 's', ignore the excess on + the right. +-----------------------------------------------------------------------------*/ + unsigned int n; char * p; p = &s[0]; @@ -1087,30 +1125,27 @@ mk_argvn(char * const s, if (ISSPACE(*p)) *p++ = '\0'; else { - vec[n++] = p; - if (n >= mk_max-1) + words[n++] = p; + if (n >= maxWordCt-1) break; while (*p && !ISSPACE(*p)) ++p; } } - vec[n] = NULL; - - return n; + words[n] = NULL; } -static int -readline(FILE * const ifP, - char * const line, - const char ** const wordList) { +static void +readline_read(readline * const readlineP, + bool * const eofP) { /*---------------------------------------------------------------------------- - Read a nonblank line from file *ifP. Return the value of the whole line - in *buf (must be at least 1024 bytes long), and parse it into words - in *wordList (must have at least 32 entries). + Read a nonblank line from the file. Make its contents available + as readlineP->arg[]. - If there is no nonblank line before EOF, return rc == -1. + Return *eofP == true iff there is no nonblank line before EOF or we + are unable to read the file. -----------------------------------------------------------------------------*/ bool gotLine; bool error; @@ -1118,22 +1153,85 @@ readline(FILE * const ifP, for (gotLine = false, error = false; !gotLine && !error; ) { char * rc; - rc = fgets(line, 1024, ifP); + rc = fgets(readlineP->line, 1024, readlineP->ifP); if (rc == NULL) error = true; else { - mk_argvn(line, wordList, 32); - if (wordList[0] != NULL) + tokenize(readlineP->line, + readlineP->arg, ARRAY_SIZE(readlineP->arg)); + if (readlineP->arg[0] != NULL) gotLine = true; } } - return error ? -1 : 0; + *eofP = error; +} + + + +static void +parseBitmapRow(const char * const hex, + unsigned int const glyphWidth, + unsigned char * const bmap, + unsigned int const origBmapIndex, + unsigned int * const newBmapIndexP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Parse one row of the bitmap for a glyph, from the hexadecimal string + for that row in the font file, 'hex'. The glyph is 'glyphWidth' + pixels wide. + + We place our result in 'bmap' at *bmapIndexP and advanced *bmapIndexP. +-----------------------------------------------------------------------------*/ + unsigned int bmapIndex; + int i; /* dot counter */ + const char * p; + + bmapIndex = origBmapIndex; + + for (i = glyphWidth, p = &hex[0], *errorP = NULL; + i > 0 && !*errorP; + i -= 4) { + + if (*p == '\0') + pm_asprintf(errorP, "Not enough hexadecimal digits for glyph " + "of width %u in '%s'", + glyphWidth, hex); + else { + char const hdig = *p++; + unsigned int hdigValue; + + if (hdig >= '0' && hdig <= '9') + hdigValue = hdig - '0'; + else if (hdig >= 'a' && hdig <= 'f') + hdigValue = 10 + (hdig - 'a'); + else if (hdig >= 'A' && hdig <= 'F') + hdigValue = 10 + (hdig - 'A'); + else + pm_asprintf(errorP, + "Invalid hex digit x%02x (%c) in bitmap data '%s'", + (unsigned int)(unsigned char)hdig, + isprint(hdig) ? hdig : '.', + hex); + + if (!*errorP) { + if (i > 0) + bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0; + if (i > 1) + bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0; + if (i > 2) + bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0; + if (i > 3) + bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0; + } + } + } + *newBmapIndexP = bmapIndex; } static void -readBitmap(FILE * const fp, +readBitmap(readline * const readlineP, unsigned int const glyphWidth, unsigned int const glyphHeight, const char * const charName, @@ -1145,48 +1243,26 @@ readBitmap(FILE * const fp, bmapIndex = 0; for (n = glyphHeight; n > 0; --n) { - int i; /* dot counter */ - int rc; - char * hex; - char line[1024]; - const char * arg[32]; + bool eof; + const char * error; - rc = readline(fp, line, arg); + readline_read(readlineP, &eof); - if (rc < 0) + if (eof) pm_error("End of file in bitmap for character '%s' in BDF " "font file.", charName); - hex = line; - for (i = glyphWidth; i > 0; i -= 4) { - if (*hex == '\0') - pm_error("Premature end of line in line '%s' of " - "bitmap for character '%s' " - "in BDF font file", line, charName); - else { - char const hdig = *hex++; - unsigned int hdigValue; - - if (hdig >= '0' && hdig <= '9') - hdigValue = hdig - '0'; - else if (hdig >= 'a' && hdig <= 'f') - hdigValue = 10 + (hdig - 'a'); - else if (hdig >= 'A' && hdig <= 'F') - hdigValue = 10 + (hdig - 'A'); - else - pm_error("Invalid hex digit '%c' in line '%s' of " - "bitmap for character '%s' " - "in BDF font file", hdig, line, charName); - - if (i > 0) - bmap[bmapIndex++] = hdigValue & 0x8 ? 1 : 0; - if (i > 1) - bmap[bmapIndex++] = hdigValue & 0x4 ? 1 : 0; - if (i > 2) - bmap[bmapIndex++] = hdigValue & 0x2 ? 1 : 0; - if (i > 3) - bmap[bmapIndex++] = hdigValue & 0x1 ? 1 : 0; - } + if (!readlineP->arg[0]) + pm_error("A line that is supposed to contain bitmap data, " + "in hexadecimal, for character '%s' is empty", charName); + + parseBitmapRow(readlineP->arg[0], glyphWidth, bmap, bmapIndex, + &bmapIndex, &error); + + if (error) { + pm_error("Error in line %d of bitmap for character '%s': %s", + n, charName, error); + pm_strfree(error); } } } @@ -1196,15 +1272,13 @@ readBitmap(FILE * const fp, static void createBmap(unsigned int const glyphWidth, unsigned int const glyphHeight, - FILE * const fp, + readline * const readlineP, const char * const charName, const char ** const bmapP) { - char line[1024]; - const char * arg[32]; unsigned char * bmap; - int rc; - + bool eof; + if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight) pm_error("Ridiculously large glyph"); @@ -1213,24 +1287,26 @@ createBmap(unsigned int const glyphWidth, if (!bmap) pm_error("no memory for font glyph byte map"); - rc = readline(fp, line, arg); - if (rc < 0) + readline_read(readlineP, &eof); + if (eof) pm_error("End of file encountered reading font glyph byte map from " "BDF font file."); - - if (streq(arg[0], "ATTRIBUTES")) { - rc = readline(fp, line, arg); - if (rc < 0) + + if (streq(readlineP->arg[0], "ATTRIBUTES")) { + bool eof; + readline_read(readlineP, &eof); + if (eof) pm_error("End of file encountered after ATTRIBUTES in BDF " "font file."); } - if (!streq(arg[0], "BITMAP")) + if (!streq(readlineP->arg[0], "BITMAP")) pm_error("'%s' found where BITMAP expected in definition of " - "character '%s' in BDF font file.", line, charName); + "character '%s' in BDF font file.", + readlineP->arg[0], charName); - assert(streq(arg[0], "BITMAP")); + assert(streq(readlineP->arg[0], "BITMAP")); - readBitmap(fp, glyphWidth, glyphHeight, charName, bmap); + readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap); *bmapP = (char *)bmap; } @@ -1238,52 +1314,53 @@ createBmap(unsigned int const glyphWidth, static void -expect(FILE * const fp, - const char * const expected, - const char ** const arg, - char * const line) { - - int rc; +readExpectedStatement(readline * const readlineP, + const char * const expected) { +/*---------------------------------------------------------------------------- + Have the readline object *readlineP read the next line from the file, but + expect it to be a line of type 'expected' (i.e. the verb token at the + beginning of the line is that, e.g. "STARTFONT"). If it isn't, fail the + program. +-----------------------------------------------------------------------------*/ + bool eof; - rc = readline(fp, line, arg); + readline_read(readlineP, &eof); - if (rc < 0) + if (eof) pm_error("EOF in BDF font file where '%s' expected", expected); - else if (!streq(arg[0], expected)) - pm_error("'%s' where '%s' expected in BDF font file", - line, expected); + else if (!streq(readlineP->arg[0], expected)) + pm_error("Statement of type '%s' where '%s' expected in BDF font file", + readlineP->arg[0], expected); } static void -skipCharacter(FILE * const fp) { +skipCharacter(readline * const readlineP) { /*---------------------------------------------------------------------------- - In BDF font file 'fp', skip through the end of the character we are - presently in. + In the BDF font file being read by readline object *readlineP, skip through + the end of the character we are presently in. -----------------------------------------------------------------------------*/ bool endChar; endChar = FALSE; while (!endChar) { - char line[1024]; - const char * arg[32]; - int rc; - rc = readline(fp, line, arg); - if (rc < 0) + bool eof; + readline_read(readlineP, &eof); + if (eof) pm_error("End of file in the middle of a character (before " "ENDCHAR) in BDF font file."); - endChar = streq(arg[0], "ENDCHAR"); + endChar = streq(readlineP->arg[0], "ENDCHAR"); } } static void -validateEncoding(const char ** const arg, - unsigned int * const codepointP, - bool * const badCodepointP) { +interpEncoding(const char ** const arg, + unsigned int * const codepointP, + bool * const badCodepointP) { /*---------------------------------------------------------------------------- With arg[] being the ENCODING statement from the font, return as *codepointP the codepoint that it indicates (code point is the character @@ -1323,44 +1400,57 @@ validateEncoding(const char ** const arg, - static void -processCharsLine(FILE * const fp, - const char ** const arg, - struct font * const fontP) { +readEncoding(readline * const readlineP, + unsigned int * const codepointP, + bool * const badCodepointP) { + + readExpectedStatement(readlineP, "ENCODING"); + + interpEncoding(readlineP->arg, codepointP, badCodepointP); +} + + +static void +processChars(readline * const readlineP, + struct font * const fontP) { +/*---------------------------------------------------------------------------- + Process the CHARS block in a BDF font file, assuming the file is positioned + just after the CHARS line. Read the rest of the block and apply its + contents to *fontP. +-----------------------------------------------------------------------------*/ unsigned int nCharacters; unsigned int nCharsDone; - if (!arg[1]) + if (!readlineP->arg[1]) pm_error("Invalid CHARS line - no arguments"); - nCharacters = atoi(arg[1]); + nCharacters = atoi(readlineP->arg[1]); nCharsDone = 0; while (nCharsDone < nCharacters) { - char line[1024]; - const char * arg[32]; - int rc; + bool eof; - rc = readline(fp, line, arg); - if (rc < 0) + readline_read(readlineP, &eof); + if (eof) pm_error("End of file after CHARS reading BDF font file"); - if (streq(arg[0], "COMMENT")) { + if (streq(readlineP->arg[0], "COMMENT")) { /* ignore */ - } else if (!streq(arg[0], "STARTCHAR")) + } else if (!streq(readlineP->arg[0], "STARTCHAR")) pm_error("no STARTCHAR after CHARS in BDF font file"); - else if (!arg[1]) + else if (!readlineP->arg[1]) pm_error("Invalid STARTCHAR - no arguments"); else { - const char * const charName = arg[1]; + const char * const charName = readlineP->arg[1]; + struct glyph * glyphP; unsigned int codepoint; bool badCodepoint; - assert(streq(arg[0], "STARTCHAR")); + assert(streq(readlineP->arg[0], "STARTCHAR")); MALLOCVAR(glyphP); @@ -1368,52 +1458,39 @@ processCharsLine(FILE * const fp, pm_error("no memory for font glyph for '%s' character", charName); - { - const char * arg[32]; - expect(fp, "ENCODING", arg, line); + readEncoding(readlineP, &codepoint, &badCodepoint); - validateEncoding(arg, &codepoint, &badCodepoint); - } if (badCodepoint) - skipCharacter(fp); + skipCharacter(readlineP); else { - { - const char * arg[32]; - expect(fp, "SWIDTH", arg, line); - } - { - const char * arg[32]; - - expect(fp, "DWIDTH", arg, line); - if (!arg[1]) - pm_error("Invalid DWIDTH statement - no arguments"); - glyphP->xadd = atoi(arg[1]); - } - { - const char * arg[32]; + readExpectedStatement(readlineP, "SWIDTH"); - expect(fp, "BBX", arg, line); - if (!arg[1]) - pm_error("Invalid BBX statement - no arguments"); - glyphP->width = atoi(arg[1]); - if (!arg[2]) - pm_error("Invalid BBX statement - only 1 argument"); - glyphP->height = atoi(arg[2]); - if (!arg[3]) - pm_error("Invalid BBX statement - only 2 arguments"); - glyphP->x = atoi(arg[3]); - if (!arg[4]) - pm_error("Invalid BBX statement - only 3 arguments"); - glyphP->y = atoi(arg[4]); - } - createBmap(glyphP->width, glyphP->height, fp, charName, + readExpectedStatement(readlineP, "DWIDTH"); + if (!readlineP->arg[1]) + pm_error("Invalid DWIDTH statement - no arguments"); + glyphP->xadd = atoi(readlineP->arg[1]); + + readExpectedStatement(readlineP, "BBX"); + if (!readlineP->arg[1]) + pm_error("Invalid BBX statement - no arguments"); + glyphP->width = atoi(readlineP->arg[1]); + if (!readlineP->arg[2]) + pm_error("Invalid BBX statement - only 1 argument"); + glyphP->height = atoi(readlineP->arg[2]); + if (!readlineP->arg[3]) + pm_error("Invalid BBX statement - only 2 arguments"); + glyphP->x = atoi(readlineP->arg[3]); + if (!readlineP->arg[4]) + pm_error("Invalid BBX statement - only 3 arguments"); + glyphP->y = atoi(readlineP->arg[4]); + + createBmap(glyphP->width, glyphP->height, readlineP, charName, &glyphP->bmap); - { - const char * arg[32]; - expect(fp, "ENDCHAR", arg, line); - } - assert(codepoint < 256); /* Ensured by validateEncoding() */ + + readExpectedStatement(readlineP, "ENDCHAR"); + + assert(codepoint < 256); /* Ensured by readEncoding() */ fontP->glyph[codepoint] = glyphP; } @@ -1425,51 +1502,57 @@ processCharsLine(FILE * const fp, static void -processFontLine(FILE * const fp, - const char * const line, - const char ** const arg, - struct font * const fontP, - bool * const endOfFontP) { +processBdfFontLine(readline * const readlineP, + struct font * const fontP, + bool * const endOfFontP) { +/*---------------------------------------------------------------------------- + Process a nonblank line just read from a BDF font file. + This processing may involve reading more lines. +-----------------------------------------------------------------------------*/ *endOfFontP = FALSE; /* initial assumption */ - if (streq(arg[0], "COMMENT")) { + assert(readlineP->arg[0] != NULL); /* Entry condition */ + + if (streq(readlineP->arg[0], "COMMENT")) { /* ignore */ - } else if (streq(arg[0], "SIZE")) { + } else if (streq(readlineP->arg[0], "SIZE")) { /* ignore */ - } else if (streq(arg[0], "STARTPROPERTIES")) { + } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) { + /* Read off the properties and ignore them all */ unsigned int propCount; unsigned int i; - if (!arg[1]) + if (!readlineP->arg[1]) pm_error("Invalid STARTPROPERTIES statement - no arguments"); - propCount = atoi(arg[1]); + propCount = atoi(readlineP->arg[1]); for (i = 0; i < propCount; ++i) { - char line[1024]; - const char * arg[32]; - int rc; - rc = readline(fp, line, arg); - if (rc < 0) + bool eof; + readline_read(readlineP, &eof); + if (eof) pm_error("End of file after STARTPROPERTIES in BDF font file"); } - } else if (streq(arg[0], "FONTBOUNDINGBOX")) { - if (!arg[1]) + } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) { + if (!readlineP->arg[1]) pm_error("Invalid FONTBOUNDINGBOX statement - no arguments"); - fontP->maxwidth = atoi(arg[1]); - if (!arg[2]) + fontP->maxwidth = atoi(readlineP->arg[1]); + if (!readlineP->arg[2]) pm_error("Invalid FONTBOUNDINGBOX statement - only 1 argument"); - fontP->maxheight = atoi(arg[2]); - if (!arg[3]) + fontP->maxheight = atoi(readlineP->arg[2]); + if (!readlineP->arg[3]) pm_error("Invalid FONTBOUNDINGBOX statement - only 2 arguments"); - fontP->x = atoi(arg[3]); - if (!arg[4]) + fontP->x = atoi(readlineP->arg[3]); + if (!readlineP->arg[4]) pm_error("Invalid FONTBOUNDINGBOX statement - only 3 arguments"); - fontP->y = atoi(arg[4]); - } else if (streq(arg[0], "ENDFONT")) { + fontP->y = atoi(readlineP->arg[4]); + } else if (streq(readlineP->arg[0], "ENDFONT")) { *endOfFontP = true; - } else if (!strcmp(arg[0], "CHARS")) - processCharsLine(fp, arg, fontP); + } else if (streq(readlineP->arg[0], "CHARS")) { + processChars(readlineP, fontP); + } else { + /* ignore */ + } } @@ -1477,18 +1560,17 @@ processFontLine(FILE * const fp, struct font * pbm_loadbdffont(const char * const name) { - FILE * fp; - char line[1024]; - const char * arg[32]; + FILE * ifP; + readline readline; struct font * fontP; bool endOfFont; - fp = fopen(name, "rb"); - if (!fp) + ifP = fopen(name, "rb"); + if (!ifP) pm_error("Unable to open BDF font file name '%s'. errno=%d (%s)", name, errno, strerror(errno)); - expect(fp, "STARTFONT", arg, line); + readline_init(&readline, ifP); MALLOCVAR(fontP); if (fontP == NULL) @@ -1500,20 +1582,22 @@ pbm_loadbdffont(const char * const name) { find in the bdf file later. */ unsigned int i; - for (i = 0; i < 256; i++) + for (i = 0; i < 256; ++i) fontP->glyph[i] = NULL; } fontP->x = fontP->y = 0; + readExpectedStatement(&readline, "STARTFONT"); + endOfFont = FALSE; while (!endOfFont) { - int rc; - rc = readline(fp, line, arg); - if (rc < 0) + bool eof; + readline_read(&readline, &eof); + if (eof) pm_error("End of file before ENDFONT statement in BDF font file"); - processFontLine(fp, line, arg, fontP, &endOfFont); + processBdfFontLine(&readline, fontP, &endOfFont); } return fontP; } diff --git a/lib/libpbmvms.c b/lib/libpbmvms.c deleted file mode 100644 index 8a2a54f8..00000000 --- a/lib/libpbmvms.c +++ /dev/null @@ -1,734 +0,0 @@ -/*************************************************************************** - This file contains library routines needed to build Netpbm for VMS. - However, as of 2000.05.26, when these were split out of libpbm1.c - (where they were all switched by #ifdef VMS), there is no evidence - that anyone is building Netpbm for VMS. -****************************************************************************/ - -/* - * @(#)argproc.c 1.0 89/02/01 Mark Pizzolato (mark@infopiz.uucp) - */ - -#ifndef lint -char argproc_version[] = "@(#)argproc.c VMS uucp Version infopiz-1.0"; -#endif - -#include "includes.h" /* System include files, system dependent */ - - -/* - * getredirection() is intended to aid in porting C programs - * to VMS (Vax-11 C) which does not have '>' and '<' - * I/O redirection, along with a command line pipe mechanism - * using the '|' AND background command execution '&'. - * The piping mechanism will probably work with almost any 'filter' type - * of program. With suitable modification, it may useful for other - * portability problems as well. - * - * Author: Mark Pizzolato mark@infopiz.UUCP - */ -struct list_item - { - struct list_item *next; - char *value; - }; - -int -getredirection(ac, av) -int *ac; -char ***av; -/* - * Process vms redirection arg's. Exit if any error is seen. - * If getredirection() processes an argument, it is erased - * from the vector. getredirection() returns a new argc and argv value. - * In the event that a background command is requested (by a trailing "&"), - * this routine creates a background subprocess, and simply exits the program. - * - * Warning: do not try to simplify the code for vms. The code - * presupposes that getredirection() is called before any data is - * read from stdin or written to stdout. - * - * Normal usage is as follows: - * - * main(argc, argv) - * int argc; - * char *argv[]; - * { - * getredirection(&argc, &argv); - * } - */ -{ - int argc = *ac; /* Argument Count */ - char **argv = *av; /* Argument Vector */ - char *ap; /* Argument pointer */ - int j; /* argv[] index */ - extern int errno; /* Last vms i/o error */ - int item_count = 0; /* Count of Items in List */ - int new_file; /* flag, true if '>' used */ - struct list_item *list_head = 0; /* First Item in List */ - struct list_item *list_tail; /* Last Item in List */ - char *in = NULL; /* Input File Name */ - char *out = NULL; /* Output File Name */ - char *outmode = "w"; /* Mode to Open Output File */ - int cmargc = 0; /* Piped Command Arg Count */ - char **cmargv = NULL;/* Piped Command Arg Vector */ - stat_t statbuf; /* fstat buffer */ - - /* - * First handle the case where the last thing on the line ends with - * a '&'. This indicates the desire for the command to be run in a - * subprocess, so we satisfy that desire. - */ - ap = argv[argc-1]; - if (0 == strcmp("&", ap)) - exit(background_process(--argc, argv)); - if ('&' == ap[strlen(ap)-1]) - { - ap[strlen(ap)-1] = '\0'; - exit(background_process(argc, argv)); - } - /* - * Now we handle the general redirection cases that involve '>', '>>', - * '<', and pipes '|'. - */ - for (new_file = 0, j = 0; j < argc; ++j) - { - if (0 == strcmp("<", argv[j])) - { - if (j+1 >= argc) - { - errno = EINVAL; - perror("No input file"); - exit(EXIT_ERR); - } - in = argv[++j]; - continue; - } - if ('<' == *(ap = argv[j])) - { - in = 1 + ap; - continue; - } - if (0 == strcmp(">", ap)) - { - if (j+1 >= argc) - { - errno = EINVAL; - perror("No output file"); - exit(EXIT_ERR); - } - out = argv[++j]; - new_file = 1; - continue; - } - if ('>' == *ap) - { - if ('>' == ap[1]) - { - outmode = "a"; - if ('\0' == ap[2]) - out = argv[++j]; - else - out = 2 + ap; - } - else - { out = 1 + ap; new_file = 1; } - continue; - } - if (0 == strcmp("|", argv[j])) - { - if (j+1 >= argc) - { - errno = EPIPE; - perror("No command to Pipe to"); - exit(EXIT_ERR); - } - cmargc = argc-(j+1); - cmargv = &argv[j+1]; - argc = j; - outmode = "wb"; /* pipes are binary mode devices */ - continue; - } - if ('|' == *(ap = argv[j])) - { - ++argv[j]; - cmargc = argc-j; - cmargv = &argv[j]; - argc = j; - outmode = "wb"; /* pipes are binary mode devices */ - continue; - } - expand_wild_cards(ap, &list_head, &list_tail, &item_count); - } - /* - * Allocate and fill in the new argument vector, Some Unix's terminate - * the list with an extra null pointer. - */ - argv = *av = calloc(item_count+1, sizeof(char *)); - for (j = 0; j < item_count; ++j, list_head = list_head->next) - argv[j] = list_head->value; - *ac = item_count; - if (cmargv != NULL) - { - char subcmd[1024]; - static char *pipe_and_fork(); - - if (out != NULL) - { - errno = EINVAL; - perror("Invalid '|' and '>' specified"); - exit(EXIT_ERR); - } - strcpy(subcmd, cmargv[0]); - for (j = 1; j < cmargc; ++j) - { - strcat(subcmd, " \""); - strcat(subcmd, cmargv[j]); - strcat(subcmd, "\""); - } - out = pipe_and_fork(subcmd); - outmode = "wb"; - } - - /* Check for input from a pipe (mailbox) */ - - if(fstat(0, &statbuf) == 0){ - if(strncmp(statbuf.st_dev, "MB", 2) == 0 || - strncmp(statbuf.st_dev, "_MB", 3) == 0){ - - /* Input from a pipe, reopen it in binary mode to disable */ - /* carriage control processing. */ - - if (in != NULL){ - errno = EINVAL; - perror("Invalid '|' and '<' specified"); - exit(EXIT_ERR); - } - freopen(statbuf.st_dev, "rb", stdin); - } - } - else { - perror("fstat failed"); - exit(EXIT_ERR); - } - -#ifdef __ALPHA - /*, "mbc=32", "mbf=2"))) blows up on the ALPHA cbm 11/08/92 */ - if ((in != NULL) && (NULL == freopen(in, "r", stdin))) - { -#else - if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2"))) - { -#endif - perror(in); /* Can't find file */ - exit(EXIT_ERR); /* Is a fatal error */ - } -#ifdef __ALPHA - if ((out != NULL) && (NULL == freopen(out, outmode, stdout))) - { -#else - if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2"))) - { -#endif - perror(ap); /* Error, can't write or append */ - exit(EXIT_ERR); /* Is a fatal error */ - } - - if ( new_file ) { - /* - * We are making an explicit output file, fstat the file and - * declare exit handler to be able change the file to fixed length - * records if necessary. - */ - char fname[256]; - static int outfile_rundown(), check_outfile_filetype(); - static stat_t ofile_stat; - static struct exit_control_block { - struct exit_control_block *flink; - int (*exit_routine)(); - int arg_count; - int *status_address; /* arg 1 */ - stat_t *stat; /* arg 2 */ - int exit_status; - int skew[128]; - } exit_block = - { 0, outfile_rundown, 2, &exit_block.exit_status, &ofile_stat, 0 }; - - if ( fstat ( fileno(stdout), &ofile_stat ) == 0 ) - sys$dclexh ( &exit_block ); - else fprintf(stderr,"Error fstating stdout - %s\n", - strerror(errno,vaxc$errno) ); - - if ( fgetname ( stdout, fname, 0 ) ) check_outfile_filetype ( fname ); - } -#ifdef DEBUG - fprintf(stderr, "Arglist:\n"); - for (j = 0; j < *ac; ++j) - fprintf(stderr, "argv[%d] = '%s'\n", j, argv[j]); -#endif -} - -static int binary_outfile = 0; -void set_outfile_binary() { binary_outfile = 1; return; } - -/* - * Check if output file should be set to binary (fixed 512) on exit based - * upon the filetype. - */ -static int check_outfile_filetype ( name ) - char *name; -{ - char *binary_filetypes, *ext, *p, *t; - binary_filetypes = (char *) getenv ( "PBM_BINARY_FILETYPES" ); - if ( binary_filetypes == NULL ) return 0; - - ext = strchr ( name, '.' ); if ( ext == NULL ) return 0; - ext++; - t = strrchr ( name, '.' ); if ( t != NULL ) *t = '\0'; - - for ( p = binary_filetypes; *p != '\0'; p++ ) { - for ( t = p; - (*p != '\0' ) && (strchr ( ", ", *p ) == NULL); - p++ ) *p = toupper(*p); - *p = '\0'; - - if ( strcmp ( t, ext ) == 0 ) { - binary_outfile = 1; - break; - } - } - return binary_outfile; -} - - -/* - * Exit handler to set output file to binary on image exit. - */ -static int outfile_rundown ( reason, statbuf ) - int *reason; - stat_t *statbuf; -{ - int code, channel, status, LIB$GETDVI(), sys$assign(), sys$qiow(); - long int fib_desc[2], devchar; - short int iosb[4]; - $DESCRIPTOR(device, statbuf->st_dev); - struct fibdef fib; /* File information block (XQP) */ - struct atrdef atr[2]; - struct fat { - unsigned char rtype, ratattrib; - unsigned short int rsize, hiblk[2], efblk[2], ffbyte, maxrec, defext, gbc; - unsigned short int reserved[4], versions; - } rat; - - if ( !binary_outfile ) return 1; /* leave file alone */ - /* - * Assign channel to device listed in stat block and test if it is - * a directory structured device, returning if not. - */ - device.dsc$w_length = strlen ( statbuf->st_dev ); - status = sys$assign ( &device, &channel, 0, 0 ); - if ((status & 1) == 0) { fprintf(stderr, - "assign error to %s (%d)\n", device.dsc$a_pointer, status); - return status; } - code = DVI$_DEVCHAR; - status = LIB$GETDVI ( &code, &channel, 0, &devchar ); - if ((status & 1) == 0) { fprintf(stderr, "getdvi error: %d\n", status); - return status; } - if ( (devchar & DEV$M_DIR) == 0 ) return 1; - /* - * Build File Information Block and Atrribute block. - */ -#ifdef __ALPHA - fib.fib$w_fid[0] = statbuf->st_ino[0]; - fib.fib$w_fid[1] = statbuf->st_ino[1]; - fib.fib$w_fid[2] = statbuf->st_ino[2]; -#else - fib.fib$r_fid_overlay.fib$w_fid[0] = statbuf->st_ino[0]; - fib.fib$r_fid_overlay.fib$w_fid[1] = statbuf->st_ino[1]; - fib.fib$r_fid_overlay.fib$w_fid[2] = statbuf->st_ino[2]; -#endif - - atr[0].atr$w_size = sizeof(rat); - atr[0].atr$w_type = ATR$C_RECATTR; - atr[0].atr$l_addr = &rat; - atr[1].atr$w_size = 0; atr[1].atr$w_type = 0; - /* - * Perform access function with read_attributes sub-function. - */ - freopen ( "SYS$OUTPUT:", "a", stdout ); - fib_desc[0] = 10; fib_desc[1] = (long int) &fib; -#ifdef __ALPHA - fib.fib$l_acctl = FIB$M_WRITE; -#else - fib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_WRITE; -#endif - status = sys$qiow ( 0, channel, IO$_ACCESS|IO$M_ACCESS, - &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 ); - /* - * If status successful, update record byte and perform a MODIFY. - */ - if ( (status&1) == 1 ) status = iosb[0]; - if ( (status&1) == 1 ) { - rat.rtype = 1; /* fixed length records */ - rat.rsize = 512; /* 512 byte block recrods */ - rat.ratattrib = 0; /* Record attributes: none */ - - status = sys$qiow - ( 0, channel, IO$_MODIFY, &iosb, 0, 0, &fib_desc, 0, 0, 0, &atr, 0 ); - if ( (status&1) == 1 ) status = iosb[0]; - } - sys$dassgn ( channel ); - if ( (status & 1) == 0 ) fprintf ( stderr, - "Failed to convert output file to binary format, status: %d\n", status); - return status; -} - - -static add_item(head, tail, value, count) -struct list_item **head; -struct list_item **tail; -char *value; -int *count; -{ - if (*head == 0) - { - if (NULL == (*head = calloc(1, sizeof(**head)))) - { - errno = ENOMEM; - perror(""); - exit(EXIT_ERR); - } - *tail = *head; - } - else - if (NULL == ((*tail)->next = calloc(1, sizeof(**head)))) - { - errno = ENOMEM; - perror(""); - exit(EXIT_ERR); - } - else - *tail = (*tail)->next; - (*tail)->value = value; - ++(*count); -} - -static expand_wild_cards(item, head, tail, count) -char *item; -struct ltem_list **head; -struct ltem_list **tail; -int *count; -{ -int expcount = 0; -int context = 0; -int status; -int status_value; -int had_version; -$DESCRIPTOR(filespec, item); -$DESCRIPTOR(defaultspec, "SYS$DISK:[]*.*;"); -$DESCRIPTOR(resultspec, ""); - - if (strcspn(item, "*%") == strlen(item)) - { - add_item(head, tail, item, count); - return; - } - resultspec.dsc$b_dtype = DSC$K_DTYPE_T; - resultspec.dsc$b_class = DSC$K_CLASS_D; - resultspec.dsc$a_pointer = NULL; - filespec.dsc$w_length = strlen(item); - /* - * Only return version specs, if the caller specified a version - */ - had_version = strchr(item, ';'); - while (1 == (1&lib$find_file(&filespec, &resultspec, &context, - &defaultspec, 0, &status_value, &0))) - { - char *string; - char *c; - - if (NULL == (string = calloc(1, resultspec.dsc$w_length+1))) - { - errno = ENOMEM; - perror(""); - exit(EXIT_ERR); - } - strncpy(string, resultspec.dsc$a_pointer, resultspec.dsc$w_length); - string[resultspec.dsc$w_length] = '\0'; - if (NULL == had_version) - *((char *)strrchr(string, ';')) = '\0'; - /* - * Be consistent with what the C RTL has already done to the rest of - * the argv items and lowercase all of these names. - */ - for (c = string; *c; ++c) - if (isupper(*c)) - *c = tolower(*c); - add_item(head, tail, string, count); - ++expcount; - } - if (expcount == 0) - add_item(head, tail, item, count); - lib$sfree1_dd(&resultspec); - lib$find_file_end(&context); -} - -static int child_st[2]; /* Event Flag set when child process completes */ - -static short child_chan;/* I/O Channel for Pipe Mailbox */ - -static exit_handler(status) -int *status; -{ -short iosb[4]; - - if (0 == child_st[0]) - { -#ifdef DEBUG - fprintf(stderr, "Waiting for Child Process to Finnish . . .\n"); -#endif - fflush(stdout); /* Have to flush pipe for binary data to */ - /* terminate properly -- <tp@mccall.com> */ -#ifdef DEBUG - fprintf(stderr, " stdout flushed. . .\n"); -#endif - sys$qio(0, child_chan, IO$_WRITEOF, iosb, 0, 0, 0, 0, 0, 0, 0, 0); -#ifdef DEBUG - fprintf(stderr, " Pipe terminated. . .\n"); -#endif - fclose(stdout); -#ifdef DEBUG - fprintf(stderr, " stdout closed. . .\n"); -#endif - sys$synch(0, child_st); - sys$dassgn(child_chan); - } -#ifdef DEBUG - fprintf(stderr, " sync done. . .\n"); -#endif -} - -#include <syidef> /* System Information Definitions */ - -static sig_child(chan) -int chan; -{ -#ifdef DEBUG - fprintf(stderr, "Child Completion AST, st: %x\n", child_st[0] ); -#endif - if (child_st[0] == 0) - { child_st[0] = 1; } - sys$setef ( 0 ); -} - -static struct exit_control_block - { - struct exit_control_block *flink; - int (*exit_routine)(); - int arg_count; - int *status_address; - int exit_status; - } exit_block = - { - 0, - exit_handler, - 1, - &exit_block.exit_status, - 0 - }; - -static char *pipe_and_fork(cmd) -char *cmd; -{ - $DESCRIPTOR(cmddsc, cmd); - static char mbxname[64], ef = 0; - $DESCRIPTOR(mbxdsc, mbxname); - short iosb[4]; - int status; - int pid; - struct - { - short dna_buflen; - short dna_itmcod; - char *dna_buffer; - short *dna_retlen; - int listend; - } itmlst = - { - sizeof(mbxname), - DVI$_DEVNAM, - mbxname, - &mbxdsc.dsc$w_length, - 0 - }; - int mbxsize; - struct - { - short mbf_buflen; - short mbf_itmcod; - int *mbf_maxbuf; - short *mbf_retlen; - int listend; - } syiitmlst = - { - sizeof(mbxsize), - SYI$_MAXBUF, - &mbxsize, - 0, - 0 - }; - - cmddsc.dsc$w_length = strlen(cmd); - /* - * Get the SYSGEN parameter MAXBUF, and the smaller of it and 2048 as - * the size of the 'pipe' mailbox. - */ - if (1 == (1&(vaxc$errno = sys$getsyiw(0, 0, 0, &syiitmlst, iosb, 0, 0, 0)))) - vaxc$errno = iosb[0]; - if (0 == (1&vaxc$errno)) - { - errno = EVMSERR; - perror("Can't get SYSGEN parameter value for MAXBUF"); - exit(EXIT_ERR); - } - if (mbxsize > 2048) - mbxsize = 2048; - if (0 == (1&(vaxc$errno = sys$crembx(0, &child_chan, mbxsize, mbxsize, 0, 0, 0)))) - { - errno = EVMSERR; - perror("Can't create pipe mailbox"); - exit(EXIT_ERR); - } - if (1 == (1&(vaxc$errno = sys$getdviw(0, child_chan, 0, &itmlst, iosb, - 0, 0, 0)))) - vaxc$errno = iosb[0]; - if (0 == (1&vaxc$errno)) - { - errno = EVMSERR; - perror("Can't get pipe mailbox device name"); - exit(EXIT_ERR); - } - mbxname[mbxdsc.dsc$w_length] = '\0'; -#ifdef DEBUG - fprintf(stderr, "Pipe Mailbox Name = '%s'\n", mbxname); -#endif - if (0 == (1&(vaxc$errno = lib$spawn(&cmddsc, &mbxdsc, 0, &1, - 0, &pid, child_st, &ef, sig_child, - &child_chan)))) - { - errno = EVMSERR; - perror("Can't spawn subprocess"); - exit(EXIT_ERR); - } -#ifdef DEBUG - fprintf(stderr, "Subprocess's Pid = %08X\n", pid); -#endif - sys$dclexh(&exit_block); - return(mbxname); -} - -background_process(argc, argv) -int argc; -char **argv; -{ -char command[2048] = "$"; -$DESCRIPTOR(value, command); -$DESCRIPTOR(cmd, "BACKGROUND$COMMAND"); -$DESCRIPTOR(null, "NLA0:"); -int pid; - - strcat(command, argv[0]); - while (--argc) - { - strcat(command, " \""); - strcat(command, *(++argv)); - strcat(command, "\""); - } - value.dsc$w_length = strlen(command); - if (0 == (1&(vaxc$errno = lib$set_symbol(&cmd, &value)))) - { - errno = EVMSERR; - perror("Can't create symbol for subprocess command"); - exit(EXIT_ERR); - } - if (0 == (1&(vaxc$errno = lib$spawn(&cmd, &null, 0, &17, 0, &pid)))) - { - errno = EVMSERR; - perror("Can't spawn subprocess"); - exit(EXIT_ERR); - } -#ifdef DEBUG - fprintf(stderr, "%s\n", command); -#endif - fprintf(stderr, "%08X\n", pid); - return(EXIT_OK); -} - -/* got this off net.sources */ - -#ifdef VMS -#define index strchr -#endif /*VMS*/ - -/* - * get option letter from argument vector - */ -int opterr = 1, /* useless, never set or used */ - optind = 1, /* index into parent argv vector */ - optopt; /* character checked for validity */ -char *optarg; /* argument associated with option */ - -#define BADCH (int)'?' -#define EMSG "" -#define tell(s) fputs(progname,stderr);fputs(s,stderr); \ - fputc(optopt,stderr);fputc('\n',stderr);return(BADCH); - -getopt(nargc,nargv,ostr) -int nargc; -char **nargv, - *ostr; -{ - static char *place = EMSG; /* option letter processing */ - register char *oli; /* option letter list index */ - char *index(); - char *progname; - - if(!*place) { /* update scanning pointer */ - if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF); - if (*place == '-') { /* found "--" */ - ++optind; - return(EOF); - } - } /* option letter okay? */ - if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) { - if(!*place) ++optind; -#ifdef VMS - progname = strrchr(nargv[0],']'); -#else - progname = rindex(nargv[0],'/'); -#endif - if (!progname) progname = nargv[0]; else progname++; - tell(": illegal option -- "); - } - if (*++oli != ':') { /* don't need argument */ - optarg = NULL; - if (!*place) ++optind; - } - else { /* need an argument */ - if (*place) optarg = place; /* no white space */ - else if (nargc <= ++optind) { /* no arg */ - place = EMSG; -#ifdef VMS - progname = strrchr(nargv[0],']'); -#else - progname = rindex(nargv[0],'/'); -#endif - if (!progname) progname = nargv[0]; else progname++; - tell(": option requires an argument -- "); - } - else optarg = nargv[optind]; /* white space */ - place = EMSG; - ++optind; - } - return(optopt); /* dump back option letter */ -} diff --git a/lib/libpgm1.c b/lib/libpgm1.c index 4d93e4be..9ef5d2c1 100644 --- a/lib/libpgm1.c +++ b/lib/libpgm1.c @@ -20,6 +20,9 @@ #include <stdio.h> #include <errno.h> +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + #include "pgm.h" #include "libpgm.h" #include "pbm.h" @@ -27,8 +30,6 @@ #include "pam.h" #include "libpam.h" #include "fileio.h" -#include "mallocvar.h" -#include "nstring.h" gray * @@ -100,10 +101,13 @@ validateComputableSize(unsigned int const cols, you expect. That failed expectation can be disastrous if you use it to allocate memory. + It is very normal to allocate space for a pixel row, so we make sure + the size of a pixel row, in bytes, can be represented by an 'int'. + A common operation is adding 1 or 2 to the highest row or column number in the image, so we make sure that's possible. -----------------------------------------------------------------------------*/ - if (cols > INT_MAX - 2) + if (cols > INT_MAX / (sizeof(gray)) || cols > INT_MAX - 2) pm_error("image width (%u) too large to be processed", cols); if (rows > INT_MAX - 2) pm_error("image height (%u) too large to be processed", rows); @@ -164,7 +168,8 @@ pgm_readpgminit(FILE * const fileP, break; default: - pm_error("bad magic number - not a Netpbm file"); + pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file", + realFormat); } validateComputableSize(*colsP, *rowsP); } @@ -172,11 +177,44 @@ pgm_readpgminit(FILE * const fileP, static void +validateRpgmRow(gray * const grayrow, + unsigned int const cols, + gray const maxval, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Check for sample values above maxval in input. + + Note: a program that wants to deal with invalid sample values itself can + simply make sure it uses a sufficiently high maxval on the read function + call, so this validation never fails. +-----------------------------------------------------------------------------*/ + if (maxval == 255 || maxval == 65535) { + /* There's no way a sample can be invalid, so we don't need to look at + the samples individually. + */ + *errorP = NULL; + } else { + unsigned int col; + for (col = 0; col < cols; ++col) { + if (grayrow[col] > maxval) { + pm_asprintf(errorP, + "gray value %u is greater than maxval (%u)", + grayrow[col], maxval); + return; + } + } + *errorP = NULL; + } +} + + + +static void readRpgmRow(FILE * const fileP, - gray * const grayrow, - int const cols, - gray const maxval, - int const format) { + gray * const grayrow, + int const cols, + gray const maxval, + int const format) { unsigned int const bytesPerSample = maxval < 256 ? 1 : 2; int const bytesPerRow = cols * bytesPerSample; @@ -186,19 +224,18 @@ readRpgmRow(FILE * const fileP, MALLOCARRAY(rowBuffer, bytesPerRow); if (rowBuffer == NULL) - asprintfN(&error, "Unable to allocate memory for row buffer " - "for %u columns", cols); + pm_asprintf(&error, "Unable to allocate memory for row buffer " + "for %u columns", cols); else { - ssize_t rc; + size_t rc; rc = fread(rowBuffer, 1, bytesPerRow, fileP); if (rc == 0) - asprintfN(&error, "Error reading row. fread() errno=%d (%s)", - errno, strerror(errno)); + pm_asprintf(&error, "Error reading row. fread() errno=%d (%s)", + errno, strerror(errno)); else if (rc != bytesPerRow) - asprintfN(&error, "Error reading row. Short read of %u bytes " - "instead of %u", rc, bytesPerRow); + pm_asprintf(&error, "Error reading row. Short read of %u bytes " + "instead of %u", (unsigned)rc, bytesPerRow); else { - error = NULL; if (maxval < 256) { unsigned int col; for (col = 0; col < cols; ++col) @@ -218,12 +255,13 @@ readRpgmRow(FILE * const fileP, grayrow[col] = g; } } + validateRpgmRow(grayrow, cols, maxval, &error); } free(rowBuffer); } if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } } @@ -241,7 +279,7 @@ readPbmRow(FILE * const fileP, jmp_buf * origJmpbufP; bit * bitrow; - bitrow = pbm_allocrow(cols); + bitrow = pbm_allocrow_packed(cols); if (setjmp(jmpbuf) != 0) { pbm_freerow(bitrow); pm_setjmpbuf(origJmpbufP); @@ -251,10 +289,14 @@ readPbmRow(FILE * const fileP, pm_setjmpbufsave(&jmpbuf, &origJmpbufP); - pbm_readpbmrow(fileP, bitrow, cols, format); - for (col = 0; col < cols; ++col) - grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0; + pbm_readpbmrow_packed(fileP, bitrow, cols, format); + for (col = 0; col < cols; ++col) { + grayrow[col] = + ((bitrow[col/8] >> (7 - col%8)) & 0x1) == PBM_WHITE ? + maxval : 0 + ; + } pm_setjmpbuf(origJmpbufP); } pbm_freerow(bitrow); @@ -338,28 +380,30 @@ pgm_readpgm(FILE * const fileP, void pgm_check(FILE * const file, - enum pm_check_type const check_type, + enum pm_check_type const checkType, int const format, int const cols, int const rows, gray const maxval, - enum pm_check_code * const retval_p) { + enum pm_check_code * const retvalP) { if (rows < 0) pm_error("Invalid number of rows passed to pgm_check(): %d", rows); if (cols < 0) pm_error("Invalid number of columns passed to pgm_check(): %d", cols); - if (check_type != PM_CHECK_BASIC) { - if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE; + if (checkType != PM_CHECK_BASIC) { + if (retvalP) + *retvalP = PM_CHECK_UNKNOWN_TYPE; } else if (PGM_FORMAT_TYPE(format) == PBM_TYPE) { - pbm_check(file, check_type, format, cols, rows, retval_p); + pbm_check(file, checkType, format, cols, rows, retvalP); } else if (format != RPGM_FORMAT) { - if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE; + if (retvalP) + *retvalP = PM_CHECK_UNCHECKABLE; } else { - pm_filepos const bytes_per_row = cols * (maxval > 255 ? 2 : 1); - pm_filepos const need_raster_size = rows * bytes_per_row; + pm_filepos const bytesPerRow = cols * (maxval > 255 ? 2 : 1); + pm_filepos const needRasterSize = rows * bytesPerRow; - pm_check(file, check_type, need_raster_size, retval_p); + pm_check(file, checkType, needRasterSize, retvalP); } } diff --git a/lib/libpgm2.c b/lib/libpgm2.c index 650d2cb5..80b5cf42 100644 --- a/lib/libpgm2.c +++ b/lib/libpgm2.c @@ -13,8 +13,8 @@ #include <string.h> #include <errno.h> -#include "pm_c_util.h" -#include "mallocvar.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" #include "pgm.h" @@ -37,10 +37,6 @@ pgm_writepgminit(FILE * const fileP, PGM_MAGIC1, plainFormat || maxval >= 1<<16 ? PGM_MAGIC2 : RPGM_MAGIC2, cols, rows, maxval ); -#ifdef VMS - if (!plainFormat) - set_outfile_binary(); -#endif } @@ -125,10 +121,12 @@ writepgmrowraw(FILE * const fileP, if (rc < 0) pm_error("Error writing row. fwrite() errno=%d (%s)", errno, strerror(errno)); - else if (rc != bytesPerRow) - pm_error("Error writing row. Short write of %u bytes " - "instead of %u", rc, bytesPerRow); - + else { + size_t const bytesWritten = rc; + if (bytesWritten != bytesPerRow) + pm_error("Error writing row. Short write of %u bytes " + "instead of %u", (unsigned)bytesWritten, bytesPerRow); + } free(rowBuffer); } diff --git a/lib/libpm.c b/lib/libpm.c index 910d5666..4374bbe3 100644 --- a/lib/libpm.c +++ b/lib/libpm.c @@ -8,8 +8,12 @@ Netpbm library subroutines. **************************************************************************/ +#define _BSD_SOURCE /* Make sure strdup is in string.h */ #define _XOPEN_SOURCE 500 /* Make sure ftello, fseeko are defined */ +#include "netpbm/pm_config.h" + +#include <assert.h> #include <unistd.h> #include <stdio.h> #include <stdarg.h> @@ -18,13 +22,17 @@ #include <setjmp.h> #include <time.h> #include <limits.h> +#if HAVE_FORK +#include <sys/wait.h> +#endif +#include <sys/types.h> -#include "pm_c_util.h" -#include "mallocvar.h" -#include "version.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/version.h" +#include "netpbm/nstring.h" +#include "netpbm/shhopt.h" #include "compile.h" -#include "nstring.h" -#include "shhopt.h" #include "pm.h" @@ -95,6 +103,86 @@ pm_longjmp(void) { void +pm_fork(int * const iAmParentP, + pid_t * const childPidP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Same as POSIX fork, except with a nicer interface and works + (fails cleanly) on systems that don't have POSIX fork(). +-----------------------------------------------------------------------------*/ +#if HAVE_FORK + int rc; + + rc = fork(); + + if (rc < 0) { + pm_asprintf(errorP, "Failed to fork a process. errno=%d (%s)", + errno, strerror(errno)); + } else { + *errorP = NULL; + + if (rc == 0) { + *iAmParentP = FALSE; + } else { + *iAmParentP = TRUE; + *childPidP = rc; + } + } +#else + pm_asprintf(errorP, "Cannot fork a process, because this system does " + "not have POSIX fork()"); +#endif +} + + + +void +pm_waitpid(pid_t const pid, + int * const statusP, + int const options, + pid_t * const exitedPidP, + const char ** const errorP) { + +#if HAVE_FORK + pid_t rc; + rc = waitpid(pid, statusP, options); + if (rc == (pid_t)-1) { + pm_asprintf(errorP, "Failed to wait for process exit. " + "waitpid() errno = %d (%s)", + errno, strerror(errno)); + } else { + *exitedPidP = rc; + *errorP = NULL; + } +#else + pm_error("INTERNAL ERROR: Attempt to wait for a process we created on " + "a system on which we can't create processes"); +#endif +} + + + +void +pm_waitpidSimple(pid_t const pid) { + + int status; + pid_t exitedPid; + const char * error; + + pm_waitpid(pid, &status, 0, &exitedPid, &error); + + if (error) { + pm_errormsg("%s", error); + pm_strfree(error); + pm_longjmp(); + } else { + assert(exitedPid != 0); + } +} + + + +void pm_setusererrormsgfn(pm_usererrormsgfn * fn) { userErrorMsgFn = fn; @@ -126,14 +214,14 @@ pm_message(const char format[], ...) { if (pm_showmessages) { const char * msg; - vasprintfN(&msg, format, args); + pm_vasprintf(&msg, format, args); if (userMessageFn) userMessageFn(msg); else fprintf(stderr, "%s: %s\n", pm_progname, msg); - strfree(msg); + pm_strfree(msg); } va_end(args); } @@ -159,11 +247,11 @@ pm_errormsg(const char format[], ...) { va_start(args, format); - vasprintfN(&msg, format, args); + pm_vasprintf(&msg, format, args); errormsg(msg); - strfree(msg); + pm_strfree(msg); va_end(args); } @@ -177,11 +265,11 @@ pm_error(const char format[], ...) { va_start(args, format); - vasprintfN(&msg, format, args); + pm_vasprintf(&msg, format, args); errormsg(msg); - strfree(msg); + pm_strfree(msg); va_end(args); @@ -190,6 +278,20 @@ pm_error(const char format[], ...) { +int +pm_have_float_format(void) { +/*---------------------------------------------------------------------------- + Return 1 if %f, %e, and %g work in format strings for pm_message, etc.; + 0 otherwise. + + Where they don't "work," that means the specifier just appears itself in + the formatted strings, e.g. "the number is g". +-----------------------------------------------------------------------------*/ + return pm_vasprintf_knows_float(); +} + + + static void * mallocz(size_t const size) { @@ -224,122 +326,30 @@ pm_freerow(void * const itrow) { -static void -allocarrayNoHeap(unsigned char ** const rowIndex, - unsigned int const cols, - unsigned int const rows, - unsigned int const size, - const char ** const errorP) { - - if (cols != 0 && UINT_MAX / cols < size) - asprintfN(errorP, - "Arithmetic overflow multiplying %u by %u to get the " - "size of a row to allocate.", cols, size); - else { - unsigned int rowsDone; - - rowsDone = 0; - *errorP = NULL; - - while (rowsDone < rows && !*errorP) { - unsigned char * const rowSpace = mallocz(cols * size); - if (rowSpace == NULL) - asprintfN(errorP, - "Unable to allocate a %u-column by %u byte row", - cols, size); - else - rowIndex[rowsDone++] = rowSpace; - } - if (*errorP) { - unsigned int row; - for (row = 0; row < rowsDone; ++row) - free(rowIndex[row]); - } - } -} - - - -static unsigned char * -allocRowHeap(unsigned int const cols, - unsigned int const rows, - unsigned int const size) { - - unsigned char * retval; - - if (cols != 0 && rows != 0 && UINT_MAX / cols / rows < size) - /* Too big even to request the memory ! */ - retval = NULL; - else - retval = mallocz(rows * cols * size); - - return retval; -} - - - char ** pm_allocarray(int const cols, int const rows, - int const size ) { + int const elementSize ) { /*---------------------------------------------------------------------------- - Allocate an array of 'rows' rows of 'cols' columns each, with each - element 'size' bytes. - - We use a special format where we tack on an extra element to the row - index to indicate the format of the array. - - We have two ways of allocating the space: fragmented and - unfragmented. In both, the row index (plus the extra element) is - in one block of memory. In the fragmented format, each row is - also in an independent memory block, and the extra row pointer is - NULL. In the unfragmented format, all the rows are in a single - block of memory called the row heap and the extra row pointer is - the address of that block. - - We use unfragmented format if possible, but if the allocation of the - row heap fails, we fall back to fragmented. ------------------------------------------------------------------------------*/ - unsigned char ** rowIndex; - const char * error; + This is for backward compatibility. MALLOCARRAY2 is usually better. - MALLOCARRAY(rowIndex, rows + 1); - if (rowIndex == NULL) - asprintfN(&error, - "out of memory allocating row index (%u rows) for an array", - rows); - else { - unsigned char * rowheap; + A problem with pm_allocarray() is that its return type is char ** + even though 'elementSize' can be other than 1. So users have + traditionally type cast the result. In the old days, that was just + messy; modern compilers can produce the wrong code if you do that. +-----------------------------------------------------------------------------*/ + char ** retval; + void * result; - rowheap = allocRowHeap(cols, rows, size); + pm_mallocarray2(&result, rows, cols, elementSize); - if (rowheap) { - /* It's unfragmented format */ + if (result == NULL) + pm_error("Failed to allocate a raster array of %u columns x %u rows", + cols, rows); - rowIndex[rows] = rowheap; /* Declare it unfragmented format */ + retval = result; - if (rowheap) { - unsigned int row; - - for (row = 0; row < rows; ++row) - rowIndex[row] = &(rowheap[row * cols * size]); - } - error = NULL; - } else { - /* We couldn't get the whole heap in one block, so try fragmented - format. - */ - rowIndex[rows] = NULL; /* Declare it fragmented format */ - - allocarrayNoHeap(rowIndex, cols, rows, size, &error); - } - } - if (error) { - pm_errormsg("Couldn't allocate %u-row array. %s", rows, error); - strfree(error); - pm_longjmp(); - } - return (char **)rowIndex; + return retval; } @@ -348,16 +358,9 @@ void pm_freearray(char ** const rowIndex, int const rows) { - void * const rowheap = rowIndex[rows]; + void * const rowIndexVoid = rowIndex; - if (rowheap != NULL) - free(rowheap); - else { - unsigned int row; - for (row = 0; row < rows; ++row) - pm_freerow(rowIndex[row]); - } - free(rowIndex); + pm_freearray2(rowIndexVoid); } @@ -475,37 +478,6 @@ pm_lcm(unsigned int const x, } -/* Initialization. */ - - -#ifdef VMS -static const char * -vmsProgname(int * const argcP, char * argv[]) { - char **temp_argv = argv; - int old_argc = *argcP; - int i; - const char * retval; - - getredirection( argcP, &temp_argv ); - if (*argcP > old_argc) { - /* Number of command line arguments has increased */ - fprintf( stderr, "Sorry!! getredirection() for VMS has " - "changed the argument list!!!\n"); - fprintf( stderr, "This is intolerable at the present time, " - "so we must stop!!!\n"); - exit(1); - } - for (i=0; i<*argcP; i++) - argv[i] = temp_argv[i]; - retval = strrchr( argv[0], '/'); - if ( retval == NULL ) retval = rindex( argv[0], ']'); - if ( retval == NULL ) retval = rindex( argv[0], '>'); - - return retval; -} -#endif - - void pm_init(const char * const progname, @@ -555,11 +527,7 @@ showVersion(void) { pm_message( "BSD defined" ); #endif /*BSD*/ #ifdef SYSV -#ifdef VMS - pm_message( "VMS & SYSV defined" ); -#else pm_message( "SYSV defined" ); -#endif #endif /*SYSV*/ #ifdef MSDOS pm_message( "MSDOS defined" ); @@ -626,6 +594,8 @@ showNetpbmHelp(const char progname[]) { if (docurl == NULL) pm_message("No 'docurl=' line in Netpbm configuration file '%s'.", netpbmConfigFileName); + + fclose(netpbmConfigFile); } if (docurl == NULL) pm_message("We have no reliable indication of where the Netpbm " @@ -639,71 +609,85 @@ showNetpbmHelp(const char progname[]) { +static void +parseCommonOptions(int * const argcP, + const char ** const argv, + bool * const showMessagesP, + bool * const showVersionP, + bool * const showHelpP, + bool * const plainOutputP) { + + unsigned int inCursor; + unsigned int outCursor; + + *showMessagesP = true; /* initial assumption */ + *showVersionP = false; /* initial assumption */ + *showHelpP = false; /* initial assumption */ + *plainOutputP = false; /* initial assumption */ + + for (inCursor = 1, outCursor = 1; inCursor < *argcP; ++inCursor) { + if (strcaseeq(argv[inCursor], "-quiet") || + strcaseeq(argv[inCursor], "--quiet")) + *showMessagesP = false; + else if (strcaseeq(argv[inCursor], "-version") || + strcaseeq(argv[inCursor], "--version")) + *showVersionP = true; + else if (strcaseeq(argv[inCursor], "-help") || + strcaseeq(argv[inCursor], "--help") || + strcaseeq(argv[inCursor], "-?")) + *showHelpP = true; + else if (strcaseeq(argv[inCursor], "-plain") || + strcaseeq(argv[inCursor], "--plain")) + *plainOutputP = true; + else + argv[outCursor++] = argv[inCursor]; + } + *argcP = outCursor; +} + + + void -pm_proginit(int * const argcP, const char * argv[]) { +pm_proginit(int * const argcP, + const char ** const argv) { /*---------------------------------------------------------------------------- Do various initialization things that all programs in the Netpbm package, and programs that emulate such programs, should do. - This includes processing global options. + This includes processing global options. We scan argv[], which has *argcP + elements, for common options and execute the functions for the ones we + find. We remove the common options from argv[], updating *argcP + accordingly. This includes calling pm_init() to initialize the Netpbm libraries. -----------------------------------------------------------------------------*/ - int argn, i; - const char * progname; - bool showmessages; - bool show_version; + const char * const progname = pm_arg0toprogname(argv[0]); + /* points to static memory in this library */ + bool showMessages; + bool plain; + bool justShowVersion; /* We're supposed to just show the version information, then exit the program. */ - bool show_help; + bool justShowHelp; /* We're supposed to just tell user where to get help, then exit the program. */ - - /* Extract program name. */ -#ifdef VMS - progname = vmsProgname(argcP, argv); -#else - progname = strrchr( argv[0], '/'); -#endif - if (progname == NULL) - progname = argv[0]; - else - ++progname; pm_init(progname, 0); - /* Check for any global args. */ - showmessages = TRUE; - show_version = FALSE; - show_help = FALSE; - pm_plain_output = FALSE; - for (argn = i = 1; argn < *argcP; ++argn) { - if (pm_keymatch(argv[argn], "-quiet", 6) || - pm_keymatch(argv[argn], "--quiet", 7)) - showmessages = FALSE; - else if (pm_keymatch(argv[argn], "-version", 8) || - pm_keymatch(argv[argn], "--version", 9)) - show_version = TRUE; - else if (pm_keymatch(argv[argn], "-help", 5) || - pm_keymatch(argv[argn], "--help", 6) || - pm_keymatch(argv[argn], "-?", 2)) - show_help = TRUE; - else if (pm_keymatch(argv[argn], "-plain", 6) || - pm_keymatch(argv[argn], "--plain", 7)) - pm_plain_output = TRUE; - else - argv[i++] = argv[argn]; - } - *argcP=i; + parseCommonOptions(argcP, argv, + &showMessages, &justShowVersion, &justShowHelp, + &plain); - pm_setMessage((unsigned int) showmessages, NULL); + pm_plain_output = plain ? 1 : 0; - if (show_version) { + pm_setMessage(showMessages ? 1 : 0, NULL); + + if (justShowVersion) { showVersion(); - exit( 0 ); - } else if (show_help) { + exit(0); + } else if (justShowHelp) { pm_error("Use 'man %s' for help.", progname); /* If we can figure out a way to distinguish Netpbm programs from other programs using the Netpbm libraries, we can do better here. @@ -715,8 +699,10 @@ pm_proginit(int * const argcP, const char * argv[]) { } + void -pm_setMessage(int const newState, int * const oldStateP) { +pm_setMessage(int const newState, + int * const oldStateP) { if (oldStateP) *oldStateP = pm_showmessages; @@ -726,6 +712,39 @@ pm_setMessage(int const newState, int * const oldStateP) { +int +pm_getMessage(void) { + + return pm_showmessages; +} + + + +static void +extractAfterLastSlash(const char * const fullPath, + char * const retval, + size_t const retvalSize) { + + char * slashPos; + + /* Chop any directories off the left end */ + slashPos = strrchr(fullPath, '/'); + + if (slashPos == NULL) { + strncpy(retval, fullPath, retvalSize); + retval[retvalSize-1] = '\0'; + } else { + strncpy(retval, slashPos +1, retvalSize); + retval[retvalSize-1] = '\0'; + } + + /* Chop any .exe off the right end */ + if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0) + retval[strlen(retval)-4] = 0; +} + + + char * pm_arg0toprogname(const char arg0[]) { /*---------------------------------------------------------------------------- @@ -742,25 +761,23 @@ pm_arg0toprogname(const char arg0[]) { The return value is in static storage within. It is NUL-terminated, but truncated at 64 characters. -----------------------------------------------------------------------------*/ - static char retval[64+1]; - char *slash_pos; - - /* Chop any directories off the left end */ - slash_pos = strrchr(arg0, '/'); - - if (slash_pos == NULL) { - strncpy(retval, arg0, sizeof(retval)); - retval[sizeof(retval)-1] = '\0'; - } else { - strncpy(retval, slash_pos +1, sizeof(retval)); - retval[sizeof(retval)-1] = '\0'; - } - - /* Chop any .exe off the right end */ - if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0) - retval[strlen(retval)-4] = 0; +#define MAX_RETVAL_SIZE 64 +#if MSVCRT + /* Note that there exists _splitpath_s, which takes a size argument, + but it is only in "secure" extensions of MSVCRT that come only with + MSVC; but _splitpath() comes with Windows. MinGW has a header file + for it. + */ + static char retval[_MAX_FNAME]; + _splitpath(arg0, 0, 0, retval, 0); + if (MAX_RETVAL_SIZE < _MAX_FNAME) + retval[MAX_RETVAL_SIZE] = '\0'; +#else + static char retval[MAX_RETVAL_SIZE+1]; + extractAfterLastSlash(arg0, retval, sizeof(retval)); +#endif - return(retval); + return retval; } @@ -784,11 +801,11 @@ pm_parse_width(const char * const arg) { unsigned int width; const char * error; - interpret_uint(arg, &width, &error); + pm_interpret_uint(arg, &width, &error); if (error) { pm_error("'%s' is invalid as an image width. %s", arg, error); - strfree(error); + pm_strfree(error); } else { if (width > INT_MAX-10) pm_error("Width %u is too large for computations.", width); @@ -809,11 +826,11 @@ pm_parse_height(const char * const arg) { unsigned int height; const char * error; - interpret_uint(arg, &height, &error); + pm_interpret_uint(arg, &height, &error); if (error) { pm_error("'%s' is invalid as an image height. %s", arg, error); - strfree(error); + pm_strfree(error); } else { if (height > INT_MAX-10) pm_error("Height %u is too large for computations.", height); diff --git a/lib/libpnm1.c b/lib/libpnm1.c index 536e5dc4..db21b078 100644 --- a/lib/libpnm1.c +++ b/lib/libpnm1.c @@ -13,6 +13,8 @@ #include <string.h> #include <errno.h> +#include "netpbm/mallocvar.h" + #include "pnm.h" #include "ppm.h" @@ -27,8 +29,6 @@ #include "pam.h" #include "libpam.h" -#include "mallocvar.h" - xel * @@ -47,15 +47,12 @@ pnm_allocrow(unsigned int const cols) { void -pnm_init( argcP, argv ) - int* argcP; - char* argv[]; - { +pnm_init(int * const argcP, char ** const argv) { ppm_init( argcP, argv ); - } +} void -pnm_nextimage(FILE *file, int * const eofP) { +pnm_nextimage(FILE * const file, int * const eofP) { pm_nextimage(file, eofP); } @@ -72,10 +69,13 @@ validateComputableSize(unsigned int const cols, you expect. That failed expectation can be disastrous if you use it to allocate memory. + It is very normal to allocate space for a pixel row, so we make sure + the size of a pixel row, in bytes, can be represented by an 'int'. + A common operation is adding 1 or 2 to the highest row or column number in the image, so we make sure that's possible. -----------------------------------------------------------------------------*/ - if (cols > INT_MAX - 2) + if (cols > INT_MAX/(sizeof(pixval) * 3) || cols > INT_MAX - 2) pm_error("image width (%u) too large to be processed", cols); if (rows > INT_MAX - 2) pm_error("image height (%u) too large to be processed", rows); @@ -126,7 +126,8 @@ pnm_readpnminit(FILE * const fileP, break; default: - pm_error("bad magic number - not a ppm, pgm, or pbm file"); + pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file", + realFormat); } validateComputableSize(*colsP, *rowsP); } @@ -178,10 +179,10 @@ readpbmrow(FILE * const fileP, jmp_buf * origJmpbufP; bit * bitrow; - bitrow = pbm_allocrow(cols); + bitrow = pbm_allocrow_packed(cols); if (setjmp(jmpbuf) != 0) { - pbm_freerow(bitrow); + pbm_freerow_packed(bitrow); pm_setjmpbuf(origJmpbufP); pm_longjmp(); } else { @@ -189,11 +190,14 @@ readpbmrow(FILE * const fileP, pm_setjmpbufsave(&jmpbuf, &origJmpbufP); - pbm_readpbmrow(fileP, bitrow, cols, format); - - for (col = 0; col < cols; ++col) - PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0 : maxval); + pbm_readpbmrow_packed(fileP, bitrow, cols, format); + for (col = 0; col < cols; ++col) { + pixval const g = + ((bitrow[col/8] >> (7 - col%8)) & 0x1) == PBM_WHITE ? + maxval : 0; + PNM_ASSIGN1(xelrow[col], g); + } pm_setjmpbuf(origJmpbufP); } pbm_freerow(bitrow); diff --git a/lib/libpnm2.c b/lib/libpnm2.c index 01ffa389..fa4bb8be 100644 --- a/lib/libpnm2.c +++ b/lib/libpnm2.c @@ -10,7 +10,7 @@ ** implied warranty. */ -#include "pm_c_util.h" +#include "netpbm/pm_c_util.h" #include "pnm.h" @@ -20,6 +20,8 @@ #include "pbm.h" + + void pnm_writepnminit(FILE * const fileP, int const cols, diff --git a/lib/libpnm3.c b/lib/libpnm3.c index c41a2108..0426ebcb 100644 --- a/lib/libpnm3.c +++ b/lib/libpnm3.c @@ -45,7 +45,7 @@ mean4(int const format, break; default: - pm_error( "Invalid format passed to pnm_backgroundxel()"); + pm_error("Invalid format passed to pnm_backgroundxel()"); } return retval; } diff --git a/lib/libppm1.c b/lib/libppm1.c index 97110730..c1eb152c 100644 --- a/lib/libppm1.c +++ b/lib/libppm1.c @@ -20,6 +20,8 @@ #include <stdio.h> #include <errno.h> +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" #include "ppm.h" #include "libppm.h" #include "pgm.h" @@ -29,8 +31,6 @@ #include "pam.h" #include "libpam.h" #include "fileio.h" -#include "mallocvar.h" -#include "nstring.h" pixel * @@ -49,12 +49,11 @@ ppm_allocrow(unsigned int const cols) { void -ppm_init( argcP, argv ) - int* argcP; - char* argv[]; - { +ppm_init(int * const argcP, char ** const argv) { pgm_init( argcP, argv ); - } +} + + void ppm_nextimage(FILE * const fileP, @@ -79,7 +78,7 @@ ppm_readppminitrest(FILE * const fileP, maxval = pm_getuint(fileP); if (maxval > PPM_OVERALLMAXVAL) pm_error("maxval of input image (%u) is too large. " - "The maximum allowed by the PPM is %u.", + "The maximum allowed by the PPM format is %u.", maxval, PPM_OVERALLMAXVAL); if (maxval == 0) pm_error("maxval of input image is zero."); @@ -100,10 +99,13 @@ validateComputableSize(unsigned int const cols, you expect. That failed expectation can be disastrous if you use it to allocate memory. + It is very normal to allocate space for a pixel row, so we make sure + the size of a pixel row, in bytes, can be represented by an 'int'. + A common operation is adding 1 or 2 to the highest row or column number in the image, so we make sure that's possible. -----------------------------------------------------------------------------*/ - if (cols > INT_MAX - 2) + if (cols > INT_MAX/(sizeof(pixval) * 3) || cols > INT_MAX - 2) pm_error("image width (%u) too large to be processed", cols); if (rows > INT_MAX - 2) pm_error("image height (%u) too large to be processed", rows); @@ -184,6 +186,92 @@ readPpmRow(FILE * const fileP, static void +interpRasterRowRaw(const unsigned char * const rowBuffer, + pixel * const pixelrow, + unsigned int const cols, + unsigned int const bytesPerSample) { + + unsigned int bufferCursor; + + bufferCursor = 0; /* start at beginning of rowBuffer[] */ + + if (bytesPerSample == 1) { + unsigned int col; + for (col = 0; col < cols; ++col) { + pixval const r = rowBuffer[bufferCursor++]; + pixval const g = rowBuffer[bufferCursor++]; + pixval const b = rowBuffer[bufferCursor++]; + PPM_ASSIGN(pixelrow[col], r, g, b); + } + } else { + /* two byte samples */ + unsigned int col; + for (col = 0; col < cols; ++col) { + pixval r, g, b; + + r = rowBuffer[bufferCursor++] << 8; + r |= rowBuffer[bufferCursor++]; + + g = rowBuffer[bufferCursor++] << 8; + g |= rowBuffer[bufferCursor++]; + + b = rowBuffer[bufferCursor++] << 8; + b |= rowBuffer[bufferCursor++]; + + PPM_ASSIGN(pixelrow[col], r, g, b); + } + } +} + + + +static void +validateRppmRow(pixel * const pixelrow, + unsigned int const cols, + pixval const maxval, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Check for sample values above maxval in input. + + Note: a program that wants to deal with invalid sample values itself can + simply make sure it uses a sufficiently high maxval on the read function + call, so this validation never fails. +-----------------------------------------------------------------------------*/ + if (maxval == 255 || maxval == 65535) { + /* There's no way a sample can be invalid, so we don't need to look at + the samples individually. + */ + *errorP = NULL; + } else { + unsigned int col; + + for (col = 0, *errorP = NULL; col < cols && !*errorP; ++col) { + pixval const r = PPM_GETR(pixelrow[col]); + pixval const g = PPM_GETG(pixelrow[col]); + pixval const b = PPM_GETB(pixelrow[col]); + + if (r > maxval) + pm_asprintf( + errorP, + "Red sample value %u is greater than maxval (%u)", + r, maxval); + else if (g > maxval) + pm_asprintf( + errorP, + "Green sample value %u is greater than maxval (%u)", + g, maxval); + else if (b > maxval) + pm_asprintf( + errorP, + "Blue sample value %u is greater than maxval (%u)", + b, maxval); + } + } +} + + + +static void readRppmRow(FILE * const fileP, pixel * const pixelrow, unsigned int const cols, @@ -199,60 +287,35 @@ readRppmRow(FILE * const fileP, MALLOCARRAY(rowBuffer, bytesPerRow); if (rowBuffer == NULL) - asprintfN(&error, "Unable to allocate memory for row buffer " - "for %u columns", cols); + pm_asprintf(&error, "Unable to allocate memory for row buffer " + "for %u columns", cols); else { ssize_t rc; rc = fread(rowBuffer, 1, bytesPerRow, fileP); if (feof(fileP)) - asprintfN(&error, "Unexpected EOF reading row of PPM image."); + pm_asprintf(&error, "Unexpected EOF reading row of PPM image."); else if (ferror(fileP)) - asprintfN(&error, "Error reading row. fread() errno=%d (%s)", - errno, strerror(errno)); - else if (rc != bytesPerRow) - asprintfN(&error, "Error reading row. Short read of %u bytes " - "instead of %u", rc, bytesPerRow); + pm_asprintf(&error, "Error reading row. fread() errno=%d (%s)", + errno, strerror(errno)); else { - unsigned int bufferCursor; - - error = NULL; - - bufferCursor = 0; /* start at beginning of rowBuffer[] */ - - if (bytesPerSample == 1) { - unsigned int col; - for (col = 0; col < cols; ++col) { - pixval const r = rowBuffer[bufferCursor++]; - pixval const g = rowBuffer[bufferCursor++]; - pixval const b = rowBuffer[bufferCursor++]; - PPM_ASSIGN(pixelrow[col], r, g, b); - } - } else { - /* two byte samples */ - unsigned int col; - for (col = 0; col < cols; ++col) { - pixval r, g, b; - - r = rowBuffer[bufferCursor++] << 8; - r |= rowBuffer[bufferCursor++]; - - g = rowBuffer[bufferCursor++] << 8; - g |= rowBuffer[bufferCursor++]; - - b = rowBuffer[bufferCursor++] << 8; - b |= rowBuffer[bufferCursor++]; - - PPM_ASSIGN(pixelrow[col], r, g, b); - } + size_t const bytesRead = rc; + if (bytesRead != bytesPerRow) + pm_asprintf(&error, + "Error reading row. Short read of %u bytes " + "instead of %u", (unsigned)bytesRead, bytesPerRow); + else { + interpRasterRowRaw(rowBuffer, pixelrow, cols, bytesPerSample); + + validateRppmRow(pixelrow, cols, maxval, &error); } } free(rowBuffer); } if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } } @@ -305,10 +368,10 @@ readPbmRow(FILE * const fileP, jmp_buf * origJmpbufP; bit * bitrow; - bitrow = pbm_allocrow(cols); + bitrow = pbm_allocrow_packed(cols); if (setjmp(jmpbuf) != 0) { - pbm_freerow(bitrow); + pbm_freerow_packed(bitrow); pm_setjmpbuf(origJmpbufP); pm_longjmp(); } else { @@ -316,10 +379,12 @@ readPbmRow(FILE * const fileP, pm_setjmpbufsave(&jmpbuf, &origJmpbufP); - pbm_readpbmrow(fileP, bitrow, cols, format); + pbm_readpbmrow_packed(fileP, bitrow, cols, format); for (col = 0; col < cols; ++col) { - pixval const g = (bitrow[col] == PBM_WHITE) ? maxval : 0; + pixval const g = + ((bitrow[col/8] >> (7 - col%8)) & 0x1) == PBM_WHITE ? + maxval : 0; PPM_ASSIGN(pixelrow[col], g, g, g); } pm_setjmpbuf(origJmpbufP); @@ -408,30 +473,32 @@ ppm_readppm(FILE * const fileP, void ppm_check(FILE * const fileP, - enum pm_check_type const check_type, + enum pm_check_type const checkType, int const format, int const cols, int const rows, pixval const maxval, - enum pm_check_code * const retval_p) { + enum pm_check_code * const retvalP) { if (rows < 0) pm_error("Invalid number of rows passed to ppm_check(): %d", rows); if (cols < 0) pm_error("Invalid number of columns passed to ppm_check(): %d", cols); - if (check_type != PM_CHECK_BASIC) { - if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE; + if (checkType != PM_CHECK_BASIC) { + if (retvalP) + *retvalP = PM_CHECK_UNKNOWN_TYPE; } else if (PPM_FORMAT_TYPE(format) == PBM_TYPE) { - pbm_check(fileP, check_type, format, cols, rows, retval_p); + pbm_check(fileP, checkType, format, cols, rows, retvalP); } else if (PPM_FORMAT_TYPE(format) == PGM_TYPE) { - pgm_check(fileP, check_type, format, cols, rows, maxval, retval_p); + pgm_check(fileP, checkType, format, cols, rows, maxval, retvalP); } else if (format != RPPM_FORMAT) { - if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE; + if (retvalP) + *retvalP = PM_CHECK_UNCHECKABLE; } else { - pm_filepos const bytes_per_row = cols * 3 * (maxval > 255 ? 2 : 1); - pm_filepos const need_raster_size = rows * bytes_per_row; + pm_filepos const bytesPerRow = cols * 3 * (maxval > 255 ? 2 : 1); + pm_filepos const needRasterSize = rows * bytesPerRow; - pm_check(fileP, check_type, need_raster_size, retval_p); + pm_check(fileP, checkType, needRasterSize, retvalP); } } diff --git a/lib/libppm2.c b/lib/libppm2.c index 3bf74bd4..694ecdd7 100644 --- a/lib/libppm2.c +++ b/lib/libppm2.c @@ -14,8 +14,8 @@ #include <errno.h> -#include "pm_c_util.h" -#include "mallocvar.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" #include "ppm.h" void @@ -36,10 +36,6 @@ ppm_writeppminit(FILE* const fileP, PPM_MAGIC1, plainFormat || maxval >= 1<<16 ? PPM_MAGIC2 : RPPM_MAGIC2, cols, rows, maxval ); -#ifdef VMS - if (!plainFormat) - set_outfile_binary(); -#endif } @@ -133,10 +129,13 @@ ppm_writeppmrowraw(FILE * const fileP, if (rc < 0) pm_error("Error writing row. fwrite() errno=%d (%s)", errno, strerror(errno)); - else if (rc != bytesPerRow) - pm_error("Error writing row. Short write of %u bytes " - "instead of %u", rc, bytesPerRow); + else { + size_t const bytesWritten = rc; + if (bytesWritten != bytesPerRow) + pm_error("Error writing row. Short write of %u bytes " + "instead of %u", (unsigned)bytesWritten, bytesPerRow); + } free(rowBuffer); } diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c index 055bfc8b..f78d0516 100644 --- a/lib/libppmcmap.c +++ b/lib/libppmcmap.c @@ -12,18 +12,26 @@ ** implied warranty. */ -#include "pm_c_util.h" -#include "nstring.h" -#include "mallocvar.h" +#include "netpbm/pm_config.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/nstring.h" +#include "netpbm/mallocvar.h" #include "ppm.h" #include "ppmcmap.h" #define HASH_SIZE 20023 -#define ppm_hashpixel(p) ( ( ( (long) PPM_GETR(p) * 33023 + \ - (long) PPM_GETG(p) * 30013 + \ - (long) PPM_GETB(p) * 27011 ) \ - & 0x7fffffff ) % HASH_SIZE ) + + +static __inline__ unsigned int +ppm_hashpixel(pixel const p) { + + return (unsigned int) (PPM_GETR(p) * 33 * 33 + + PPM_GETG(p) * 33 + + PPM_GETB(p)) % HASH_SIZE; +} + + colorhist_vector ppm_computecolorhist( pixel ** const pixels, @@ -153,7 +161,7 @@ readppmrow(FILE * const fileP, if (setjmp(jmpbuf) != 0) { pm_setjmpbuf(origJmpbufP); - asprintfN(errorP, "Failed to read row of image."); + pm_asprintf(errorP, "Failed to read row of image."); } else { pm_setjmpbufsave(&jmpbuf, &origJmpbufP); @@ -227,8 +235,8 @@ buildHashTable(FILE * const ifP, else { MALLOCVAR(chl); if (chl == NULL) - asprintfN(errorP, - "out of memory computing hash table"); + pm_asprintf(errorP, + "out of memory computing hash table"); chl->ch.color = apixel; chl->ch.value = 1; chl->next = cht[hash]; @@ -282,7 +290,7 @@ computecolorhash(pixel ** const pixels, MALLOCARRAY(rowbuffer, cols); if (rowbuffer == NULL) - asprintfN(errorP, "Unable to allocate %u-column row buffer.", cols); + pm_asprintf(errorP, "Unable to allocate %u-column row buffer.", cols); else { colorhash_table cht; bool tooManyColors; @@ -290,7 +298,7 @@ computecolorhash(pixel ** const pixels, cht = alloccolorhash(); if (cht == NULL) - asprintfN(errorP, "Unable to allocate color hash."); + pm_asprintf(errorP, "Unable to allocate color hash."); else { buildHashTable(ifP, pixels, cols, rows, maxval, format, maxcolors, cht, rowbuffer, @@ -326,7 +334,7 @@ ppm_computecolorhash(pixel ** const pixels, if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } return cht; @@ -351,7 +359,7 @@ ppm_computecolorhash2(FILE * const ifP, if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } return cht; @@ -484,7 +492,7 @@ ppm_colorhisttocolorhash(colorhist_vector const chv, cht = alloccolorhash( ); /* Initializes to NULLs */ if (cht == NULL) - asprintfN(&error, "Unable to allocate color hash"); + pm_asprintf(&error, "Unable to allocate color hash"); else { unsigned int i; @@ -496,13 +504,13 @@ ppm_colorhisttocolorhash(colorhist_vector const chv, for (chl = cht[hash]; chl && !error; chl = chl->next) if (PPM_EQUAL(chl->ch.color, color)) - asprintfN(&error, "same color found twice: (%u %u %u)", - PPM_GETR(color), - PPM_GETG(color), - PPM_GETB(color)); + pm_asprintf(&error, "same color found twice: (%u %u %u)", + PPM_GETR(color), + PPM_GETG(color), + PPM_GETB(color)); MALLOCVAR(chl); if (chl == NULL) - asprintfN(&error, "out of memory"); + pm_asprintf(&error, "out of memory"); else { chl->ch.color = color; chl->ch.value = i; @@ -515,7 +523,7 @@ ppm_colorhisttocolorhash(colorhist_vector const chv, } if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } else retval = cht; @@ -681,9 +689,33 @@ fail: } + +static int (*customCmp)(pixel *, pixel *); + +#ifndef LITERAL_FN_DEF_MATCH +static qsort_comparison_fn customStub; +#endif + static int -pixel_cmp(const void * const a, const void * const b) { - const pixel *p1 = (const pixel *)a, *p2 = (const pixel *)b; +customStub(const void * const a, + const void * const b) { + + return (*customCmp)((pixel *)a, (pixel *)b); +} + + + +#ifndef LITERAL_FN_DEF_MATCH +static qsort_comparison_fn pixelCmp; +#endif + +static int +pixelCmp(const void * const a, + const void * const b) { + + const pixel * const p1 = (const pixel *)a; + const pixel * const p2 = (const pixel *)b; + int diff; diff = PPM_GETR(*p1) - PPM_GETR(*p2); @@ -695,23 +727,18 @@ pixel_cmp(const void * const a, const void * const b) { return diff; } -static int (*custom_cmp)(pixel *, pixel *); - -static int -custom_stub(const void * const a, const void * const b) { - return (*custom_cmp)((pixel *)a, (pixel *)b); -} void -ppm_sortcolorrow(pixel * const colorrow, const int ncolors, +ppm_sortcolorrow(pixel * const colorrow, + int const ncolors, int (*cmpfunc)(pixel *, pixel *)) { - if( cmpfunc ) { - custom_cmp = cmpfunc; - qsort((void *)colorrow, ncolors, sizeof(pixel), custom_stub); + if (cmpfunc) { + customCmp = cmpfunc; + qsort((void *)colorrow, ncolors, sizeof(pixel), customStub); } else - qsort((void *)colorrow, ncolors, sizeof(pixel), pixel_cmp); + qsort((void *)colorrow, ncolors, sizeof(pixel), pixelCmp); } diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c index 58663c94..aee8fd83 100644 --- a/lib/libppmcolor.c +++ b/lib/libppmcolor.c @@ -17,9 +17,9 @@ #include <string.h> #include <math.h> -#include "pm_c_util.h" -#include "mallocvar.h" -#include "nstring.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" #include "ppm.h" #include "colorname.h" @@ -264,7 +264,7 @@ parseOldX11(char const colorname[], computeHexTable(hexit); - if (!strishex(&colorname[1])) + if (!pm_strishex(&colorname[1])) pm_error("Non-hexadecimal characters in #-type color specification"); switch (strlen(colorname) - 1 /* (Number of hex digits) */) { @@ -451,6 +451,43 @@ ppm_colorname(const pixel * const colorP, #define MAXCOLORNAMES 1000u +static const char ** +allocColorNames() { + + const char ** colornames; + + MALLOCARRAY(colornames, MAXCOLORNAMES); + + if (colornames) { + unsigned int i; + for (i = 0; i < MAXCOLORNAMES; ++i) + colornames[i] = NULL; + } + return colornames; +} + + + +static colorhash_table +allocColorHash(void) { + + colorhash_table cht; + jmp_buf jmpbuf; + jmp_buf * origJmpbufP; + + if (setjmp(jmpbuf) != 0) + cht = NULL; + else { + pm_setjmpbufsave(&jmpbuf, &origJmpbufP); + cht = ppm_alloccolorhash(); + } + pm_setjmpbuf(origJmpbufP); + + return cht; +} + + + static void processColorfileEntry(struct colorfile_entry const ce, colorhash_table const cht, @@ -460,8 +497,8 @@ processColorfileEntry(struct colorfile_entry const ce, const char ** const errorP) { if (*colornameIndexP >= MAXCOLORNAMES) - asprintfN(errorP, "Too many colors in colorname dictionary. " - "Max allowed is %u", MAXCOLORNAMES); + pm_asprintf(errorP, "Too many colors in colorname dictionary. " + "Max allowed is %u", MAXCOLORNAMES); else { pixel color; @@ -479,7 +516,7 @@ processColorfileEntry(struct colorfile_entry const ce, colornames[*colornameIndexP] = strdup(ce.colorname); colors[*colornameIndexP] = color; if (colornames[*colornameIndexP] == NULL) - asprintfN(errorP, "Unable to allocate space for color name"); + pm_asprintf(errorP, "Unable to allocate space for color name"); else { *errorP = NULL; ++(*colornameIndexP); @@ -500,7 +537,7 @@ openColornameFile(const char * const fileName, jmp_buf * origJmpbufP; if (setjmp(jmpbuf) != 0) { - asprintfN(errorP, "Failed to open color name file"); + pm_asprintf(errorP, "Failed to open color name file"); pm_setjmpbuf(origJmpbufP); pm_longjmp(); } else { @@ -525,6 +562,9 @@ readOpenColorFile(FILE * const colorFileP, Read the color dictionary file *colorFileP and add the colors in it to colornames[], colors[], and 'cht'. + colornames[] and colors[] must be allocated with MAXCOLORNAMES entries + at entry. + We may add colors to 'cht' even if we fail. -----------------------------------------------------------------------------*/ unsigned int nColorsDone; @@ -543,43 +583,18 @@ readOpenColorFile(FILE * const colorFileP, processColorfileEntry(ce, cht, colornames, colors, &nColorsDone, errorP); } - if (!*errorP) { - *nColorsP = nColorsDone; - - while (nColorsDone < MAXCOLORNAMES) - colornames[nColorsDone++] = NULL; - } + *nColorsP = nColorsDone; if (*errorP) { unsigned int colorIndex; for (colorIndex = 0; colorIndex < nColorsDone; ++colorIndex) - strfree(colornames[colorIndex]); + pm_strfree(colornames[colorIndex]); } } -static colorhash_table -allocColorHash(void) { - - colorhash_table cht; - jmp_buf jmpbuf; - jmp_buf * origJmpbufP; - - if (setjmp(jmpbuf) != 0) - cht = NULL; - else { - pm_setjmpbufsave(&jmpbuf, &origJmpbufP); - cht = ppm_alloccolorhash(); - } - pm_setjmpbuf(origJmpbufP); - - return cht; -} - - - static void readColorFile(const char * const fileName, bool const mustOpen, @@ -588,7 +603,20 @@ readColorFile(const char * const fileName, pixel * const colors, colorhash_table const cht, const char ** const errorP) { +/*---------------------------------------------------------------------------- + Read the color dictionary file named 'fileName' and add the colors in it + to colornames[], colors[], and 'cht'. Return as *nColorsP the number + of colors in it. + If the file is not openable (e.g. not file by that name exists), abort the + program if 'mustOpen' is true; otherwise, return values indicating a + dictionary with no colors. + + colornames[] and colors[] must be allocated with MAXCOLORNAMES entries + at entry. + + We may add colors to 'cht' even if we fail. +-----------------------------------------------------------------------------*/ FILE * colorFileP; openColornameFile(fileName, mustOpen, &colorFileP, errorP); @@ -598,11 +626,6 @@ readColorFile(const char * const fileName, empty file */ *nColorsP = 0; - { - unsigned int i; - for (i = 0; i < MAXCOLORNAMES; ++i) - colornames[i] = NULL; - } *errorP = NULL; } else { readOpenColorFile(colorFileP, nColorsP, colornames, colors, cht, @@ -626,24 +649,24 @@ readcolordict(const char * const fileName, const char ** colornames; - MALLOCARRAY(colornames, MAXCOLORNAMES); + colornames = allocColorNames(); if (colornames == NULL) - asprintfN(errorP, "Unable to allocate space for colorname table."); + pm_asprintf(errorP, "Unable to allocate space for colorname table."); else { pixel * colors; MALLOCARRAY(colors, MAXCOLORNAMES); if (colors == NULL) - asprintfN(errorP, "Unable to allocate space for color table."); + pm_asprintf(errorP, "Unable to allocate space for color table."); else { colorhash_table cht; cht = allocColorHash(); if (cht == NULL) - asprintfN(errorP, "Unable to allocate space for color hash"); + pm_asprintf(errorP, "Unable to allocate space for color hash"); else { readColorFile(fileName, mustOpen, nColorsP, colornames, colors, cht, @@ -675,7 +698,28 @@ ppm_readcolordict(const char * const fileName, const char *** const colornamesP, pixel ** const colorsP, colorhash_table * const chtP) { +/*---------------------------------------------------------------------------- + Read the color dictionary from the file named 'fileName'. If we can't open + the file (e.g. because it does not exist), and 'mustOpen' is false, return + an empty dictionary (it contains no colors). But if 'mustOpen' is true, + abort the program instead of returning an empty dictionary. + + Return as *nColorsP the number of colors in the dictionary. + Return as *colornamesP the names of those colors. *colornamesP is a + malloced array that Caller must free with ppm_freecolornames(). + The first *nColorsP entries are valid; *chtP contains indices into this + array. + + Return as *colorsP the colors. *colorsP is a malloced array of size + MAXCOLORS with the first elements filled in and the rest undefined. + + Return as *chtP a color hash table mapping each color in the dictionary + to the index into *colornamesP for the name of the color. + + Each of 'nColorsP, 'colornamesP', and 'colorsP' may be null, in which case + we do not return the corresponding information (or allocate memory for it). +-----------------------------------------------------------------------------*/ colorhash_table cht; const char ** colornames; pixel * colors; @@ -687,7 +731,7 @@ ppm_readcolordict(const char * const fileName, if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); ppm_freecolorhash(cht); } else { if (chtP) diff --git a/lib/libppmd.c b/lib/libppmd.c index 2325dd68..262679ec 100644 --- a/lib/libppmd.c +++ b/lib/libppmd.c @@ -18,9 +18,9 @@ #include <assert.h> #include <stdlib.h> -#include "pm_config.h" -#include "pm_c_util.h" -#include "mallocvar.h" +#include "netpbm/pm_config.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" #include "ppm.h" #include "ppmdfont.h" #include "ppmdraw.h" @@ -966,14 +966,25 @@ typedef struct int edge; } coord; -typedef struct fillobj { +typedef struct fillState { int n; + /* Number of elements in 'coords' */ int size; int curedge; int segstart; int ydir; int startydir; coord * coords; +} fillState; + +typedef struct fillobj { + + /* The only reason we have a struct fillState separate from + struct fillobj is that the drawproc interface is defined to + have drawing not modify the fillobj, i.e. it passed + const fillobj * to the drawing program. + */ + struct fillState * stateP; } fillobj; #define SOME 1000 @@ -984,16 +995,24 @@ struct fillobj * ppmd_fill_create(void) { fillobj * fillObjP; + struct fillState * stateP; MALLOCVAR(fillObjP); if (fillObjP == NULL) pm_error("out of memory allocating a fillhandle"); - fillObjP->n = 0; - fillObjP->size = SOME; - MALLOCARRAY(fillObjP->coords, fillObjP->size); - if (fillObjP->coords == NULL) + + MALLOCVAR(stateP); + if (stateP == NULL) pm_error("out of memory allocating a fillhandle"); - fillObjP->curedge = 0; + + stateP->n = 0; + stateP->size = SOME; + MALLOCARRAY(stateP->coords, stateP->size); + if (stateP->coords == NULL) + pm_error("out of memory allocating a fillhandle"); + stateP->curedge = 0; + + fillObjP->stateP = stateP; /* Turn off line clipping. */ /* UGGH! We must eliminate this global variable */ @@ -1021,14 +1040,95 @@ ppmd_fill_init(void) { void -ppmd_fill_destroy(struct fillobj * fillObjP) { +ppmd_fill_destroy(struct fillobj * const fillObjP) { - free(fillObjP->coords); + free(fillObjP->stateP->coords); + free(fillObjP->stateP); free(fillObjP); } +static void +addCoord(struct fillState * const stateP, + ppmd_point const point) { + + stateP->coords[stateP->n].point = point; + stateP->coords[stateP->n].edge = stateP->curedge; + + ++stateP->n; +} + + + +static void +startNewSegment(struct fillState * const stateP) { +/*---------------------------------------------------------------------------- + Close off the segment we're currently building and start a new one. +-----------------------------------------------------------------------------*/ + if (stateP->startydir != 0 && stateP->ydir != 0) { + /* There's stuff in the current segment. */ + if (stateP->startydir == stateP->ydir) { + /* Oops, first edge and last edge of current segment are the same. + Change all points in the first edge to be in the last. + */ + int const firstEdge = stateP->coords[stateP->segstart].edge; + int const lastEdge = stateP->coords[stateP->n - 1].edge; + coord * const segStartCoordP = &stateP->coords[stateP->segstart]; + coord * const segEndCoordP = &stateP->coords[stateP->n]; + + coord * fcP; + + for (fcP = segStartCoordP; + fcP < segEndCoordP && fcP->edge == firstEdge; + ++fcP) + fcP->edge = lastEdge; + } + } + /* And start new segment. */ + ++stateP->curedge; + stateP->segstart = stateP->n; + stateP->ydir = 0; + stateP->startydir = 0; +} + + + +static void +continueSegment(struct fillState * const stateP, + int const dy) { +/*---------------------------------------------------------------------------- + 'dy' is how much the current point is above the previous one. +-----------------------------------------------------------------------------*/ + if (dy != 0) { + if (stateP->ydir != 0 && stateP->ydir != dy) { + /* Direction changed. Insert a fake coord, old + position but new edge number. + */ + ++stateP->curedge; + addCoord(stateP, stateP->coords[stateP->n - 1].point); + } + stateP->ydir = dy; + if (stateP->startydir == 0) + stateP->startydir = dy; + } +} + + + + +/* ppmd_fill_drawprocp() is a drawproc that turns an outline drawing function + into a filled shape function. This is a somewhat off-label application of + a drawproc: A drawproc is intended just to draw a point. So e.g. you + might draw a circle with a fat brush by calling ppmd_circle with a drawproc + that draws a point as a 10-pixel disk. + + But ppmd_fill_drawproc() just draws a point the trivial way: as one pixel. + However, it tracks every point that is drawn in a form that a subsequent + ppmd_fill() call can use to to fill in the shape drawn, assuming it turns + out to be a closed shape. +*/ + void ppmd_fill_drawprocp(pixel ** const pixels, unsigned int const cols, @@ -1037,85 +1137,39 @@ ppmd_fill_drawprocp(pixel ** const pixels, ppmd_point const p, const void * const clientdata) { - fillobj * fh; - coord * cp; - - fh = (fillobj*) clientdata; + const fillobj * const fillObjP = clientdata; + struct fillState * const stateP = fillObjP->stateP; - /* If these are the same coords we saved last time, don't bother. */ - if (fh->n > 0) { - ppmd_point const lastPoint = fh->coords[fh->n - 1].point; - if (pointsEqual(p, lastPoint)) - return; - } - - /* Ok, these are new; make room for two more coords. */ - if (fh->n + 1 >= fh->size) { - fh->size += SOME; - REALLOCARRAY(fh->coords, fh->size); - if (fh->coords == NULL) + /* Make room for two more coords, the max we might add. */ + if (stateP->n + 2 > stateP->size) { + stateP->size += SOME; + REALLOCARRAY(stateP->coords, stateP->size); + if (stateP->coords == NULL) pm_error("out of memory enlarging a fillhandle"); } - /* Check for extremum and set the edge number. */ - if (fh->n == 0) { + if (stateP->n == 0) { /* Start first segment. */ - fh->segstart = fh->n; - fh->ydir = 0; - fh->startydir = 0; + stateP->segstart = stateP->n; + stateP->ydir = 0; + stateP->startydir = 0; + addCoord(stateP, p); } else { - coord * const ocp = &(fh->coords[fh->n - 1]); - int dx, dy; - - dx = p.x - ocp->point.x; - dy = p.y - ocp->point.y; - if (dx < -1 || dx > 1 || dy < -1 || dy > 1) { - /* Segment break. Close off old one. */ - if (fh->startydir != 0 && fh->ydir != 0) - if (fh->startydir == fh->ydir) { - /* Oops, first edge and last edge are the same. - Renumber the first edge in the old segment. - */ - const coord * const fcpLast= &(fh->coords[fh->n -1]); - coord * fcp; - - int oldedge; - - fcp = &(fh->coords[fh->segstart]); - oldedge = fcp->edge; - for (; fcp <= fcpLast && fcp->edge == oldedge ; ++fcp) - fcp->edge = ocp->edge; - } - /* And start new segment. */ - ++fh->curedge; - fh->segstart = fh->n; - fh->ydir = 0; - fh->startydir = 0; + ppmd_point const prevPoint = stateP->coords[stateP->n - 1].point; + int const dx = p.x - prevPoint.x; + int const dy = p.y - prevPoint.y; + + if (dx == 0 && dy == 0) { + /* These are the same coords we had last time; don't bother */ } else { - /* Segment continues. */ - if (dy != 0) { - if (fh->ydir != 0 && fh->ydir != dy) { - /* Direction changed. Insert a fake coord, old - position but new edge number. - */ - ++fh->curedge; - cp = &fh->coords[fh->n]; - cp->point = ocp->point; - cp->edge = fh->curedge; - ++fh->n; - } - fh->ydir = dy; - if (fh->startydir == 0) - fh->startydir = dy; - } + if (abs(dx) > 1 || abs(dy) > 1) + startNewSegment(stateP); + else + continueSegment(stateP, dy); + + addCoord(stateP, p); } } - - /* Save this coord. */ - cp = &fh->coords[fh->n]; - cp->point = p; - cp->edge = fh->curedge; - ++fh->n; } @@ -1137,12 +1191,12 @@ ppmd_fill_drawproc(pixel** const pixels, #ifndef LITERAL_FN_DEF_MATCH -static qsort_comparison_fn yx_compare; +static qsort_comparison_fn yxCompare; #endif static int -yx_compare(const void * const c1Arg, - const void * const c2Arg) { +yxCompare(const void * const c1Arg, + const void * const c2Arg) { const coord * const c1P = c1Arg; const coord * const c2P = c2Arg; @@ -1173,10 +1227,12 @@ ppmd_fill(pixel ** const pixels, int const cols, int const rows, pixval const maxval, - struct fillobj * const fh, + struct fillobj * const fillObjP, ppmd_drawproc drawProc, const void * const clientdata) { + struct fillState * const fh = fillObjP->stateP; + int pedge; int i, edge, lx, rx, py; coord * cp; @@ -1202,7 +1258,7 @@ ppmd_fill(pixel ** const pixels, ppmd_setlineclip(oldclip); /* Sort the coords by Y, secondarily by X. */ - qsort((char*) fh->coords, fh->n, sizeof(coord), yx_compare); + qsort((char*) fh->coords, fh->n, sizeof(coord), yxCompare); /* Find equal coords with different edge numbers, and swap if necessary. */ edge = -1; diff --git a/lib/libppmfloyd.c b/lib/libppmfloyd.c index ec6256ff..d2b9c7b1 100644 --- a/lib/libppmfloyd.c +++ b/lib/libppmfloyd.c @@ -22,9 +22,9 @@ do Floyd-Steinberg. ** implied warranty. */ +#include "netpbm/mallocvar.h" #include "ppm.h" #include "ppmfloyd.h" -#include "mallocvar.h" diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c index 2a54e896..f1573c68 100644 --- a/lib/libppmfuzzy.c +++ b/lib/libppmfuzzy.c @@ -5,8 +5,8 @@ <kenan@unix.ba> in 2006. =============================================================================*/ -#include "pm_c_util.h" -#include "nstring.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/nstring.h" #include "ppm.h" typedef double fzLog; diff --git a/lib/libsystem.c b/lib/libsystem.c index 1c407ce4..fd3c52ec 100644 --- a/lib/libsystem.c +++ b/lib/libsystem.c @@ -13,6 +13,7 @@ Contributed to the public domain. =============================================================================*/ #define _XOPEN_SOURCE +#define _BSD_SOURCE /* Make SIGWINCH defined on OpenBSD */ #include <stdarg.h> #include <unistd.h> @@ -23,8 +24,8 @@ #include <signal.h> #include <sys/wait.h> -#include "pm_c_util.h" -#include "mallocvar.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" #include "pm.h" #include "pm_system.h" @@ -133,7 +134,7 @@ createPipeFeeder(void pipeFeederRtn(int, void *), int pipeToFeed[2]; pid_t rc; - pipe(pipeToFeed); + pm_pipe(pipeToFeed); rc = fork(); if (rc < 0) { pm_error("fork() of stdin feeder failed. errno=%d (%s)", @@ -179,7 +180,7 @@ spawnProcessor(const char * const progName, pid_t rc; if (pipeStdout) - pipe(stdoutpipe); + pm_pipe(stdoutpipe); rc = fork(); if (rc < 0) { @@ -220,6 +221,20 @@ spawnProcessor(const char * const progName, static const char * signalName(unsigned int const signalClass) { +/* There are various signal classes that are not universally defined, + so we make a half-hearted attempt to determine whether they are and + not try to recognize the ones that aren't. We do this by testing + whether a macro is defind with the signal class name. That could give + a false negative, because the signal class name isn't necessarily + defined as a macro, but it's a really, really small problem to miss + one of these signal classes here, so we don't bother with all the work + it would take to do it right. + + OpenBSD does not have SIGWINCH and SIGIO in 2013. Everyone else seems + to have them. OpenBSD does have them if the code is not declared as + X/open code (i.e. OpenBSD seems to interpret _XOPEN_SOURCE backward - + it removes features rather than adds them). +*/ switch (signalClass) { case SIGHUP: /* POSIX.1 */ return "SIGHUP"; @@ -263,6 +278,11 @@ signalName(unsigned int const signalClass) { return "SIGTTIN"; case SIGTTOU: /* POSIX.1 */ return "SIGTTOU"; +#ifdef SIGURG +/* SCO Openserver 5.0.7/3.2 does not have SIGURG */ + case SIGURG: + return "SIGURG"; +#endif case SIGXCPU: return "SIGXCPU"; case SIGXFSZ: @@ -271,17 +291,23 @@ signalName(unsigned int const signalClass) { return "SIGVTALRM"; case SIGPROF: return "SIGPROF"; +#ifdef SIGWINCH + case SIGWINCH: + return "SIGWINCH"; +#endif +#ifdef SIGIO +/* SCO Openserver 5.0.7/3.2 does not have SIGIO */ + case SIGIO: + return "SIGIO"; +#endif +#ifdef SIGPWR + case SIGPWR: + return "SIGPWR"; +#endif case SIGSYS: return "SIGSYS"; default: return "???"; - - /* There are various other signal classes on some systems, but - not defined by POSIX and not on at least one system we - know of for which someone wanted to compile Netpbm. The - list includes: SIGPWR, SIGLOST, SIGINFO, SIGRTxx, - SIGURG, SIGWINCH, SIGIO. - */ } } @@ -508,20 +534,20 @@ pm_feed_from_memory(int const pipeToFeedFd, struct bufferDesc * const inputBufferP = feederParm; - FILE * const outfile = fdopen(pipeToFeedFd, "w"); + FILE * const outFileP = fdopen(pipeToFeedFd, "w"); - int bytesTransferred; + size_t bytesTransferred; /* The following signals (and normally kills) the process with SIGPIPE if the pipe does not take all 'size' bytes. */ bytesTransferred = - fwrite(inputBufferP->buffer, 1, inputBufferP->size, outfile); + fwrite(inputBufferP->buffer, 1, inputBufferP->size, outFileP); if (inputBufferP->bytesTransferredP) *(inputBufferP->bytesTransferredP) = bytesTransferred; - fclose(outfile); + fclose(outFileP); } @@ -532,14 +558,14 @@ pm_accept_to_memory(int const pipetosuckFd, struct bufferDesc * const outputBufferP = accepterParm; - FILE * const infile = fdopen(pipetosuckFd, "r"); + FILE * const inFileP = fdopen(pipetosuckFd, "r"); - int bytesTransferred; + size_t bytesTransferred; bytesTransferred = - fread(outputBufferP->buffer, 1, outputBufferP->size, infile); + fread(outputBufferP->buffer, 1, outputBufferP->size, inFileP); - fclose(infile); + fclose(inFileP); if (outputBufferP->bytesTransferredP) *(outputBufferP->bytesTransferredP) = bytesTransferred; diff --git a/lib/libsystem_dummy.c b/lib/libsystem_dummy.c index db26ba7a..97dd8984 100644 --- a/lib/libsystem_dummy.c +++ b/lib/libsystem_dummy.c @@ -4,7 +4,7 @@ This is a dummy version of libsystem.c, for use on systems that don't have the kind of process control that libsystem.c needs. - With this module, program will build cleanly, but if a program actually + With this module, programs will build cleanly, but if a program actually calls pm_system(), it will die with an error message saying that the facility is not available. =============================================================================*/ diff --git a/lib/lum.h b/lib/lum.h index 34d3866e..3d20032b 100644 --- a/lib/lum.h +++ b/lib/lum.h @@ -1,3 +1,5 @@ +#include <netpbm/pm_config.h> + /* Lookup tables for fast RGB -> luminance calculation. */ static int times77[256] = { 0, 77, 154, 231, 308, 385, 462, 539, diff --git a/lib/pam.h b/lib/pam.h index c28c5c2c..c58e36a2 100644 --- a/lib/pam.h +++ b/lib/pam.h @@ -1,11 +1,14 @@ -/*---------------------------------------------------------------------------- +/*============================================================================= These are declarations for use with the Portable Arbitrary Map (PAM) format and the Netpbm library functions specific to them. ------------------------------------------------------------------------------*/ + This file was originally written by Bryan Henderson and is contributed + to the public domain by him and subsequent authors. +=============================================================================*/ #ifndef PAM_H #define PAM_H +#include <netpbm/pm_config.h> #include <netpbm/pm.h> #include <netpbm/pnm.h> @@ -49,9 +52,13 @@ struct pam { */ FILE * file; int format; - /* The format code of the raw image. This is PAM_FORMAT + /* The format code of the image. This is PAM_FORMAT unless the PAM image is really a view of a PBM, PGM, or PPM - image. Then it's PBM_FORMAT, RPBM_FORMAT, etc. + image. Then it's PBM_FORMAT, RPBM_FORMAT, etc. For output, + only the format _type_ is significant, e.g. PBM_FORMAT + and RPBM_FORMAT have identical effect. This is because on + output, 'plainformat' determines whether the output is the + raw or plain format of the type given by 'format'. */ unsigned int plainformat; /* Logical: On output, use plain version of the format type @@ -64,6 +71,9 @@ struct pam { Before Netpbm 10.32, this was rather different. It simply described for convenience the plainness of the format indicated by 'format'. + + This is meaningless when 'format' is PAM_FORMAT, as PAM does not + have plain and raw variations. */ int height; /* Height of image in rows */ int width; @@ -109,6 +119,23 @@ struct pam { libnetpbm does not return comments and does not allocate any storage. */ + int visual; /* boolean */ + /* tuple_type is one of the PAM-defined tuple types for visual + images ("GRAYSCALE", "RGB_ALPHA", etc.). + */ + unsigned int color_depth; + /* Number of color planes (i.e. 'depth', but without transparency). + The color planes are the lowest numbered ones. Meaningless if + 'visual' is false. + */ + int have_opacity; /* boolean */ + /* The tuples have an opacity (transparency, alpha) plane. + Meaningless if 'visual' is false. + */ + unsigned int opacity_plane; + /* The plane number of the opacity plane; meaningless if + 'haveOpacity' is false or 'visual' is false. + */ }; #define PAM_HAVE_ALLOCATION_DEPTH 1 @@ -136,6 +163,9 @@ struct pam { #define PAM_PBM_TUPLETYPE "BLACKANDWHITE" #define PAM_PGM_TUPLETYPE "GRAYSCALE" #define PAM_PPM_TUPLETYPE "RGB" +#define PAM_PBM_ALPHA_TUPLETYPE "BLACKANDWHITE_ALPHA" +#define PAM_PGM_ALPHA_TUPLETYPE "GRAYSCALE_ALPHA" +#define PAM_PPM_ALPHA_TUPLETYPE "RGB_ALPHA" #define PAM_PBM_BLACK PAM_BLACK #define PAM_PBM_WHITE PAM_BW_WHITE @@ -275,6 +305,14 @@ pnm_makearrayrgb(const struct pam * const pamP, tuple ** const tuples); void +pnm_makerowrgba(const struct pam * const pamP, + tuple * const tuplerow); + +void +pnm_addopacityrow(const struct pam * const pamP, + tuple * const tuplerow); + +void pnm_getopacity(const struct pam * const pamP, int * const haveOpacityP, unsigned int * const opacityPlaneP); @@ -282,10 +320,6 @@ pnm_getopacity(const struct pam * const pamP, void pnm_createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP); -void -createBlackTuple(const struct pam * const pamP, tuple * const blackTupleP); - - tuple pnm_allocpamtuple(const struct pam * const pamP); @@ -483,9 +517,15 @@ pnm_YCbCr_to_rgbtuple(const struct pam * const pamP, ((tuple)[PAM_RED_PLANE] == (tuple)[PAM_GRN_PLANE] && \ (tuple)[PAM_RED_PLANE] == (tuple)[PAM_BLU_PLANE]) +tuple +pnm_backgroundtuple(struct pam * const pamP, + tuple ** const tuples); + /*---------------------------------------------------------------------------- These are meant for passing to pm_system() as Standard Input feeder and Standard Output accepter. + + The 'feederParm' or 'accepterParm' is a pointer to a struct pamtuples. -----------------------------------------------------------------------------*/ void diff --git a/lib/pamdraw.h b/lib/pamdraw.h new file mode 100644 index 00000000..aaa6f20e --- /dev/null +++ b/lib/pamdraw.h @@ -0,0 +1,317 @@ +/* pamdraw.h - header file for simple drawing routines in libnetpbm. +** +** Simple, yes, and also fairly slow if the truth be told; but also very +** flexible and powerful. +** +** The two basic concepts are the drawproc and clientdata. All the drawing +** routines take a drawproc that does the actual drawing. A drawproc draws +** a single point, and it looks like this: +*/ + +#include <netpbm/pm_config.h> + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} /* to fake out automatic code indenters */ +#endif + + +typedef struct { + int x; + int y; +} pamd_point; + +static __inline__ pamd_point +pamd_makePoint(int const x, + int const y) { + + pamd_point p; + + p.x = x; + p.y = y; + + return p; +} + +void +pamd_validateCoord(int const c); + +void +pamd_validatePoint(pamd_point const p); + +typedef enum { + PAMD_PATHLEG_LINE +} pamd_pathlegtype; + +struct pamd_linelegparms { + pamd_point end; +}; + +typedef struct { +/*---------------------------------------------------------------------------- + A leg of a pamd_path. +-----------------------------------------------------------------------------*/ + pamd_pathlegtype type; + union { + struct pamd_linelegparms linelegparms; + } u; +} pamd_pathleg; + +typedef struct { +/*---------------------------------------------------------------------------- + A closed path +-----------------------------------------------------------------------------*/ + unsigned int version; + /* Must be zero. For future expansion. */ + pamd_point begPoint; + unsigned int legCount; + /* Number of legs in the path; i.e. size of 'legs' array */ + size_t legSize; + /* Size of storage occupied by one pamd_pathleg. I.e. + sizeof(pamd_pathleg). Used for + binary backward compatibility between callers and libpamd + as the definition of pamd_pathleg changes. + */ + pamd_pathleg * legs; +} pamd_path; + + + +typedef void pamd_drawproc(tuple **, unsigned int, unsigned int, unsigned int, + sample, pamd_point, const void *); + +pamd_drawproc pamd_point_drawproc; + +/* +** So, you call a drawing routine, e.g. pamd_line(), and pass it a drawproc; +** it calls the drawproc for each point it wants to draw. Why so complicated? +** Because you can define your own drawprocs to do more interesting things than +** simply draw the point. For example, you could make one that calls back into +** another drawing routine, say pamd_circle() to draw a circle at each point +** of a line. +** +** Slow? Well sure, we're talking results here, not realtime. You can do +** tricks with this arrangement that you couldn't even think of before. +** Still, to speed things up for the 90% case you can use this: +*/ +#define PAMD_NULLDRAWPROC NULL +/* +** Just like pamd_point_drawproc() it simply draws the point, but it's done +** inline, and clipping is assumed to be handled at a higher level. +** +** Now, what about clientdata. Well, it's an arbitrary pointer, and can +** mean something different to each different drawproc. For the above two +** drawprocs, clientdata should be a pointer to a tuple holding the color +** to be drawn. Other drawprocs can use it to point to something else, +** e.g. some structure to be modified, or they can ignore it. +*/ + + +/* Outline drawing routines. Lines, splines, circles, etc. */ + +int +pamd_setlinetype(int const type); + +#define PAMD_LINETYPE_NORMAL 0 +#define PAMD_LINETYPE_NODIAGS 1 +/* If you set NODIAGS, all tuples drawn by pamd_line() will be 4-connected +** instead of 8-connected; in other words, no diagonals. This is useful +** for some applications, for example when you draw many parallel lines +** and you want them to fit together without gaps. +*/ + +int +pamd_setlineclip(int const clip); + +#define pamd_setlineclipping(x) pamd_setlineclip(x) +/* Normally, pamd_line() clips to the edges of the pixmap. You can use this +** routine to disable the clipping, for example if you are using a drawproc +** that wants to do its own clipping. +*/ + +void +pamd_line(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p0, + pamd_point const p1, + pamd_drawproc drawProc, + const void * const clientdata); + + /* Draws a line from p0 to p1. */ + +void +pamd_spline3(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const p0, + pamd_point const p1, + pamd_point const p2, + pamd_drawproc drawProc, + const void * const clientdata); + + /* Draws a three-point spline from p0 to p2, with p1 + as the control point. All drawing is done via pamd_line(), + so the routines that control it control pamd_spline3p() as well. + */ + +void +pamd_polyspline(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const p0, + unsigned int const nc, + pamd_point * const c, + pamd_point const p1, + pamd_drawproc drawProc, + const void * const clientdata); + + /* Draws a bunch of splines end to end. p0 and p1 are the initial and + final points, and the c[] are the intermediate control points. nc is + the number of these control points. + */ + +void +pamd_spline4(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const endPt0, + pamd_point const endPt1, + pamd_point const ctlPt0, + pamd_point const ctlPt1, + pamd_drawproc drawProc, + const void * const clientdata); + +void +pamd_circle(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const center, + unsigned int const radius, + pamd_drawproc drawProc, + const void * const clientData); + /* Draws a circle centered at 'center' with radius 'radius' */ + +/* Simple filling routines. */ + +void +pamd_filledrectangle(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + int const left, + int const top, + int const width, + int const height, + pamd_drawproc drawProc, + const void * const clientdata); + /* Fills in the rectangle [left, top, width, height]. */ + + +void +pamd_fill_path(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_path * const pathP, + tuple const color); + /* Fills in a closed path. Not much different from pamd_fill(), + but with a different interface. + */ + + +/* Arbitrary filling routines. With these you can fill any outline that +** you can draw with the outline routines. +*/ + +struct fillobj; + +struct fillobj * +pamd_fill_create(void); + /* Returns a blank fillhandle. */ + +void +pamd_fill_destroy(struct fillobj * fillObjP); + +void +pamd_fill_drawproc(tuple ** const tuples, + unsigned int const cols, + unsigned int const rows, + unsigned int const depth, + sample const maxval, + pamd_point const p, + const void * const clientdata); + /* Use this drawproc to trace the outline you want filled. Use + the fillhandle as the clientdata. + */ + +void +pamd_fill(tuple ** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + struct fillobj * const fillObjP, + pamd_drawproc drawProc, + const void * const clientdata); + + /* Once you've traced the outline, give the fillhandle to this routine to + do the actual drawing. As usual, it takes a drawproc and clientdata; + you could define drawprocs to do stipple fills and such. + */ + +/* Text drawing routines. */ + +void +pamd_text(tuple** const tuples, + int const cols, + int const rows, + int const depth, + sample const maxval, + pamd_point const pos, + int const height, + int const angle, + const char * const sArg, + pamd_drawproc drawProc, + const void * const clientdata); + + /* Draws the null-terminated string 's' left justified at the point ('x', + 'y'). The text will be 'height' tuples high and will be aligned on a + baseline inclined 'angle' degrees with the X axis. The supplied + drawproc and clientdata are passed to pamd_line() which performs the + actual drawing. + */ + +void +pamd_text_box(int const height, + int const angle, + const char * const s, + int * const leftP, + int * const topP, + int * const rightP, + int * const bottomP); + /* Calculates the extents box for text drawn by pamd_text with the given + string, size, and orientation. Most extent box calculations should use + an angle specification of zero to calculate the unrotated box enclosing + the text. If you need the extents of rotated text, however, you can + call pamd_text_box with a nonzero angle. + */ + +#ifdef __cplusplus +} +#endif + diff --git a/lib/pammap.h b/lib/pammap.h index c9c1deed..da2e2470 100644 --- a/lib/pammap.h +++ b/lib/pammap.h @@ -1,11 +1,11 @@ -/****************************************************************************** +/*============================================================================= pammap.h -******************************************************************************* +=============================================================================== Interface header file for hash table and lookup table pam functions in libpnm. -******************************************************************************/ +=============================================================================*/ #ifndef PAMMAP_H #define PAMMAP_H diff --git a/lib/path.c b/lib/path.c index 5a1d4988..10ae92d2 100644 --- a/lib/path.c +++ b/lib/path.c @@ -10,8 +10,8 @@ #include <assert.h> -#include "pm_c_util.h" -#include "mallocvar.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" #include "ppm.h" #include "ppmdfont.h" #include "ppmdraw.h" diff --git a/lib/pbm.h b/lib/pbm.h index 24574d07..a29adb48 100644 --- a/lib/pbm.h +++ b/lib/pbm.h @@ -58,9 +58,21 @@ pbm_allocrow(unsigned int const cols); #define pbm_freearray_packed(packed_bits, rows) \ pm_freearray((char **) packed_bits, rows) -bit** pbm_readpbm(FILE* file, int* colsP, int* rowsP); -void pbm_readpbminit(FILE* file, int* colsP, int* rowsP, int* formatP); -void pbm_readpbmrow(FILE* file, bit* bitrow, int cols, int format); +bit** +pbm_readpbm(FILE * const file, + int * const colsP, + int * const rowsP); + +void +pbm_readpbminit(FILE * const file, + int * const colsP, + int * const rowsP, int * const formatP); + +void +pbm_readpbmrow(FILE * const file, + bit * const bitrow, + int const cols, + int const format); void pbm_readpbmrow_packed(FILE * const file, @@ -76,6 +88,10 @@ pbm_readpbmrow_bitoffset(FILE * const fileP, unsigned int const offset); void +pbm_cleanrowend_packed(unsigned char * const packedBits, + unsigned int const cols); + +void pbm_writepbminit(FILE * const fileP, int const cols, int const rows, diff --git a/lib/pbmfont.h b/lib/pbmfont.h index a977a11f..bedf57b9 100644 --- a/lib/pbmfont.h +++ b/lib/pbmfont.h @@ -1,6 +1,8 @@ /* pbmfont.h - header file for font routines in libpbm */ +#include "pbm.h" + #ifdef __cplusplus extern "C" { #endif diff --git a/lib/pm.h b/lib/pm.h index a0c2005e..47cbfe83 100644 --- a/lib/pm.h +++ b/lib/pm.h @@ -23,10 +23,6 @@ #include <sys/stat.h> #include <fcntl.h> -#ifdef VMS -#include <perror.h> -#endif - #ifdef __cplusplus extern "C" { #endif @@ -90,7 +86,7 @@ extern "C" { We're ignoring S_IREAD now to see if anyone misses it. If there are still users that need it, we can handle it here. */ -#ifdef WIN32 +#if MSVCRT #define PM_S_IWUSR _S_IWRITE #define PM_S_IRUSR _S_IREAD #else @@ -123,6 +119,9 @@ pm_proginit(int * const argcP, const char * argv[]); void pm_setMessage(int const newState, int * const oldStateP); +int +pm_getMessage(void); + FILE * pm_tmpfile(void); @@ -185,6 +184,21 @@ pm_setjmpbufsave(jmp_buf * const jmpbufP, void pm_longjmp(void); +void +pm_fork(int * const iAmParentP, + pid_t * const childPidP, + const char ** const errorP); + +void +pm_waitpid(pid_t const pid, + int * const statusP, + int const options, + pid_t * const exitedPidP, + const char ** const errorP); + + +void +pm_waitpidSimple(pid_t const pid); typedef void pm_usermessagefn(const char * msg); @@ -205,6 +219,9 @@ pm_errormsg(const char format[], ...); void PM_GNU_PRINTF_ATTR(1,2) pm_error (const char reason[], ...); +int +pm_have_float_format(void); + /* Obsolete - use shhopt and user's manual instead */ void pm_usage (const char usage[]); @@ -280,6 +297,16 @@ pm_readbiglongu(FILE * const ifP, } int +pm_readbiglong2(FILE * const ifP, + int32_t * const lP); + +static __inline__ int +pm_readbiglongu2(FILE * const ifP, + uint32_t * const lP) { + return pm_readbiglong2(ifP, (int32_t *) lP); +} + +int pm_writebiglong(FILE * const ofP, long const l); @@ -320,6 +347,16 @@ pm_readlittlelongu(FILE * const ifP, } int +pm_readlittlelong2(FILE * const ifP, + int32_t * const lP); + +static __inline__ int +pm_readlittlelong2u(FILE * const ifP, + uint32_t * const lP) { + return pm_readlittlelong2(ifP, (int32_t *) lP); +} + +int pm_writelittlelong(FILE * const ofP, long const l); diff --git a/lib/pmfileio.c b/lib/pmfileio.c index 84ab53ab..8176ae6a 100644 --- a/lib/pmfileio.c +++ b/lib/pmfileio.c @@ -10,7 +10,8 @@ does it in other libc's). pm_config.h defines TMPDIR as P_tmpdir in some environments. */ -#define _XOPEN_SOURCE 500 /* Make sure ftello, fseeko are defined */ +#define _BSD_SOURCE /* Make sure strdup is defined */ +#define _XOPEN_SOURCE 500 /* Make sure ftello, fseeko, strdup are defined */ #define _LARGEFILE_SOURCE 1 /* Make sure ftello, fseeko are defined */ #define _LARGEFILE64_SOURCE 1 #define _FILE_OFFSET_BITS 64 @@ -28,19 +29,21 @@ #define _LARGE_FILE_API /* This makes the the x64() functions available on AIX */ +#include "netpbm/pm_config.h" #include <unistd.h> +#include <assert.h> #include <stdio.h> #include <fcntl.h> #include <stdarg.h> #include <string.h> #include <errno.h> -#ifdef __DJGPP__ - #include <io.h> +#if HAVE_IO_H + #include <io.h> /* For mktemp */ #endif -#include "pm_c_util.h" -#include "mallocvar.h" -#include "nstring.h" +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" #include "pm.h" @@ -48,18 +51,14 @@ /* File open/close that handles "-" as stdin/stdout and checks errors. */ -FILE* +FILE * pm_openr(const char * const name) { - FILE* f; + FILE * f; - if (strcmp(name, "-") == 0) + if (streq(name, "-")) f = stdin; else { -#ifndef VMS f = fopen(name, "rb"); -#else - f = fopen(name, "r", "ctx=stm"); -#endif if (f == NULL) pm_error("Unable to open file '%s' for reading. " "fopen() returns errno %d (%s)", @@ -70,18 +69,14 @@ pm_openr(const char * const name) { -FILE* +FILE * pm_openw(const char * const name) { - FILE* f; + FILE * f; - if (strcmp(name, "-") == 0) + if (streq(name, "-")) f = stdout; else { -#ifndef VMS f = fopen(name, "wb"); -#else - f = fopen(name, "w", "mbc=32", "mbf=2"); /* set buffer factors */ -#endif if (f == NULL) pm_error("Unable to open file '%s' for writing. " "fopen() returns errno %d (%s)", @@ -129,10 +124,10 @@ tempFileOpenFlags(void) { retval = 0 | O_CREAT | O_RDWR -#ifndef WIN32 +#if !MSVCRT | O_EXCL #endif -#ifdef WIN32 +#if MSVCRT | O_BINARY #endif ; @@ -226,25 +221,25 @@ makeTmpfileWithTemplate(const char * const filenameTemplate, filenameBuffer = strdup(filenameTemplate); if (filenameBuffer == NULL) - asprintfN(errorP, "Unable to allocate storage for temporary " - "file name"); + pm_asprintf(errorP, "Unable to allocate storage for temporary " + "file name"); else { int rc; rc = mkstemp2(filenameBuffer); if (rc < 0) - asprintfN(errorP, - "Unable to create temporary file according to name " - "pattern '%s'. mkstemp() failed with errno %d (%s)", - filenameTemplate, errno, strerror(errno)); + pm_asprintf(errorP, + "Unable to create temporary file according to name " + "pattern '%s'. mkstemp() failed with errno %d (%s)", + filenameTemplate, errno, strerror(errno)); else { *fdP = rc; *filenameP = filenameBuffer; *errorP = NULL; } if (*errorP) - strfree(filenameBuffer); + pm_strfree(filenameBuffer); } } @@ -255,13 +250,10 @@ pm_make_tmpfile_fd(int * const fdP, const char ** const filenameP) { const char * filenameTemplate; - unsigned int fnamelen; const char * tmpdir; const char * dirseparator; const char * error; - fnamelen = strlen(pm_progname) + 10; /* "/" + "_XXXXXX\0" */ - tmpdir = tmpDir(); if (tmpdir[strlen(tmpdir) - 1] == '/') @@ -269,20 +261,20 @@ pm_make_tmpfile_fd(int * const fdP, else dirseparator = "/"; - asprintfN(&filenameTemplate, "%s%s%s%s", - tmpdir, dirseparator, pm_progname, "_XXXXXX"); + pm_asprintf(&filenameTemplate, "%s%s%s%s", + tmpdir, dirseparator, pm_progname, "_XXXXXX"); - if (filenameTemplate == strsol) - asprintfN(&error, - "Unable to allocate storage for temporary file name"); + if (filenameTemplate == pm_strsol) + pm_asprintf(&error, + "Unable to allocate storage for temporary file name"); else { makeTmpfileWithTemplate(filenameTemplate, fdP, filenameP, &error); - strfree(filenameTemplate); + pm_strfree(filenameTemplate); } if (error) { pm_errormsg("%s", error); - strfree(error); + pm_strfree(error); pm_longjmp(); } } @@ -302,7 +294,7 @@ pm_make_tmpfile(FILE ** const filePP, if (*filePP == NULL) { close(fd); unlink(*filenameP); - strfree(*filenameP); + pm_strfree(*filenameP); pm_error("Unable to create temporary file. " "fdopen() failed with errno %d (%s)", @@ -312,17 +304,130 @@ pm_make_tmpfile(FILE ** const filePP, +bool const canUnlinkOpen = +#if CAN_UNLINK_OPEN + 1 +#else + 0 +#endif + ; + + + +typedef struct UnlinkListEntry { +/*---------------------------------------------------------------------------- + This is an entry in the linked list of files to close and unlink as the + program exits. +-----------------------------------------------------------------------------*/ + struct UnlinkListEntry * next; + int fd; + char fileName[1]; /* Actually variable length */ +} UnlinkListEntry; + +static UnlinkListEntry * unlinkListP; + + + +static void +unlinkTempFiles(void) { +/*---------------------------------------------------------------------------- + Close and unlink (so presumably delete) the files in the list + *unlinkListP. + + This is an atexit function. +-----------------------------------------------------------------------------*/ + while (unlinkListP) { + UnlinkListEntry * const firstEntryP = unlinkListP; + + unlinkListP = unlinkListP->next; + + close(firstEntryP->fd); + unlink(firstEntryP->fileName); + + free(firstEntryP); + } +} + + + +static UnlinkListEntry * +newUnlinkListEntry(const char * const fileName, + int const fd) { + + UnlinkListEntry * const unlinkListEntryP = + malloc(sizeof(*unlinkListEntryP) + strlen(fileName) + 1); + + if (unlinkListEntryP) { + strcpy(unlinkListEntryP->fileName, fileName); + unlinkListEntryP->fd = fd; + unlinkListEntryP->next = NULL; + } + return unlinkListEntryP; +} + + + +static void +addUnlinkListEntry(const char * const fileName, + int const fd) { + + UnlinkListEntry * const unlinkListEntryP = + newUnlinkListEntry(fileName, fd); + + if (unlinkListEntryP) { + unlinkListEntryP->next = unlinkListP; + unlinkListP = unlinkListEntryP; + } +} + + + +static void +scheduleUnlinkAtExit(const char * const fileName, + int const fd) { +/*---------------------------------------------------------------------------- + Set things up to have the file unlinked as the program exits. + + This is messy and probably doesn't work in all situations; it is a hack + to get Unix code essentially working on Windows, without messing up the + code too badly for Unix. +-----------------------------------------------------------------------------*/ + static bool unlinkListEstablished = false; + + if (!unlinkListEstablished) { + atexit(unlinkTempFiles); + unlinkListP = NULL; + unlinkListEstablished = true; + } + + addUnlinkListEntry(fileName, fd); +} + + + +static void +arrangeUnlink(const char * const fileName, + int const fd) { + + if (canUnlinkOpen) + unlink(fileName); + else + scheduleUnlinkAtExit(fileName, fd); +} + + + FILE * pm_tmpfile(void) { FILE * fileP; - const char * tmpfile; + const char * tmpfileNm; - pm_make_tmpfile(&fileP, &tmpfile); + pm_make_tmpfile(&fileP, &tmpfileNm); - unlink(tmpfile); + arrangeUnlink(tmpfileNm, fileno(fileP)); - strfree(tmpfile); + pm_strfree(tmpfileNm); return fileP; } @@ -333,13 +438,13 @@ int pm_tmpfile_fd(void) { int fd; - const char * tmpfile; + const char * tmpfileNm; - pm_make_tmpfile_fd(&fd, &tmpfile); + pm_make_tmpfile_fd(&fd, &tmpfileNm); - unlink(tmpfile); + arrangeUnlink(tmpfileNm, fd); - strfree(tmpfile); + pm_strfree(tmpfileNm); return fd; } @@ -557,6 +662,23 @@ pm_readbiglong(FILE * const ifP, int +pm_readbiglong2(FILE * const ifP, + int32_t * const lP) { + int rc; + long l; + + rc = pm_readbiglong(ifP, &l); + + assert((int32_t)l == l); + + *lP = (int32_t)l; + + return rc; +} + + + +int pm_writebiglong(FILE * const ofP, long const l) { @@ -615,6 +737,23 @@ pm_readlittlelong(FILE * const ifP, int +pm_readlittlelong2(FILE * const ifP, + int32_t * const lP) { + int rc; + long l; + + rc = pm_readlittlelong(ifP, &l); + + assert((int32_t)l == l); + + *lP = (int32_t)l; + + return rc; +} + + + +int pm_writelittlelong(FILE * const ofP, long const l) { @@ -892,10 +1031,6 @@ pm_check(FILE * const file, pm_filepos curpos; /* Current position of file; -1 if none */ int rc; -#ifdef LARGEFILEDEBUG - pm_message("pm_filepos received by pm_check() is %u bytes.", - sizeof(pm_filepos)); -#endif /* Note: FTELLO() is either ftello() or ftell(), depending on the capabilities of the underlying C library. It is defined in pm_config.h. ftello(), in turn, may be either ftell() or diff --git a/lib/pnm.h b/lib/pnm.h index e4bd34a8..3b490552 100644 --- a/lib/pnm.h +++ b/lib/pnm.h @@ -20,6 +20,9 @@ typedef pixval xelval; #define PNM_OVERALLMAXVAL PPM_OVERALLMAXVAL #define PNM_MAXMAXVAL PPM_MAXMAXVAL #define PNM_GET1(x) PPM_GETB(x) +#define PNM_GETR(x) PPM_GETR(x) +#define PNM_GETG(x) PPM_GETG(x) +#define PNM_GETB(x) PPM_GETB(x) #define PNM_ASSIGN1(x,v) PPM_ASSIGN(x,0,0,v) #define PNM_ASSIGN(x,r,g,b) PPM_ASSIGN(x,r,g,b) #define PNM_EQUAL(x,y) PPM_EQUAL(x,y) @@ -34,7 +37,7 @@ pnm_init(int * const argcP, char ** const argv); void -pnm_nextimage(FILE *file, int * const eofP); +pnm_nextimage(FILE * const file, int * const eofP); xel * pnm_allocrow(unsigned int const cols); diff --git a/lib/ppm.h b/lib/ppm.h index 719189bf..82241b70 100644 --- a/lib/ppm.h +++ b/lib/ppm.h @@ -3,6 +3,7 @@ #ifndef _PPM_H_ #define _PPM_H_ +#include <netpbm/pm_config.h> #include <netpbm/pm.h> #include <netpbm/pgm.h> @@ -76,7 +77,7 @@ ppm_blackpixel(void) { return retval; } -void ppm_init(int * argcP, char* argv[]); +void ppm_init(int * const argcP, char ** const argv); #define ppm_allocarray(cols, rows) \ ((pixel**) pm_allocarray(cols, rows, sizeof(pixel))) @@ -250,6 +251,12 @@ ppm_hsv_from_color(pixel const color, pixval const maxval); static __inline__ pixval +ppm_luminosity(pixel const p) { + + return (pixval)(PPM_LUMIN(p) + 0.5); +} + +static __inline__ pixval ppm_colorvalue(pixel const p) { /*---------------------------------------------------------------------------- The color value (V is HSV) as a pixval diff --git a/lib/ppmdfont.c b/lib/ppmdfont.c index c0db3f51..f64cd10f 100644 --- a/lib/ppmdfont.c +++ b/lib/ppmdfont.c @@ -3,8 +3,8 @@ #include <errno.h> #include <string.h> +#include "netpbm/mallocvar.h" #include "pm.h" -#include "mallocvar.h" #include "ppmdfont.h" diff --git a/lib/ppmdraw.h b/lib/ppmdraw.h index d7a02e79..df22b44d 100644 --- a/lib/ppmdraw.h +++ b/lib/ppmdraw.h @@ -8,6 +8,8 @@ ** a single point, and it looks like this: */ +#include <netpbm/pm_config.h> + #ifdef __cplusplus extern "C" { #endif diff --git a/lib/rgb.txt b/lib/rgb.txt index ad4d9d87..28231080 100644 --- a/lib/rgb.txt +++ b/lib/rgb.txt @@ -876,6 +876,16 @@ 139 0 0 DarkRed 144 238 144 LightGreen +# From Wikipedia article on Ochre, 10.04.21 + +204 119 34 Ochre + +# From Wikipedia article on Azure, 15.11.30 +# Note that this is much different from the older colors of that name in this +# file. + + 0 127 255 Azure5 + # These were more or less invented for use with Netpbm: 255 255 255 D65 diff --git a/lib/standardppmdfont.c b/lib/standardppmdfont.c index aa4707e2..bc0c8ff8 100644 --- a/lib/standardppmdfont.c +++ b/lib/standardppmdfont.c @@ -1,4 +1,4 @@ -/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a ppmfont file. */ +/* THIS FILE WAS GENERATED BY 'ppmdcfont' from a ppmdfont file. */ #include "ppmdfont.h" diff --git a/lib/util/Makefile b/lib/util/Makefile index 8b3fa1c1..02119edf 100644 --- a/lib/util/Makefile +++ b/lib/util/Makefile @@ -5,23 +5,35 @@ endif SUBDIR = lib/util VPATH=.:$(SRCDIR)/$(SUBDIR) +default:all + include $(BUILDDIR)/config.mk # nstring is required for asprintf(), etc. Also some systems don't have # snprintf(), e.g. Solaris 2.5.1. 2002.03.29. -UTILOBJECTS = shhopt.o nstring.o vasprintf.o filename.o nsleep.o +UTILOBJECTS = \ + bitio.o \ + filename.o \ + io.o \ + mallocvar.o \ + matrix.o \ + nsleep.o \ + nstring.o \ + runlength.o \ + shhopt.o \ + token.o \ + vasprintf.o \ MERGE_OBJECTS = -all: $(UTILOBJECTS) - include $(SRCDIR)/common.mk -INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc +all: $(UTILOBJECTS) + +$(UTILOBJECTS): CFLAGS_TARGET=$(CFLAGS_SHLIB) $(UTILOBJECTS):%.o:%.c importinc - $(CC) -c $(INCLUDES) -DNDEBUG $(CPPFLAGS) $(CFLAGS) $(CFLAGS_SHLIB) \ - $(CFLAGS_PERSONAL) $(CADD) -o $@ $< + $(CC) -c $(INCLUDES) $(CFLAGS_ALL) -o $@ $< testnstring: test.c nstring.h nstring.o - $(CC) $(CFLAGS) $(CADD) -o $@ nstring.o $< + $(CC) $(CFLAGS_ALL) -o $@ nstring.o $< diff --git a/lib/bitio.c b/lib/util/bitio.c index ca1b55f9..ca1b55f9 100644 --- a/lib/bitio.c +++ b/lib/util/bitio.c diff --git a/lib/bitio.h b/lib/util/bitio.h index dfc5a153..dfc5a153 100644 --- a/lib/bitio.h +++ b/lib/util/bitio.h diff --git a/lib/util/filename.c b/lib/util/filename.c index e3a9a89f..18c12e3c 100644 --- a/lib/util/filename.c +++ b/lib/util/filename.c @@ -20,7 +20,7 @@ pm_basename(const char * const fileName) { if (fileName[i] == '/') basenameStart = i+1; } - asprintfN(&retval, "%s", &fileName[basenameStart]); + pm_asprintf(&retval, "%s", &fileName[basenameStart]); return retval; } diff --git a/lib/util/floatcode.h b/lib/util/floatcode.h index 23c57e9b..dc31d038 100644 --- a/lib/util/floatcode.h +++ b/lib/util/floatcode.h @@ -124,7 +124,7 @@ pm_doubleFromBigendDouble(pm_bigendDouble const arg) { }; break; case LITTLE_ENDIAN: { union { - unsigned char bytes[4]; + unsigned char bytes[8]; double native; } converter; @@ -163,7 +163,7 @@ pm_bigendDoubleFromDouble(double const arg) { } break; case LITTLE_ENDIAN: { union { - unsigned char bytes[4]; + unsigned char bytes[8]; double native; } converter; diff --git a/lib/util/intcode.h b/lib/util/intcode.h index dd42f669..1066ee9b 100644 --- a/lib/util/intcode.h +++ b/lib/util/intcode.h @@ -188,8 +188,6 @@ typedef struct { This is an important data type because decent file formats use big-endian -- they don't care if some CPU happens to use some other code for its own work. - - uint64_t is supported only when available. -----------------------------------------------------------------------------*/ unsigned char bytes[8]; } bigend64; diff --git a/lib/util/io.c b/lib/util/io.c new file mode 100644 index 00000000..54ecb6a8 --- /dev/null +++ b/lib/util/io.c @@ -0,0 +1,92 @@ +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#include "mallocvar.h" +#include "nstring.h" + +#include "io.h" + + +void +pm_freadline(FILE * const fileP, + const char ** const lineP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Read a line (assuming the file is text with lines delimited by newlines) + from file *fileP. Return that line in newly malloced storage as *lineP. + + The newline delimiter is not part of the line. + + As a special case, if the file doesn't end in a newline, the characters + after the last newline are a line. + + If there are no more lines in the file, return *lineP == NULL. +-----------------------------------------------------------------------------*/ + char * buffer; + size_t bufferSize; + size_t cursor; + bool gotLine; + bool eof; + + bufferSize = 1024; /* initial value */ + *errorP = NULL; /* initial value */ + + MALLOCARRAY(buffer, bufferSize); + + for (cursor = 0, gotLine = false, eof = false; + !gotLine && !eof && !*errorP; ) { + if (cursor + 1 >= bufferSize) { + if (bufferSize > INT_MAX/2) { + free(buffer); + buffer = NULL; + } else { + bufferSize *= 2; + REALLOCARRAY(buffer, bufferSize); + } + } + + if (!buffer) + pm_asprintf(errorP, + "Couldn't get memory for a %u-byte file read buffer.", + (unsigned int)bufferSize); + else { + int const rc = getc(fileP); + + if (rc < 0) { + if (feof(fileP)) + eof = true; + else + pm_asprintf(errorP, + "Failed to read a character from file. " + "Errno = %d (%s)", + errno, strerror(errno)); + } else { + char const c = (char)rc; + + if (c == '\n') + gotLine = true; + else { + buffer[cursor++] = c; + } + } + } + } + if (*errorP) { + if (buffer) + free(buffer); + } else { + if (eof && cursor == 0) { + *lineP = NULL; + free(buffer); + } else { + assert(cursor < bufferSize); + buffer[cursor++] = '\0'; + + *lineP = buffer; + } + } +} + + diff --git a/lib/util/io.h b/lib/util/io.h new file mode 100644 index 00000000..39ccd14b --- /dev/null +++ b/lib/util/io.h @@ -0,0 +1,11 @@ +#ifndef IO_H_INCLUDED +#define IO_H_INCLUDED + +#include <stdio.h> + +void +pm_freadline(FILE * const fileP, + const char ** const lineP, + const char ** const errorP); + +#endif diff --git a/lib/util/mallocvar.c b/lib/util/mallocvar.c new file mode 100644 index 00000000..339d5d61 --- /dev/null +++ b/lib/util/mallocvar.c @@ -0,0 +1,185 @@ +#include <limits.h> +#include <stdlib.h> + +#include "pm_c_util.h" +#include "nstring.h" + +#include "mallocvar.h" + + +static void * +mallocz(size_t const size) { +/*---------------------------------------------------------------------------- + Same as malloc(), except it is legal to allocate zero bytes. +-----------------------------------------------------------------------------*/ + return malloc(MAX(1, size)); +} + + + +static void +allocarrayNoHeap(void ** const rowIndex, + unsigned int const rows, + unsigned int const cols, + unsigned int const elementSize, + bool * const failedP) { + + unsigned int rowsDone; + + for (rowsDone = 0, *failedP = false; rowsDone < rows && !*failedP; ) { + void * rowSpace; + + mallocProduct(&rowSpace, cols, elementSize); + + if (rowSpace == NULL) { + unsigned int row; + + *failedP = true; + + /* unwind partially completed job */ + for (row = 0; row < rowsDone; ++row) + free(rowIndex[row]); + } else + rowIndex[rowsDone++] = rowSpace; + } +} + + + +static unsigned char * +allocRowHeap(unsigned int const rows, + unsigned int const cols, + unsigned int const size) { +/*---------------------------------------------------------------------------- + Allocate a row heap. That's a chunk of memory for use in a + pm_mallocarray2 two-dimensional array to contain the rows. + + The heap must fit 'rows' rows of 'cols' columns each of elements + 'size' bytes in size. + + Return NULL if we can't get the memory. +-----------------------------------------------------------------------------*/ + unsigned char * retval; + + if (cols != 0 && rows != 0 && UINT_MAX / cols / rows < size) + /* Too big even to request the memory ! */ + retval = NULL; + else + retval = mallocz(rows * cols * size); + + return retval; +} + + + +void +pm_mallocarray2(void ** const resultP, + unsigned int const rows, + unsigned int const cols, + unsigned int const elementSize) { +/*---------------------------------------------------------------------------- + Allocate an array of 'rows' rows of 'cols' columns each, with each + element 'size' bytes. + + We use the C multidimensional array paradigm: The array is a row + index (array of pointers to rows) plus an array of elements for each + of those rows. So a[row][col] gives you the element of the two + dimensional array at Row 'row', Column 'col'. + + Each array element is ideally aligned to an 'elementSize' boundary. But we + guarantee this only for 1, 2, 4, 8, and 16, because of limitations of libc + malloc() (which we use to allocate all the memory). + + We tack on two extra elements to the end of the row index, transparent to + the user, for use in memory management (in particular, in destroying the + array). The first is a NULL pointer, so you can tell the vertical + dimension of the array. The second points to the row heap (see below). + + We have two ways of allocating the space: fragmented and unfragmented. In + both, the row index (plus the extra elements) is in one block of memory. + In the fragmented format, each row is also in an independent memory block, + and the row heap pointer (see above) is NULL. In the unfragmented format, + all the rows are in a single block of memory called the row heap and the + row heap pointer points to that. + + We use unfragmented format if possible, but if the allocation of the + row heap fails, we fall back to fragmented. +-----------------------------------------------------------------------------*/ + void ** rowIndex; + bool failed; + + MALLOCARRAY(rowIndex, rows + 1 + 1); + if (rowIndex == NULL) + failed = true; + else { + unsigned char * rowheap; + + rowheap = allocRowHeap(cols, rows, elementSize); + + if (rowheap) { + /* It's unfragmented format */ + + rowIndex[rows+1] = rowheap; /* Declare it unfragmented format */ + + if (rowheap) { + unsigned int row; + + for (row = 0; row < rows; ++row) + rowIndex[row] = &(rowheap[row * cols * elementSize]); + } + failed = false; + } else { + /* We couldn't get the whole heap in one block, so try fragmented + format. + */ + rowIndex[rows+1] = NULL; /* Declare it fragmented format */ + + allocarrayNoHeap(rowIndex, rows, cols, elementSize, &failed); + } + rowIndex[rows+0] = NULL; /* mark end of rows */ + } + if (failed) + *resultP = NULL; + else + *resultP = rowIndex; +} + + + +static unsigned int +array2RowCount(void ** const rowIndex) { +/*---------------------------------------------------------------------------- + Return the number of rows in the 2-dimensional array. +-----------------------------------------------------------------------------*/ + /* The end of the rows is marked by a null pointer where a row + pointer otherwise would be. + */ + + unsigned int row; + + for (row = 0; rowIndex[row]; ++row); + + return row; +} + + + +void +pm_freearray2(void ** const rowIndex) { + + unsigned int const rows = array2RowCount(rowIndex); + + void * const rowheap = rowIndex[rows+1]; + + if (rowheap != NULL) + free(rowheap); + else { + /* Free each individually malloced row */ + unsigned int row; + for (row = 0; row < rows; ++row) + pm_freerow(rowIndex[row]); + } + free(rowIndex); +} + + diff --git a/lib/util/mallocvar.h b/lib/util/mallocvar.h index 1f2be127..e92e3fe4 100644 --- a/lib/util/mallocvar.h +++ b/lib/util/mallocvar.h @@ -87,7 +87,7 @@ reallocProduct(void ** const blockP, void * array; \ array = arrayName; \ reallocProduct(&array, nElements, sizeof(arrayName[0])); \ - if (!array) \ + if (!array && arrayName) \ free(arrayName); \ arrayName = array; \ } while (0) @@ -107,6 +107,21 @@ do { \ abort(); \ } while(0) +#define MALLOCARRAY2(arrayName, nRows, nCols) do { \ + void * array; \ + pm_mallocarray2(&array, nRows, nCols, sizeof(arrayName[0][0])); \ + arrayName = array; \ +} while (0) + +#define MALLOCARRAY2_NOFAIL(arrayName, nRows, nCols) do { \ + MALLOCARRAY2(arrayName, nRows, nCols); \ + if ((arrayName) == NULL) \ + abort(); \ +} while (0) + +void +pm_freearray2(void ** const rowIndex); + #define MALLOCVAR(varName) \ varName = malloc(sizeof(*varName)) @@ -114,6 +129,12 @@ do { \ #define MALLOCVAR_NOFAIL(varName) \ do {if ((varName = malloc(sizeof(*varName))) == NULL) abort();} while(0) +void +pm_mallocarray2(void ** const resultP, + unsigned int const cols, + unsigned int const rows, + unsigned int const elementSize); + #ifdef __cplusplus } #endif diff --git a/lib/util/matrix.c b/lib/util/matrix.c new file mode 100644 index 00000000..e9456e93 --- /dev/null +++ b/lib/util/matrix.c @@ -0,0 +1,223 @@ +/*============================================================================= + matrix +=============================================================================== + + Matrix math. + +=============================================================================*/ + +#include <assert.h> +#include <math.h> +#include <stdlib.h> +#include <stdio.h> + +#include "pm_c_util.h" +#include "mallocvar.h" +#include "nstring.h" + +#include "matrix.h" + + +static double const epsilon = 1e-10; + + + +static void +swap(double * const aP, + double * const bP) { + + double const oldA = *aP; + + *aP = *bP; + *bP = oldA; +} + + + +static void +initializeWorkMatrices(unsigned int const n, + double ** const aInit, + const double * const cInit, + double *** const aP, + double ** const cP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Allocate memory for an n x n matrix, initialize it to the value of + aInit[], and return it as *aP. + + Allocate memory for an n x 1 matrix, initialize it to the value of + cInit[], and return it as *cP. +-----------------------------------------------------------------------------*/ + double ** a; + double * c; + + MALLOCARRAY2(a, n, n); + if (a == NULL) + pm_asprintf(errorP, "Could not get memory for a %u x %u matrix", n, n); + else { + unsigned int i; + for (i = 0; i < n; ++i) { + unsigned int j; + for (j = 0; j < n; ++j) + a[i][j] = aInit[i][j]; + } + MALLOCARRAY(c, n); + if (c == NULL) + pm_asprintf(errorP, "Could not get memory for a %u x 1 matrix", n); + else { + unsigned int i; + for (i = 0; i < n; ++i) + c[i] = cInit[i]; + + *errorP = NULL; + } + if (*errorP) + free(a); + } + *aP = a; + *cP = c; +} + + + +static void +findLargestIthCoeff(unsigned int const n, + double ** const a, + unsigned int const i, + unsigned int * const istarP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Among the 'i'th and following rows in 'a' (which has 'n' total rows), + find the one with the largest 'i'th column. + + And it had better be greater than zero; if not, we fail (return *errorP + non-null). + + Return its index as *istarP. +-----------------------------------------------------------------------------*/ + double maxSoFar; + unsigned int maxIdx; + unsigned int ii; + + for (ii = i, maxSoFar = 0.0; ii < n; ++ii) { + double const thisA = fabs(a[ii][i]); + if (thisA >= maxSoFar) { + maxIdx = ii; + maxSoFar = thisA; + } + } + if (maxSoFar < epsilon) { + const char * const baseMsg = "Matrix equation has no unique solution"; + if (pm_have_float_format()) + pm_asprintf(errorP, "%s. (debug: coeff %u %e < %e)", + baseMsg, i, maxSoFar, epsilon); + else + pm_asprintf(errorP, "%s", baseMsg); + } else { + *istarP = maxIdx; + *errorP = NULL; + } +} + + + +static void +eliminateOneUnknown(unsigned int const i, + unsigned int const n, + double ** const a, + double * const c, + const char ** const errorP) { + + unsigned int maxRow; + + findLargestIthCoeff(n, a, i, &maxRow, errorP); + + if (!*errorP) { + /* swap rows 'i' and 'maxRow' in 'a' and 'c', so that the ith + row has the largest ith coefficient. + */ + unsigned int j; + for (j = 0; j < n; j++) + swap(&a[maxRow][j], &a[i][j]); + + swap(&c[maxRow], &c[i]); + + /* Combine rows so that the ith coefficient in every row below + the ith is zero. + */ + { + unsigned int ii; + for (ii = i+1; ii < n; ++ii) { + double const multiplier = a[ii][i] / a[i][i]; + /* This is what we multiply the whole ith row by to make + its ith coefficient equal to that in the iith row. + */ + unsigned int j; + + /* Combine ith row into iith row so that the ith coefficient + in the iith is zero. + */ + c[ii] = c[ii] - multiplier * c[i]; + + for (j = 0; j < n; ++j) + a[ii][j] = a[ii][j] - multiplier * a[i][j]; + + assert(a[ii][i] < epsilon); + } + } + *errorP = NULL; + } +} + + + +void +pm_solvelineareq(double ** const aArg, + double * const x, + double * const cArg, + unsigned int const n, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Solve the matrix equation 'a' * 'x' = 'c' for 'x'. + + 'n' is the dimension of the matrices. 'a' is 'n' x 'n', + while 'x' and 'c' are 'n' x 1. +-----------------------------------------------------------------------------*/ + /* We use Gaussian reduction. */ + + double ** a; + double * c; + + initializeWorkMatrices(n, aArg, cArg, &a, &c, errorP); + + if (!*errorP) { + unsigned int i; + + for (i = 0, *errorP = NULL; i < n && !*errorP; ++i) + eliminateOneUnknown(i, n, a, c, errorP); + + if (!*errorP) { + /* a[] now has all zeros in the lower left triangle. */ + /* Work from the bottom up to solve for the unknowns x[], from + the a and c rows in question and all the x[] below it + */ + + unsigned int k; + for (k = 0; k < n; ++k) { + unsigned int const m = n - k - 1; + unsigned int j; + double xwork; + + for (j = m+1, xwork = c[m]; j < n; ++j) + xwork -= a[m][j] * x[j]; + + x[m] = xwork / a[m][m]; + } + } + } + pm_freearray2((void**)a); + free(c); +} + + + diff --git a/lib/util/matrix.h b/lib/util/matrix.h new file mode 100644 index 00000000..13ae0373 --- /dev/null +++ b/lib/util/matrix.h @@ -0,0 +1,11 @@ +#ifndef MATRIX_H_INCLUDED +#define MATRIX_H_INCLUDED + +void +pm_solvelineareq(double ** const aArg, + double * const x, + double * const cArg, + unsigned int const n, + const char ** const errorP); + +#endif diff --git a/lib/util/nsleep.c b/lib/util/nsleep.c index 943b8c77..24d48207 100644 --- a/lib/util/nsleep.c +++ b/lib/util/nsleep.c @@ -1,4 +1,6 @@ -#ifdef WIN32 +#include "pm_config.h" + +#if MSVCRT #include <windows.h> #include <process.h> #else @@ -10,9 +12,9 @@ void -sleepN(unsigned int const milliseconds) { +pm_sleep(unsigned int const milliseconds) { -#ifdef WIN32 +#if MSVCRT SleepEx(milliseconds, TRUE); #else diff --git a/lib/util/nsleep.h b/lib/util/nsleep.h index 372b8008..fdf45ab7 100644 --- a/lib/util/nsleep.h +++ b/lib/util/nsleep.h @@ -2,6 +2,6 @@ #define NSLEEP_H_INCLUDED void -sleepN(unsigned int const milliseconds); +pm_sleep(unsigned int const milliseconds); #endif diff --git a/lib/util/nstring.c b/lib/util/nstring.c index 8842aa05..711cfca9 100644 --- a/lib/util/nstring.c +++ b/lib/util/nstring.c @@ -39,7 +39,7 @@ * IMPLEMENTED CONVERSION SPECIFIERS AND DATA TYPES * * This snprintf implements only the following conversion specifiers: - * s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) + * s, c, d, u, o, x, X, p, f (and synonyms: i, D, U, O - see below) * with flags: '-', '+', ' ', '0' and '#'. * An asterisk is acceptable for field width as well as precision. * @@ -66,7 +66,7 @@ * * The following is specifically NOT implemented: * - flag ' (thousands' grouping character) is recognized but ignored - * - numeric conversion specifiers: f, e, E, g, G and synonym F, + * - numeric conversion specifiers: e, E, g, G and synonym F, * as well as the new a and A conversion specifiers * - length modifier 'L' (long double) and 'q' (quad - use 'll' instead) * - wide character/string conversions: lc, ls, and nonstandard @@ -113,6 +113,12 @@ */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ +#define _BSD_SOURCE /* Make sure strdup() is in string.h */ +#define _GNU_SOURCE + /* Because of conditional compilation, this is GNU source only if the C + library is GNU. + */ #define PORTABLE_SNPRINTF_VERSION_MAJOR 2 #define PORTABLE_SNPRINTF_VERSION_MINOR 2 @@ -190,12 +196,12 @@ static char credits[] = "\n\ void -vsnprintfN(char * const str, - size_t const str_m, - const char * const fmt, - va_list ap, - size_t * const sizeP) { - +pm_vsnprintf(char * const str, + size_t const str_m, + const char * const fmt, + va_list ap, + size_t * const sizeP) { + size_t str_l = 0; const char *p = fmt; @@ -215,16 +221,19 @@ vsnprintfN(char * const str, const char *q = strchr(p + 1,'%'); size_t n = !q ? strlen(p) : (q - p); if (str_l < str_m) { - size_t avail = str_m - str_l; - fast_memcpy(str + str_l, p, (n > avail ? avail : n)); + size_t const avail = str_m - str_l; + fast_memcpy(str + str_l, p, (MIN(n, avail))); } p += n; str_l += n; } else { const char *starting_p; - size_t min_field_width = 0, precision = 0; - int zero_padding = 0, precision_specified = 0, justify_left = 0; - int alternate_form = 0, force_sign = 0; - int space_for_positive = 1; + size_t min_field_width; + size_t precision = 0; + bool precision_specified; + bool justify_left; + bool alternate_form; + bool force_sign; + bool space_for_positive; /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored. */ @@ -242,16 +251,18 @@ vsnprintfN(char * const str, argument for the c conversion is unsigned. */ - size_t number_of_zeros_to_pad = 0; + bool zero_padding; + + size_t number_of_zeros_to_pad; /* number of zeros to be inserted for numeric conversions as required by the precision or minimal field width */ - size_t zero_padding_insertion_ind = 0; + size_t zero_padding_insertion_ind; /* index into tmp where zero padding is to be inserted */ - char fmt_spec = '\0'; + char fmt_spec; /* current conversion specifier character */ str_arg = credits; @@ -261,17 +272,26 @@ vsnprintfN(char * const str, ++p; /* skip '%' */ /* parse flags */ + justify_left = false; /* initial value */ + alternate_form = false; /* initial value */ + force_sign = false; /* initial value */ + space_for_positive = false; /* initial value */ + zero_padding = false; /* initial value */ + number_of_zeros_to_pad = 0; /* initial value */ + zero_padding_insertion_ind = 0; /* initial value */ + fmt_spec = '\0'; /* initial value */ + while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '\'') { switch (*p) { - case '0': zero_padding = 1; break; - case '-': justify_left = 1; break; - case '+': force_sign = 1; space_for_positive = 0; break; - case ' ': force_sign = 1; break; + case '0': zero_padding = true; break; + case '-': justify_left = true; break; + case '+': force_sign = true; space_for_positive = false; break; + case ' ': force_sign = true; break; /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */ - case '#': alternate_form = 1; break; + case '#': alternate_form = true; break; case '\'': break; } ++p; @@ -283,9 +303,10 @@ vsnprintfN(char * const str, /* parse field width */ if (*p == '*') { int j; - p++; j = va_arg(ap, int); - if (j >= 0) min_field_width = j; - else { min_field_width = -j; justify_left = 1; } + ++p; + j = va_arg(ap, int); + if (j >= 0) { min_field_width = j; justify_left = false; } + else { min_field_width = -j; justify_left = true; } } else if (isdigit((int)(*p))) { /* size_t could be wider than unsigned int; make sure we treat argument like common implementations do @@ -294,16 +315,19 @@ vsnprintfN(char * const str, while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); min_field_width = uj; - } + } else + min_field_width = 0; + /* parse precision */ if (*p == '.') { - p++; precision_specified = 1; + ++p; + precision_specified = true; if (*p == '*') { int j = va_arg(ap, int); p++; if (j >= 0) precision = j; else { - precision_specified = 0; precision = 0; + precision_specified = false; precision = 0; /* NOTE: Solaris 2.6 man page claims that in this case the precision should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page @@ -322,7 +346,9 @@ vsnprintfN(char * const str, uj = 10*uj + (unsigned int)(*p++ - '0'); precision = uj; } - } + } else + precision_specified = false; + /* parse 'h', 'l' and 'll' length modifiers */ if (*p == 'h' || *p == 'l') { length_modifier = *p; p++; @@ -357,7 +383,7 @@ vsnprintfN(char * const str, Unix and Linux does not. */ - zero_padding = 0; + zero_padding = false; /* turn zero padding off for string conversions */ str_arg_l = 1; switch (fmt_spec) { @@ -387,9 +413,7 @@ vsnprintfN(char * const str, else { /* memchr on HP does not like n > 2^31 !!! */ const char * q = - memchr(str_arg, '\0', - precision <= 0x7fffffff ? - precision : 0x7fffffff); + memchr(str_arg, '\0', MIN(precision, 0x7fffffff)); str_arg_l = !q ? precision : (q-str_arg); } break; @@ -483,7 +507,7 @@ vsnprintfN(char * const str, Perl. */ if (precision_specified) - zero_padding = 0; + zero_padding = false; if (fmt_spec == 'd') { if (force_sign && arg_sign >= 0) tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; @@ -549,9 +573,9 @@ vsnprintfN(char * const str, */ if (zero_padding_insertion_ind < str_arg_l && tmp[zero_padding_insertion_ind] == '-') { - zero_padding_insertion_ind++; + zero_padding_insertion_ind += 1; } - if (zero_padding_insertion_ind+1 < str_arg_l && + if (zero_padding_insertion_ind + 1 < str_arg_l && tmp[zero_padding_insertion_ind] == '0' && (tmp[zero_padding_insertion_ind+1] == 'x' || tmp[zero_padding_insertion_ind+1] == 'X') ) { @@ -559,7 +583,7 @@ vsnprintfN(char * const str, } } { - size_t num_of_digits = + size_t const num_of_digits = str_arg_l - zero_padding_insertion_ind; if (alternate_form && fmt_spec == 'o' /* unless zero is already the first character */ @@ -576,7 +600,7 @@ vsnprintfN(char * const str, explicit precision of zero */ precision = num_of_digits+1; - precision_specified = 1; + precision_specified = true; } } /* zero padding to specified precision? */ @@ -585,23 +609,37 @@ vsnprintfN(char * const str, } /* zero padding to specified minimal field width? */ if (!justify_left && zero_padding) { - int n = + int const n = min_field_width - (str_arg_l+number_of_zeros_to_pad); - if (n > 0) number_of_zeros_to_pad += n; + if (n > 0) + number_of_zeros_to_pad += n; } } break; + case 'f': { + char f[10]; + if (precision_specified) + snprintf(f, ARRAY_SIZE(f), "%%%u.%uf", + (unsigned)min_field_width, (unsigned)precision); + else + snprintf(f, ARRAY_SIZE(f), "%%%uf", + (unsigned)min_field_width); + + str_arg_l = sprintf(tmp, f, va_arg(ap, double)); + str_arg = &tmp[0]; + + min_field_width = 0; + zero_padding_insertion_ind = 0; + } break; default: - /* unrecognized conversion specifier, keep format - string as-is + /* Unrecognized conversion specifier. Discard the + unrecognized conversion, just keep the unrecognized + conversion character. */ - zero_padding = 0; + zero_padding = false; /* turn zero padding off for non-numeric convers. */ /* reset flags */ - justify_left = 1; + justify_left = true; min_field_width = 0; - /* discard the unrecognized conversion, just keep the - unrecognized conversion character - */ str_arg = p; str_arg_l = 0; if (*p) @@ -620,12 +658,12 @@ vsnprintfN(char * const str, if (!justify_left) { /* left padding with blank or zero */ - int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + int n = min_field_width - (str_arg_l + number_of_zeros_to_pad); if (n > 0) { if (str_l < str_m) { - size_t avail = str_m-str_l; - fast_memset(str+str_l, (zero_padding ? '0' : ' '), - (n > avail ? avail : n)); + size_t const avail = str_m - str_l; + fast_memset(str + str_l, (zero_padding ? '0' : ' '), + (MIN(n, avail))); } str_l += n; } @@ -639,40 +677,44 @@ vsnprintfN(char * const str, */ zero_padding_insertion_ind = 0; } else { - /* insert first part of numerics (sign or '0x') before - zero padding - */ - int n = zero_padding_insertion_ind; - if (n > 0) { - if (str_l < str_m) { - size_t avail = str_m-str_l; - fast_memcpy(str+str_l, str_arg, (n>avail?avail:n)); + { + /* insert first part of numerics (sign or '0x') before + zero padding + */ + int const n = zero_padding_insertion_ind; + if (n > 0) { + if (str_l < str_m) { + size_t const avail = str_m - str_l; + fast_memcpy(str + str_l, str_arg, (MIN(n, avail))); + } + str_l += n; } - str_l += n; } - /* insert zero padding as requested by the precision - or min field width - */ - n = number_of_zeros_to_pad; - if (n > 0) { - if (str_l < str_m) { - size_t avail = str_m - str_l; - fast_memset(str + str_l, '0', (n > avail ? avail : n)); + { + /* insert zero padding as requested by the precision + or min field width + */ + int const n = number_of_zeros_to_pad; + if (n > 0) { + if (str_l < str_m) { + size_t const avail = str_m - str_l; + fast_memset(str + str_l, '0', (MIN(n, avail))); + } + str_l += n; } - str_l += n; } } /* insert formatted string (or as-is conversion specifier for unknown conversions) */ { - int n = str_arg_l - zero_padding_insertion_ind; + int const n = str_arg_l - zero_padding_insertion_ind; if (n > 0) { if (str_l < str_m) { - size_t avail = str_m-str_l; + size_t const avail = str_m-str_l; fast_memcpy(str + str_l, str_arg + zero_padding_insertion_ind, - (n > avail ? avail : n)); + MIN(n, avail)); } str_l += n; } @@ -680,11 +722,12 @@ vsnprintfN(char * const str, /* insert right padding */ if (justify_left) { /* right blank padding to the field width */ - int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + int const n = + min_field_width - (str_arg_l + number_of_zeros_to_pad); if (n > 0) { if (str_l < str_m) { - size_t avail = str_m-str_l; - fast_memset(str+str_l, ' ', (n>avail?avail:n)); + size_t const avail = str_m - str_l; + fast_memset(str+str_l, ' ', (MIN(n, avail))); } str_l += n; } @@ -696,7 +739,7 @@ vsnprintfN(char * const str, of overwriting the last character (shouldn't happen, but just in case) */ - str[str_l <= str_m-1 ? str_l : str_m-1] = '\0'; + str[MIN(str_l, str_m - 1)] = '\0'; } *sizeP = str_l; } @@ -704,17 +747,17 @@ vsnprintfN(char * const str, int -snprintfN(char * const dest, - size_t const str_m, - const char * const fmt, - ...) { +pm_snprintf(char * const dest, + size_t const str_m, + const char * const fmt, + ...) { size_t size; va_list ap; va_start(ap, fmt); - vsnprintfN(dest, str_m, fmt, ap, &size); + pm_vsnprintf(dest, str_m, fmt, ap, &size); va_end(ap); @@ -726,73 +769,96 @@ snprintfN(char * const dest, /* When a function that is supposed to return a malloc'ed string cannot - get the memory for it, it should return 'strsol'. That has a much + get the memory for it, it should return 'pm_strsol'. That has a much better effect on the caller, if the caller doesn't explicitly allow for the out of memory case, than returning NULL. Note that it is very rare for the system not to have enough memory to return a small string, so it's OK to have somewhat nonsensical behavior when it happens. We just don't want catastrophic behavior. - 'strsol' is an external symbol, so if Caller wants to detect the + 'pm_strsol' is an external symbol, so if Caller wants to detect the out-of-memory failure, he certainly can. */ -const char * const strsol = "NO MEMORY TO CREATE STRING!"; +const char * const pm_strsol = "NO MEMORY TO CREATE STRING!"; + + + +const char * +pm_strdup(const char * const arg) { + + const char * const dup = strdup(arg); + + return dup ? dup : pm_strsol; +} void PM_GNU_PRINTF_ATTR(2,3) -asprintfN(const char ** const resultP, - const char * const fmt, - ...) { +pm_asprintf(const char ** const resultP, + const char * const fmt, + ...) { + const char * result; va_list varargs; +#if HAVE_VASPRINTF + int rc; + va_start(varargs, fmt); + rc = vasprintf((char **)&result, fmt, varargs); + va_end(varargs); + if (rc < 0) + result = pm_strsol; +#else size_t dryRunLen; va_start(varargs, fmt); - - vsnprintfN(NULL, 0, fmt, varargs, &dryRunLen); + + pm_vsnprintf(NULL, 0, fmt, varargs, &dryRunLen); va_end(varargs); if (dryRunLen + 1 < dryRunLen) /* arithmetic overflow */ - *resultP = strsol; + result = pm_strsol; else { size_t const allocSize = dryRunLen + 1; - char * result; - result = malloc(allocSize); - if (result == NULL) - *resultP = strsol; - else { + char * buffer; + buffer = malloc(allocSize); + if (buffer != NULL) { va_list varargs; size_t realLen; va_start(varargs, fmt); - vsnprintfN(result, allocSize, fmt, varargs, &realLen); + pm_vsnprintf(buffer, allocSize, fmt, varargs, &realLen); assert(realLen == dryRunLen); va_end(varargs); - *resultP = result; + result = buffer; } } +#endif + + if (result == NULL) + *resultP = pm_strsol; + else + *resultP = result; } void -strfree(const char * const string) { +pm_strfree(const char * const string) { - if (string != strsol) + if (string != pm_strsol) free((void *) string); } const char * -strsepN(char ** const stringP, const char * const delim) { +pm_strsep(char ** const stringP, const char * const delim) { const char * retval; if (stringP == NULL || *stringP == NULL) @@ -824,65 +890,75 @@ strsepN(char ** const stringP, const char * const delim) { int -stripeq(const char * const comparand, - const char * const comparator) { +pm_stripeq(const char * const comparand, + const char * const comparator) { /*---------------------------------------------------------------------------- Compare two strings, ignoring leading and trailing white space. Return 1 (true) if the strings are identical; 0 (false) otherwise. -----------------------------------------------------------------------------*/ - char *p, *q, *px, *qx; - char equal; + const char * p; + const char * q; + const char * px; + const char * qx; + bool equal; /* Make p and q point to the first non-blank character in each string. - If there are no non-blank characters, make them point to the terminating - NULL. - */ + If there are no non-blank characters, make them point to the terminating + NUL. + */ - p = (char *) comparand; - while (ISSPACE(*p)) p++; - q = (char *) comparator; - while (ISSPACE(*q)) q++; + p = &comparand[0]; + while (ISSPACE(*p)) + p++; + q = &comparator[0]; + while (ISSPACE(*q)) + q++; /* Make px and qx point to the last non-blank character in each string. If there are no nonblank characters (which implies the string is - null), make them point to the terminating NULL. + null), make them point to the terminating NUL. */ - if (*p == '\0') px = p; + if (*p == '\0') + px = p; else { px = p + strlen(p) - 1; - while (ISSPACE(*px)) px--; + while (ISSPACE(*px)) + --px; } - if (*q == '\0') qx = q; + if (*q == '\0') + qx = q; else { qx = q + strlen(q) - 1; - while (ISSPACE(*qx)) qx--; + while (ISSPACE(*qx)) + --qx; } - equal = 1; /* initial assumption */ - - /* If the stripped strings aren't the same length, - we know they aren't equal - */ - if (px - p != qx - q) equal = 0; - - else - while (p <= px) { - if (*p != *q) equal = 0; - p++; q++; + if (px - p != qx - q) { + /* The stripped strings aren't the same length, so we know they aren't + equal. + */ + equal = false; + } else { + /* Move p and q through the nonblank characters, comparing. */ + for (equal = true; p <= px; ++p, ++q) { + assert(q <= qx); /* Because stripped strings are same length */ + if (*p != *q) + equal = false; + } } - return equal; + return equal ? 1 : 0; } const void * -memmemN(const void * const haystackArg, - size_t const haystacklen, - const void * const needleArg, - size_t const needlelen) { +pm_memmem(const void * const haystackArg, + size_t const haystacklen, + const void * const needleArg, + size_t const needlelen) { const unsigned char * const haystack = haystackArg; const unsigned char * const needle = needleArg; @@ -902,7 +978,7 @@ memmemN(const void * const haystackArg, bool -strishex(const char * const subject) { +pm_strishex(const char * const subject) { bool retval; unsigned int i; @@ -919,12 +995,12 @@ strishex(const char * const subject) { void -interpret_uint(const char * const string, - unsigned int * const valueP, - const char ** const errorP) { +pm_interpret_uint(const char * const string, + unsigned int * const valueP, + const char ** const errorP) { if (string[0] == '\0') - asprintfN(errorP, "Null string."); + pm_asprintf(errorP, "Null string."); else { /* strtoul() does a bizarre thing where if the number is out of range, it returns a clamped value but tells you about it @@ -939,13 +1015,13 @@ interpret_uint(const char * const string, ulongValue = strtoul(string, &tail, 10); if (tail[0] != '\0') - asprintfN(errorP, "Non-digit stuff in string: %s", tail); + pm_asprintf(errorP, "Non-digit stuff in string: %s", tail); else if (errno == ERANGE) - asprintfN(errorP, "Number too large"); + pm_asprintf(errorP, "Number too large"); else if (ulongValue > UINT_MAX) - asprintfN(errorP, "Number too large"); + pm_asprintf(errorP, "Number too large"); else if (string[0] == '-') - asprintfN(errorP, "Negative number"); + pm_asprintf(errorP, "Negative number"); /* Sleazy code; string may have leading spaces. */ else { *valueP = ulongValue; diff --git a/lib/util/nstring.h b/lib/util/nstring.h index 53f1e4c0..7238a76e 100644 --- a/lib/util/nstring.h +++ b/lib/util/nstring.h @@ -137,62 +137,68 @@ strncaseeq(const char * const comparand, - The returned string is a const char * instead of a char *. The const is more correct. - - If the function can't get the memory, it returns 'strsol', + - If the function can't get the memory, it returns 'pm_strsol', which is a string that is in static memory that contains text indicating an out of memory failure has occurred, intead of NULL. This makes it much easier for programs to ignore this possibility. strfree() is strictly a Netpbm invention, to allow proper type checking - when freeing storage allocated by the Netpbm asprintfN(). + when freeing storage allocated by the Netpbm pm_asprintf(). */ -extern const char * const strsol; +extern const char * const pm_strsol; int -snprintfN(char * const dest, - size_t const str_m, - const char * const fmt, - ...) PM_GNU_PRINTF_ATTR(3,4); +pm_snprintf(char * const dest, + size_t const str_m, + const char * const fmt, + ...) PM_GNU_PRINTF_ATTR(3,4); void -vsnprintfN(char * const str, - size_t const str_m, - const char * const fmt, - va_list ap, - size_t * const sizeP); +pm_vsnprintf(char * const str, + size_t const str_m, + const char * const fmt, + va_list ap, + size_t * const sizeP); + +const char * +pm_strdup(const char * const arg); void -asprintfN(const char ** const resultP, - const char * const fmt, - ...) PM_GNU_PRINTF_ATTR(2,3); +pm_asprintf(const char ** const resultP, + const char * const fmt, + ...) PM_GNU_PRINTF_ATTR(2,3); void -vasprintfN(const char ** const resultP, - const char * const format, - va_list args); +pm_vasprintf(const char ** const resultP, + const char * const format, + va_list args); + +bool +pm_vasprintf_knows_float(void); void -strfree(const char * const string); +pm_strfree(const char * const string); const char * -strsepN(char ** const stringP, const char * const delim); +pm_strsep(char ** const stringP, const char * const delim); int -stripeq(const char * const comparand, - const char * const comparator); +pm_stripeq(const char * const comparand, + const char * const comparator); const void * -memmemN(const void * const haystackArg, - size_t const haystacklen, - const void * const needleArg, - size_t const needlelen); +pm_memmem(const void * const haystackArg, + size_t const haystacklen, + const void * const needleArg, + size_t const needlelen); bool -strishex(const char * const subject); +pm_strishex(const char * const subject); void -interpret_uint(const char * const string, +pm_interpret_uint(const char * const string, unsigned int * const valueP, const char ** const errorP); diff --git a/lib/util/pm_c_util.h b/lib/util/pm_c_util.h index 07913f30..01a07657 100644 --- a/lib/util/pm_c_util.h +++ b/lib/util/pm_c_util.h @@ -1,6 +1,11 @@ #ifndef PM_C_UTIL_INCLUDED #define PM_C_UTIL_INCLUDED +/* Note that for MAX and MIN, if either of the operands is a floating point + Not-A-Number, the result is the second operand. So if you're computing a + running maximum and want to ignore the NaNs in the computation, put the + running maximum variable second. +*/ #undef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #undef MIN @@ -36,35 +41,49 @@ use "int". */ -/* We used to assume that if TRUE was defined, then bool was too. - However, we had a report on 2001.09.21 of a Tru64 system that had - TRUE but not bool and on 2002.03.21 of an AIX 4.3 system that was - likewise. So now we define bool all the time, unless the macro - HAVE_BOOL is defined. If someone is using the Netpbm libraries and - also another library that defines bool, he can either make the - other library define/respect HAVE_BOOL or just define HAVE_BOOL in - the file that includes pm_config.h or with a compiler option. Note - that C++ always has bool. +/* We will probably never again see a system that does not have + <stdbool.h>, but just in case, we have our own alternative here. - A preferred way of getting booleans is <stdbool.h>. But it's not - available on all platforms, and it's easy to reproduce what it does - here. + Evidence shows that the compiler actually produces better code with + <stdbool.h> than with bool simply typedefed to int. */ + +#ifdef __cplusplus + /* C++ has a bool type and false and true constants built in. */ +#else + /* The test for __STDC__ is paranoid. It is there just in case some + nonstandard compiler defines __STDC_VERSION__ in an arbitrary manner. + */ + #if ( defined(__GNUC__) && (__GNUC__ >= 3) ) || \ + ( defined(__STDC__) && (__STDC__ ==1) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) ) + #include <stdbool.h> + #else + /* We used to assume that if TRUE was defined, then bool was too. + However, we had a report on 2001.09.21 of a Tru64 system that had + TRUE but not bool and on 2002.03.21 of an AIX 4.3 system that was + likewise. So now we define bool all the time, unless the macro + HAVE_BOOL is defined. If someone is using the Netpbm libraries and + also another library that defines bool, he can either make the + other library define/respect HAVE_BOOL or just define HAVE_BOOL in + the file that includes pm_config.h or with a compiler option. Note + that C++ always has bool. + */ + #ifndef HAVE_BOOL + #define HAVE_BOOL 1 + typedef int bool; + #endif + #ifndef true + enum boolvalue {false=0, true=1}; + #endif + #endif +#endif + #ifndef TRUE - #define TRUE 1 + #define TRUE true #endif #ifndef FALSE - #define FALSE 0 - #endif -/* C++ has a bool type and false and true constants built in. */ -#ifndef __cplusplus - #ifndef HAVE_BOOL - #define HAVE_BOOL 1 - typedef int bool; - #endif - #ifndef true - enum boolvalue {false=0, true=1}; - #endif + #define FALSE false #endif #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) diff --git a/lib/util/runlength.c b/lib/util/runlength.c new file mode 100644 index 00000000..e5c60db0 --- /dev/null +++ b/lib/util/runlength.c @@ -0,0 +1,374 @@ +/*============================================================================= + runlength.c +=============================================================================== + "Packbits" run-length encoding and variants. + + Copyright (C) 2015 by Akira Urushibata (afu@wta.att.ne.jp). + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, provided + that the above copyright notice appear in all copies and that both that + copyright notice and this permission notice appear in supporting + documentation. This software is provided "as is" without express or implied + warranty. + + Functions pm_rlenc_byte() and pm_rlenc_word() are based on algorithm + originally by Robert A. Knop (rknop@mop.caltech.edu). + + Those who wish to incorporate the code herein into their own programs are + strongly discouraged from removing the comments within borders consisting + of "+" marks. This is a practical consideration based on code inspection + and bug fixes of various programs which use run-length compression. + +=============================================================================== + + Packbits is a simple yet efficient simple run-length compression method. It + has a provision for uncompressed sequences which allows efficient encoding + of noisy input. + + A survey of netpbm source code in 2015 found Packbits encoding in the + following Netpbm programs: pbmtoescp2, pbmtomacp, pnmtopalm, pnmtopclxl, + pnmtops, ppmtoilbm and ppmtopjxl. + + Packbits is an option in the TIFF standard; pamtotiff can generate TIFF + images that use Packbits compression, but does so via code in the TIFF + library (not part of Netpbm). + + Variants of Packbits are employed in pnmtosgi, pamtopdbimg, pbmtoppa + and pbmtogo. + + All the above programs formerly did the same compression through different + code. This redundancy bloated the Netpbm package and made maintenance + difficult. This maintenance difficulty surfaced as an issue when tests with + valgrind revealed multiple memory-related problems in the above programs. + Indeed, just determining which programs do this compression by this method + was not simple because of differences in terminology. "Packbits" is often + called "run length encoding." In ppmtoilbm the name is "byterun1." + + Today, all Netpbm programs that do Packbits compression use the facilities + in this file for it. +=============================================================================*/ + +#include <string.h> + +#include "pm.h" +#include "pm_c_util.h" +#include "runlength.h" +#include "mallocvar.h" + + + +static const char * const errorUndefinedMode = + "Internal error: compression mode %u not supported"; + + + +/*----------------------------------------------------------------------------- + Run length encoding + + In this simple run-length encoding scheme, compressed and uncompressed + strings follow a single index or "flag" byte N. There are several + variations, differing in the format of the flag byte, maximum string length + and element size (byte or word). + + In the most widely used version, Packbits, the meaning of the flag byte N + is defined as follows: + + 0-127 means the next N+1 bytes are uncompressed. + 129-255 means the next byte is to be repeated 257-N times. + 128 is not used. + + The name "Packbits" is misleading: it packs bytes, not bits. +-----------------------------------------------------------------------------*/ + + + +void +pm_rlenc_compressbyte(const unsigned char * const inbuf, + unsigned char * const outbuf, + enum pm_RleMode const mode, + size_t const inSize, + size_t * const outputSizeP) { +/*----------------------------------------------------------------------------- + Compress the contents of input buffer 'inbuf' with Packbits encoding into + output buffer 'outbuf'. 'inSize' is the number of bytes of data in 'inbuf'. + Return as *outputSizeP the number of bytes we put in 'outbuf'. + + 'outbuf' should be allocated with pm_rlenc_allocoutbuf(). + + Always encode 3-byte repeat sequences. + + Encode 2-byte repeat sequences only when they are at the start of the block. + This ensures that the output is never unnecessary bloated. + + Original algorithm by Robert A. Knop (rknop@mop.caltech.edu) +-----------------------------------------------------------------------------*/ + unsigned int const maxRun = 128; + + size_t inCurs, outCurs; + + if (mode != PM_RLE_PACKBITS) + pm_error(errorUndefinedMode, mode); + + for (inCurs = 0, outCurs = 0; inCurs < inSize; ) { + if ((inCurs < inSize - 1) && (inbuf[inCurs] == inbuf[inCurs+1])) { + /*Begin replicate run*/ + size_t const hold = inCurs; + size_t count; + for (count = 0; + inCurs < inSize && + inbuf[inCurs] == inbuf[hold] && + count < maxRun; + ++inCurs, ++count) + ; + + outbuf[outCurs++] = (unsigned char)(257 - count); + outbuf[outCurs++] = inbuf[hold]; + } else { + /*Do a non-run*/ + size_t const hold = outCurs; + size_t count; + ++outCurs; + count = 0; + while(((inCurs + 2 >= inSize) && (inCurs < inSize) ) || + ((inCurs + 2 < inSize) && + ((inbuf[inCurs] != inbuf[inCurs+1] ) + || (inbuf[inCurs] != inbuf[inCurs+2] ) ) ) ) { + outbuf[outCurs++] = inbuf[inCurs++]; + if (++count >= 128) + break; + } + outbuf[hold] = (unsigned char)(count - 1); + } + } + *outputSizeP = outCurs; +} + + + +static void +setFlagElement(void * const outP, + enum pm_RleMode const mode, + bool const isRepeatRun, + size_t const count) { +/*--------------------------------------------------------------------------- + Write the flag byte or word at specified point in the output buffer. +-----------------------------------------------------------------------------*/ + switch (mode) { + case PM_RLE_SGI16: + * (uint16_t *) outP = (isRepeatRun ? 0x00 : 0x80 ) | count; + break; + case PM_RLE_PALM16: + * (unsigned char *) outP = isRepeatRun ? + (unsigned char)(257 - count) : (unsigned char) (count - 1); + break; + default: + pm_error(errorUndefinedMode, mode); + } +} + + + +void +pm_rlenc_compressword(const uint16_t * const inbuf, + unsigned char * const outbuf, + enum pm_RleMode const mode, + size_t const inSize, + size_t * const outputSizeP) { +/*--------------------------------------------------------------------------- + Similar to pm_rlenc_byte(), but this works with two-byte elements. The + difference between SGI16 and PALM16 is the size of the flag. SGI16 : 16 + bits; PALM16 : 8 bits + + 'inSize' is a number of words,but *outputSizeP is a number of bytes. +-----------------------------------------------------------------------------*/ + size_t inCurs, outCurs; + size_t flagSz; + unsigned int maxRunSz; + + switch (mode) { + case PM_RLE_SGI16: + flagSz = 2; + maxRunSz = 127; + break; + case PM_RLE_PALM16: + flagSz = 1; + maxRunSz = 128; + break; + default: + pm_error(errorUndefinedMode, mode); + } + + for (inCurs = 0, outCurs = 0; inCurs < inSize; ) { + if ((inCurs < inSize - 1) && (inbuf[inCurs] == inbuf[inCurs+1])) { + uint16_t const runValue = inbuf[inCurs]; + size_t count; + /* Do a run of 'runValue' values */ + for (count = 0; + count < maxRunSz && + inCurs < inSize && + inbuf[inCurs] == runValue; + ++inCurs, ++count) + ; + setFlagElement(&outbuf[outCurs], mode, TRUE, count); + outCurs += flagSz; + * (uint16_t *) &outbuf[outCurs] = runValue; + outCurs += 2; + } else { + /*Do a non run*/ + size_t const nonrunStart = inCurs; + size_t count; + count = 0; + while (count < maxRunSz && + ((inCurs + 2 >= inSize && inCurs < inSize) || + (inCurs + 2 < inSize && + (inbuf[inCurs] != inbuf[inCurs+1] + || inbuf[inCurs] != inbuf[inCurs+2])))) { + ++inCurs; + ++count; + } + setFlagElement(&outbuf[outCurs], mode, FALSE, count); + outCurs += flagSz; + memcpy(&outbuf[outCurs], &inbuf[nonrunStart], count * 2); + outCurs += count * 2; + } + } + + if (mode == PM_RLE_SGI16) { + * (uint16_t *) &outbuf[outCurs] = 0; /* terminator */ + outCurs += 2; + } + + *outputSizeP = outCurs; +} + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Worst case output size + + The term "worst-case output size" can mean one of two things depending on + whether the encoder is efficient or not. + + Sub-optimal encoder: the output size can be up to twice the input size. + This happens (or one may rather say "is achieved by a determined encoder") + when input is split into one-byte blocks. + + Efficient encoder: the output is larger than the input by the minimum + number of flag bytes. The worst case happens when there are no repeat + sequences in the input. + + The key to an efficient encoder is correct handling of short, inefficient + sequences. The following algorithm (not actually used in this file) + describes the idea. + + A run is a maximal set of two or more consecutive identical values in the + input. A nonrun is a maximal set of values in which every value is + different from its neighbors. + + A compressed block is one that encodes a sequence of identical values + (which could be a run or just part of a run) as a value and a count. + count. An uncompressed block is one that encodes a sequence of any values + by listing the values individually. + + Start by encoding the entire input as uncompressed blocks. Seek runs that + can be encoded with compressed blocks, but only if doing so doesn't make + the output larger. + + Criteria to avoid bloat: + + - Overhead for a single uncompressed block: 1 byte. + + - Overhead for one uncompressed block and a compressed block: 2 bytes. + + - Overhead for two uncompressed blocks and a compressed block: 3 bytes. + + - New blocks at the edge of any existing block add 1 byte of overhead. + New blocks in the middle of existing blocks add 2 bytes of overhead. + + - Whenever savings are larger than the added overhead, encode the run + as a compressed block. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + + +size_t +pm_rlenc_maxbytes(size_t const inSize, /* number of elements */ + enum pm_RleMode const mode) { +/*--------------------------------------------------------------------------- + Calculate worst case output size from input size and encoding mode. + + 'inSize' counts the number of elements, not bytes: input size is (2 * + inSize) bytes if input is an array of 16-bit words. + + Return value is the maximum possible output size in bytes regardless of + type of input. + + Abort the program if the maximum possible output size is greater than + INT_MAX. +-----------------------------------------------------------------------------*/ + /* The upper limit could be raised above INT_MAX, but no program needs that + today. + */ + size_t blockSize; + size_t flagSize; + size_t itemSize; + size_t miscSize; + size_t overhead; + + switch (mode) { + case PM_RLE_PACKBITS: + blockSize = 128; flagSize = 1; itemSize = 1; miscSize = 0; break; + case PM_RLE_SGI8: + blockSize = 127; flagSize = 1; itemSize = 1; miscSize = 0; break; + case PM_RLE_GRAPHON: case PM_RLE_PPA: + blockSize = 64; flagSize = 1; itemSize = 1; miscSize = 0; break; + case PM_RLE_SGI16: + blockSize = 127; flagSize = 2; itemSize = 2; miscSize = 2; break; + case PM_RLE_PALM16: + blockSize = 128; flagSize = 1; itemSize = 2; miscSize = 0; break; + default: + pm_error(errorUndefinedMode, mode); + } + + overhead = miscSize + + (inSize / blockSize + (inSize % blockSize > 0 ? 1 : 0) ) * flagSize; + + if (inSize > INT_MAX / itemSize || + inSize * itemSize > INT_MAX - overhead) + pm_error("Cannot do RLE compression. Input too large."); + + return inSize * itemSize + overhead; +} + + + +void +pm_rlenc_allocoutbuf(unsigned char ** const outbufP, + size_t const inSize, /* element count */ + enum pm_RleMode const mode) { +/*--------------------------------------------------------------------------- + Allocate an output buffer sufficient for input with inSize elements, using + compression mode 'mode'. Element may be byte or word, whichever 'mode' + implies. +-----------------------------------------------------------------------------*/ + size_t const size = pm_rlenc_maxbytes(inSize, mode); + + unsigned char * outbuf; + + MALLOCARRAY(outbuf, size); + if (outbuf == NULL) + pm_error("Out of memory trying to get %u bytes for RLE output buffer", + (unsigned)size); + + *outbufP = outbuf; +} + + + +void +pm_rlenc_freebuf(void * const buf) { + free(buf); +} + + diff --git a/lib/util/runlength.h b/lib/util/runlength.h new file mode 100644 index 00000000..4857ae61 --- /dev/null +++ b/lib/util/runlength.h @@ -0,0 +1,51 @@ +#ifndef RUNLENGTH_INCLUDED +#define RUNLENGTH_INCLUDED + +#include "pm_config.h" + +#include <limits.h> + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} /* to fake out automatic code indenters */ +#endif + + +enum pm_RleMode { PM_RLE_PACKBITS, /* most common mode */ + PM_RLE_GRAPHON, /* reserved */ + PM_RLE_PPA, /* reserved */ + PM_RLE_SGI8, /* reserved */ + PM_RLE_SGI16, + PM_RLE_PALM16 + }; + +size_t +pm_rlenc_maxbytes (size_t const inSize, + enum pm_RleMode const mode); + +void +pm_rlenc_allocoutbuf(unsigned char ** const outbufP, + size_t const inSize, + enum pm_RleMode const mode); + + +void +pm_rlenc_freebuf(void * const buf); + +void +pm_rlenc_compressbyte(const unsigned char * const inbuf, + unsigned char * const outbuf, + enum pm_RleMode const mode, + size_t const inSize, + size_t * const outputSizeP); + +void +pm_rlenc_compressword(const uint16_t * const inbuf, + unsigned char * const outbuf, + enum pm_RleMode const mode, + size_t const itemCnt, + size_t * const outputSizeP); + +#endif diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c index 718186fa..ccf2d1eb 100644 --- a/lib/util/shhopt.c +++ b/lib/util/shhopt.c @@ -31,6 +31,7 @@ #include "mallocvar.h" #include "nstring.h" +#include "token.h" #include "shhopt.h" /*-----------------------------------------------------------------------+ @@ -76,7 +77,7 @@ optFatalFunc(const char *format, ...) | | RETURNS Number of options in the given array. | - | DESCRIPTION Count elements in an optStruct-array. The strcture must + | DESCRIPTION Count elements in an optStruct-array. The structure must | be ended using an element of type OPT_END. */ static int @@ -237,6 +238,7 @@ optNeedsArgument(const optEntry opt) || opt.type == OPT_ULONG || opt.type == OPT_FLOAT || opt.type == OPT_NAMELIST + || opt.type == OPT_STRINGLIST ; } @@ -281,46 +283,12 @@ getToken(const char * const tokenStart, we return an empty string and *nextP == tokenStart, i.e. *nextP doesn't necessarily advance. -----------------------------------------------------------------------------*/ - char * token; - const char * cursor; - unsigned int charCount; - - /* Run through the token, counting characters */ - - charCount = 0; - cursor = tokenStart; - - while (*cursor != delimiter && *cursor != '\0') { - if (*cursor == '\\') { - ++cursor; - if (*cursor == '\0') - optFatal("string ends with an escape character (\\)"); - } - ++cursor; - ++charCount; - } + const char * error; - token = malloc(charCount + 1); - if (token == NULL) - optFatal("Could not allocate %u bytes of memory to parse a string", - charCount + 1); - - /* Go back and do it again, this time copying the characters */ - charCount = 0; - cursor = tokenStart; - - while (*cursor != delimiter && *cursor != '\0') { - if (*cursor == '\\') - ++cursor; - - assert(*cursor != '\0'); + pm_gettoken(tokenStart, delimiter, tokenP, nextP, &error); - token[charCount++] = *cursor++; - } - token[charCount] = '\0'; - - *tokenP = token; - *nextP = cursor; + if (error) + optFatal("error parsing a token: %s", error); } @@ -375,6 +343,41 @@ parseNameList(const char * const listText, +static void +parseStringList(const char * const listText, + const char *** const listP) { + + unsigned int const maxStringCount = 100; + + const char * cursor; + unsigned int stringCount; + const char ** list; + + MALLOCARRAY_NOFAIL(list, maxStringCount+1); + + cursor = &listText[0]; /* initial value */ + + stringCount = 0; /* initial value */ + + while (stringCount < maxStringCount && *cursor != '\0') { + const char * next; + + getToken(cursor, ',', &list[stringCount++], &next); + + cursor = next; + + if (*cursor != '\0') { + assert(*cursor == ','); + ++cursor; + } + } + list[stringCount] = NULL; + + *listP = list; +} + + + /*------------------------------------------------------------------------ | NAME optExecute | @@ -479,6 +482,15 @@ optExecute(optEntry const opt, char *arg, int lng) parseNameList(arg, (struct optNameValue **)opt.arg); } break; + case OPT_STRINGLIST: { + if (arg == NULL) + optFatal("internal error: optExecute() called with NULL argument " + "'%s'", optString(opt, lng)); + + if (opt.arg) + parseStringList(arg, (const char ***)opt.arg); + + } break; default: break; } @@ -502,19 +514,20 @@ optExecute(optEntry const opt, char *arg, int lng) | that _must_ abort the program. */ void -optSetFatalFunc(void (*f)(const char *, ...)) -{ +pm_optSetFatalFunc(void (*f)(const char *, ...)) { + optFatal = f; } + /*------------------------------------------------------------------------ - | NAME optParseOptions + | NAME pm_optParseOptions | | FUNCTION Parse commandline options. | | SYNOPSIS #include "shhopt.h" - | void optParseOptions(int *argc, char *argv[], + | void pm_optParseOptions(int *argc, char *argv[], | optStruct opt[], int allowNegNum); | | INPUT argc Pointer to number of options. @@ -541,7 +554,7 @@ optSetFatalFunc(void (*f)(const char *, ...)) | Any error leads to program abortion. */ void -optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum) +pm_optParseOptions(int *argc, char *argv[], optStruct opt[], int allowNegNum) { int ai, /* argv index. */ optarg, /* argv index of option argument, or -1 if none. */ @@ -724,13 +737,13 @@ fatalUnrecognizedLongOption(const char * const optionName, const char * entry; if (optEntryP->longName) - asprintfN(&entry, "-%s ", optEntryP->longName); + pm_asprintf(&entry, "-%s ", optEntryP->longName); else - asprintfN(&entry, "-%c ", optEntryP->shortName); + pm_asprintf(&entry, "-%c ", optEntryP->shortName); strncat(optList, entry, sizeof(optList) - strlen(optList) - 1); - strfree(entry); + pm_strfree(entry); if (strlen(optList) + 1 == sizeof(optList)) { /* Buffer is full. Overwrite end of list with ellipsis */ @@ -809,12 +822,12 @@ parse_long_option(char * const argv[], /*------------------------------------------------------------------------ - | NAME optParseOptions2 + | NAME pm_optParseOptions2 | | FUNCTION Parse commandline options. | | SYNOPSIS #include "shhopt.h" - | void optParseOptions2(int *argc, char *argv[], + | void pm_optParseOptions2(int *argc, char *argv[], | optStruct2 opt, unsigned long flags); | | INPUT argc Pointer to number of options. @@ -834,9 +847,9 @@ parse_long_option(char * const argv[], | extracted and stored in the variables or passed to | functions pointed to by entries in opt. | - | This differs from optParseOptions in that it accepts + | This differs from pm_optParseOptions in that it accepts | long options with just one hyphen and doesn't accept - | any short options. It also has accomodations for + | any short options. It also has accommodations for | future expansion. | | Options and arguments used are removed from the argv- @@ -845,10 +858,10 @@ parse_long_option(char * const argv[], | Any error leads to program abortion. */ void -optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, +pm_optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, const unsigned long flags) /*---------------------------------------------------------------------------- - This does the same thing as optParseOptions3(), except that there is no + This does the same thing as pm_optParseOptions3(), except that there is no "specified" return value. This function exists for backward compatibility. @@ -865,7 +878,7 @@ optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, optFatal("Memory allocation failed (trying to allocate space for " "new-format option table)"); - optParseOptions3(argc_p, argv, opt3, sizeof(opt3), flags); + pm_optParseOptions3(argc_p, argv, opt3, sizeof(opt3), flags); free(opt3.opt_table); } @@ -890,7 +903,7 @@ zero_specified(optEntry opt_table[]) { /*------------------------------------------------------------------------ - | NAME optParseOptions3 + | NAME pm_optParseOptions3 | | FUNCTION Parse commandline options. | @@ -919,9 +932,9 @@ zero_specified(optEntry opt_table[]) { | extracted and stored in the variables or passed to | functions pointed to by entries in opt. | - | This differs from optParseOptions in that it accepts + | This differs from pm_optParseOptions in that it accepts | long options with just one hyphen and doesn't accept - | any short options. It also has accomodations for + | any short options. It also has accommodations for | future expansion. | | Options and arguments used are removed from the argv- @@ -930,7 +943,7 @@ zero_specified(optEntry opt_table[]) { | Any error leads to program abortion. */ void -optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, +pm_optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, const unsigned int optStructSize, const unsigned long flags) { int ai; /* argv index. */ @@ -996,13 +1009,13 @@ optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, void -optDestroyNameValueList(struct optNameValue * const list) { +pm_optDestroyNameValueList(struct optNameValue * const list) { unsigned int i; for (i = 0; list[i].name; ++i) { - strfree(list[i].name); - strfree(list[i].value); + pm_strfree(list[i].name); + pm_strfree(list[i].value); } free(list); diff --git a/lib/util/shhopt.h b/lib/util/shhopt.h index 99096a76..9a446290 100644 --- a/lib/util/shhopt.h +++ b/lib/util/shhopt.h @@ -1,4 +1,5 @@ -/*============================================================================= +#if 0 +============================================================================= HERE IS AN EXAMPLE OF THE USE OF SHHOPT: @@ -6,8 +7,9 @@ HERE IS AN EXAMPLE OF THE USE OF SHHOPT: int main ( int argc, char **argv ) { - // initial values here are just to demonstrate what gets set and - // what doesn't by the code below. + /* initial values here are just to demonstrate what gets set and + what doesn't by the code below. + */ int help_flag = 7; unsigned int help_spec =7; unsigned int height_spec =7; @@ -20,7 +22,8 @@ main ( int argc, char **argv ) { optStruct3 opt; unsigned int option_def_index = 0; - optEntry *option_def = malloc(100*sizeof(option_def[0])); + optEntry * option_def; + MALLOCARRAY(option_def, 100); OPTENT3(0, "help", OPT_FLAG, &help_flag, &help_spec, 0); OPTENT3(0, "height", OPT_INT, &height, &height_spec, 0); @@ -34,7 +37,7 @@ main ( int argc, char **argv ) { opt.allowNegNum = 1; - optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0); printf("argc=%d\n", argc); @@ -67,8 +70,15 @@ Now run this program with something like myprog -v /etc/passwd -name=Bryan --hei=4 -========================================================================*/ + If your program takes no options (so you have no OPTENT3 macro invocations), + you need an OPTENTINIT call to establish the empty option table: + + optEntry * option_def; + MALLOCARRAY(option_def, 1); + OPTENTINIT; +======================================================================== +#endif /* 0 */ #ifndef SHHOPT_H #define SHHOPT_H @@ -90,13 +100,14 @@ typedef enum { OPT_LONG, /* signed long integer argument. */ OPT_ULONG, /* unsigned long integer argument. */ OPT_FLOAT, /* floating point argument. */ + OPT_STRINGLIST, /* list like "arg1,arg2.arg3" */ OPT_NAMELIST /* list like "key1=val1,key2=val2" */ } optArgType; typedef struct { /* This structure describes a single program option in a form for - use by the optParseOptions() or optParseOptions2() function. + use by the pm_optParseOptions() or pm_optParseOptions2() function. */ char shortName; /* short option name. */ const char *longName; /* long option name, not including '--'. */ @@ -108,7 +119,7 @@ typedef struct { typedef struct { /* This structure describes a single program option in a form for - use by the optParseOptions3() function. + use by the pm_optParseOptions3() function. */ char shortName; /* short option name. */ const char *longName; /* long option name, not including '--' or '-' */ @@ -129,7 +140,7 @@ typedef struct { typedef struct { /* This structure describes the options of a program in a form for - use with the optParseOptions2() function. + use with the pm_optParseOptions2() function. */ unsigned char short_allowed; /* boolean */ /* The syntax may include short (i.e. one-character) options. @@ -146,7 +157,7 @@ typedef struct { } optStruct2; typedef struct { - /* Same as optStruct2, but for optParseOptions3() */ + /* Same as optStruct2, but for pm_optParseOptions3() */ unsigned char short_allowed; unsigned char allowNegNum; optEntry *opt_table; @@ -187,9 +198,10 @@ typedef struct { } /* OPTENT3 is the same as OPTENTRY except that it also sets the "specified" - element of the table entry (so it assumes OPTION_DEF is a table of - optEntry instead of optStruct). It sets it to the number of times that - the option appears in the command line. + element of the table entry (so it assumes OPTION_DEF is a table of optEntry + instead of optStruct). This is a pointer to a variable that the parser + will set to the number of times that the option appears in the command + line. Here is an example: @@ -215,18 +227,21 @@ struct optNameValue { -void optSetFatalFunc(void (*f)(const char *, ...)); -void optParseOptions(int *argc, char *argv[], - optStruct opt[], int allowNegNum); void -optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, +pm_optSetFatalFunc(void (*f)(const char *, ...)); + +void +pm_optParseOptions(int *argc, char *argv[], + optStruct opt[], int allowNegNum); +void +pm_optParseOptions2(int * const argc_p, char *argv[], const optStruct2 opt, const unsigned long flags); void -optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, +pm_optParseOptions3(int * const argc_p, char *argv[], const optStruct3 opt, const unsigned int optStructSize, const unsigned long flags); void -optDestroyNameValueList(struct optNameValue * const list); +pm_optDestroyNameValueList(struct optNameValue * const list); #ifdef __cplusplus } diff --git a/lib/util/token.c b/lib/util/token.c new file mode 100644 index 00000000..a68a4821 --- /dev/null +++ b/lib/util/token.c @@ -0,0 +1,80 @@ +#include <stdlib.h> +#include <assert.h> + +#include "nstring.h" + +#include "token.h" + + +void +pm_gettoken(const char * const tokenStart, + char const delimiter, + const char ** const tokenP, + const char ** const nextP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Find the token starting at 'tokenStart' up to but not including + the first 'delimiter' character or end of string. Return it in newly + malloced memory as *tokenP, NUL-terminated. + + Make *nextP point just past the token, i.e. to the delimiter or + end of string NUL character. + + Note that if the string is empty, or starts with the delimiter, + we return an empty string and *nextP == tokenStart, i.e. *nextP + doesn't necessarily advance. +-----------------------------------------------------------------------------*/ + char * token; + const char * cursor; + unsigned int charCount; + + /* Run through the token, counting characters */ + + charCount = 0; /* initial value */ + cursor = tokenStart; /* initial value */ + *errorP = NULL; /* initial value */ + + while (*cursor != delimiter && *cursor != '\0' && !*errorP) { + if (*cursor == '\\') { + ++cursor; + if (*cursor == '\0') + pm_asprintf(errorP, + "string ends with an escape character (\\)"); + } else { + ++cursor; + ++charCount; + } + } + if (!*errorP) { + token = malloc(charCount + 1); + if (token == NULL) + pm_asprintf(errorP, "Could not allocate %u bytes of memory " + "to parse a string", + charCount + 1); + else { + /* Go back and do it again, this time copying the characters */ + charCount = 0; + cursor = tokenStart; + + while (*cursor != delimiter && *cursor != '\0') { + if (*cursor == '\\') + ++cursor; + + assert(*cursor != '\0'); + + token[charCount++] = *cursor++; + } + token[charCount] = '\0'; + + *tokenP = token; + *nextP = cursor; + } + } +} + + + + + + + diff --git a/lib/util/token.h b/lib/util/token.h new file mode 100644 index 00000000..292a9164 --- /dev/null +++ b/lib/util/token.h @@ -0,0 +1,11 @@ +#ifndef TOKEN_H_INCLUDE +#define TOKEN_H_INCLUDE + +void +pm_gettoken(const char * const tokenStart, + char const delimiter, + const char ** const tokenP, + const char ** const nextP, + const char ** const errorP); + +#endif diff --git a/lib/util/vasprintf.c b/lib/util/vasprintf.c index 47b4079d..a947f763 100644 --- a/lib/util/vasprintf.c +++ b/lib/util/vasprintf.c @@ -1,19 +1,21 @@ #define _GNU_SOURCE - /* Due to conditional compilation, this is GNU source only if the C library - is GNU. + /* Because of conditional compilation, this is GNU source only if the C + library is GNU. */ #include <stdlib.h> #include <string.h> #include "pm_config.h" +#include "pm_c_util.h" + #include "nstring.h" void -vasprintfN(const char ** const resultP, - const char * const format, - va_list varargs) { +pm_vasprintf(const char ** const resultP, + const char * const format, + va_list varargs) { char * result; @@ -23,7 +25,7 @@ vasprintfN(const char ** const resultP, rc = vasprintf(&result, format, varargs); if (rc < 0) - *resultP = strsol; + *resultP = pm_strsol; else *resultP = result; #else @@ -38,16 +40,22 @@ vasprintfN(const char ** const resultP, So instead, we just allocate 4K and truncate or waste as necessary. + + Note that we don't recognize the floating point specifiers (%f, %e, %g) + - we render them as 'f', 'e', and 'g'. It would be too much work to + make this code handle those, just for the few systems on which it runs. + Instead, we have pm_vasprintf_knows_float(), and any caller that cares + enough can avoid using these specifiers where they don't work. */ size_t const allocSize = 4096; result = malloc(allocSize); if (result == NULL) - *resultP = strsol; + *resultP = pm_strsol; else { size_t realLen; - vsnprintfN(result, allocSize, format, varargs, &realLen); + pm_vsnprintf(result, allocSize, format, varargs, &realLen); if (realLen >= allocSize) strcpy(result + allocSize - 15, "<<<TRUNCATED"); @@ -56,3 +64,14 @@ vasprintfN(const char ** const resultP, } #endif } + + + +bool +pm_vasprintf_knows_float(void) { +#if HAVE_VASPRINTF + return true; +#else + return false; +#endif +} diff --git a/lib/util/wordaccess.h b/lib/util/wordaccess.h index 2eaa2b24..df0eaf12 100644 --- a/lib/util/wordaccess.h +++ b/lib/util/wordaccess.h @@ -44,33 +44,32 @@ #include "pm_config.h" -#if (!defined(WORDACCESS_GENERIC) && HAVE_GCC_BITCOUNT ) - - #if BYTE_ORDER == BIG_ENDIAN /* See pm_config.h */ - /* Sun Sparc 64, etc */ - #include "wordaccess_gcc3_be.h" - - #elif (BITS_PER_LONG == 64) - /* AMD Athlon 64, Intel x86_64, Intel Itanium, etc. */ - #include "wordaccess_64_le.h" - - #elif (BITS_PER_LONG == 32) - /* Intel x86_32 (80386, 80486, Pentium), etc. */ - #include "wordaccess_generic.h" - - #else - /* Extremely rare case. - If long is neither 32 nor 64 bits, (say, 128) it comes here. - */ - #define WORDACCESS_GENERIC - #include "wordaccess_generic.h" - - #endif - +#if defined(WORDACCESS_GENERIC) + /* User wants this, regardless of whether machine can do better */ + #include "wordaccess_generic.h" +#elif BYTE_ORDER == BIG_ENDIAN + #if UNALIGNED_OK + #include "wordaccess_be_unaligned.h" + #else + /* Sparc */ + #include "wordaccess_be_aligned.h" + #endif +#elif HAVE_GCC_BITCOUNT + #if (BITS_PER_LONG == 64) + /* AMD Athlon 64, Intel x86_64, Intel Itanium, etc. */ + #include "wordaccess_64_le.h" + #elif (BITS_PER_LONG == 32) + /* Intel x86_32 (80386, 80486, Pentium), etc. */ + #include "wordaccess_generic.h" + #else + /* Extremely rare case. + If long is neither 32 nor 64 bits, (say, 128) it comes here. + */ + #include "wordaccess_generic.h" + #endif #else - /* Non GCC, GCC prior to v.3.4 or WORDACCESS_GENERIC defined */ - #include "wordaccess_generic.h" - + /* Non GCC or GCC prior to v.3.4; little-endian */ + #include "wordaccess_generic.h" #endif #endif diff --git a/lib/util/wordaccess_be_aligned.h b/lib/util/wordaccess_be_aligned.h new file mode 100644 index 00000000..f3bbb841 --- /dev/null +++ b/lib/util/wordaccess_be_aligned.h @@ -0,0 +1,35 @@ +/*============================================================================= + This file is the part of wordaccess.h for use under with big-endian + machines that require word accesses to be word-aligned. +*===========================================================================*/ + +typedef unsigned long int wordint; +typedef unsigned char wordintBytes[sizeof(wordint)]; + +static __inline__ wordint +bytesToWordint(wordintBytes bytes) { + uint16_t const hi = *((uint16_t *) (bytes + 0)); + uint16_t const mh = *((uint16_t *) (bytes + 2)); + uint16_t const ml = *((uint16_t *) (bytes + 4)); + uint16_t const lo = *((uint16_t *) (bytes + 6)); + return + (((wordint) hi) << 48) | + (((wordint) mh) << 32) | + (((wordint) ml) << 24) | + (((wordint) lo) << 0); +} + + + +static __inline__ void +wordintToBytes(wordintBytes * const bytesP, + wordint const wordInt) { + uint16_t const hi = ((wordInt >> 48) & 0xFF); + uint16_t const mh = ((wordInt >> 32) & 0xFF); + uint16_t const ml = ((wordInt >> 24) & 0xFF); + uint16_t const lo = ((wordInt >> 0) & 0xFF); + *(uint16_t *)(bytesP + 0) = hi; + *(uint16_t *)(bytesP + 2) = mh; + *(uint16_t *)(bytesP + 4) = ml; + *(uint16_t *)(bytesP + 6) = lo; +} diff --git a/lib/util/wordaccess_gcc3_be.h b/lib/util/wordaccess_be_unaligned.h index 5aa63521..95b68ac7 100644 --- a/lib/util/wordaccess_gcc3_be.h +++ b/lib/util/wordaccess_be_unaligned.h @@ -1,9 +1,6 @@ /*============================================================================= - This file is the part of wordaccess.h for use under these - conditions: - - * GCC (>=3.4), GLIBC - * Big-Endian machines + This file is the part of wordaccess.h for use on a big-endian machine + that does not require word accesses to be word-aligned. *===========================================================================*/ typedef unsigned long int wordint; diff --git a/lib/util/wordaccess_generic.h b/lib/util/wordaccess_generic.h index 94cc8124..6e0a20ef 100644 --- a/lib/util/wordaccess_generic.h +++ b/lib/util/wordaccess_generic.h @@ -1,11 +1,11 @@ /*============================================================================= - This file is the part of wordaccess.h for use under these + This file is the part of wordaccess.h for use under any of these conditions: - * Compilers other than GCC + * Compiler other than GCC * GCC before version 3.4 - * Specified by the user with WORDACCESS_GENERIC + * Requested by the user with WORDACCESS_GENERIC =============================================================================*/ #include "intcode.h" |