diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2018-09-29 03:18:57 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2018-09-29 03:18:57 +0000 |
commit | e227331a6b5626e100705fcff3269fd8479ea988 (patch) | |
tree | fd51fff8ec8dc90185ce5ae457805a0f25a0ded6 | |
parent | cdd64d00181d5e5a6c8142cdb59f2215014ca82d (diff) | |
download | netpbm-mirror-e227331a6b5626e100705fcff3269fd8479ea988.tar.gz netpbm-mirror-e227331a6b5626e100705fcff3269fd8479ea988.tar.xz netpbm-mirror-e227331a6b5626e100705fcff3269fd8479ea988.zip |
Promote current Development release as Advanced
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@3352 9d0c8265-081b-0410-96cb-a4ca84ce46f8
58 files changed, 5847 insertions, 1739 deletions
diff --git a/GNUmakefile b/GNUmakefile index c2139d2a..3bf3623d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -460,6 +460,7 @@ CHECK_VARS = \ ZLIB="$(ZLIB)" \ # Test files in source tree. +# BUILDBINDIRS is a list of directories which contain target binaries check-tree : BUILDBINDIRS :=./analyzer \ ./converter/other \ @@ -479,6 +480,7 @@ check-tree : BUILDBINDIRS :=./analyzer \ ./editor/pamflip \ ./editor/specialty \ ./generator \ +./generator/pamtris \ ./other \ ./other/pamx diff --git a/analyzer/pamgetcolor.c b/analyzer/pamgetcolor.c index 993dd79d..cd9d2028 100644 --- a/analyzer/pamgetcolor.c +++ b/analyzer/pamgetcolor.c @@ -3,98 +3,115 @@ #include <pm_gamma.h> #include <pam.h> +#include "pm_c_util.h" #include "shhopt.h" #include "mallocvar.h" typedef unsigned int uint; -/* specification of a cirtular "region" over which to measre the avg. color: */ typedef struct { +/*---------------------------------------------------------------------------- + Specification of a circular "region" over which to measure the average color +-----------------------------------------------------------------------------*/ uint x; /* coordinates of the center */ uint y; /* of the region; */ - char const * label; /* optional label supplied on the command line */ + const char * label; /* optional label supplied on the command line */ } RegSpec; -/* represents a single color measurement over a "region": */ typedef struct { +/*---------------------------------------------------------------------------- + Represents a single color measurement over a "region" +-----------------------------------------------------------------------------*/ uint area; /* area in pixels over which to average the color */ /* cumulative normalised intensity-proportiunal value of the region: */ double color[3]; } RegData; -/*command-line parameters: */ typedef struct { +/*---------------------------------------------------------------------------- + All the information the user supplied in the command line, in a form easy + for the program to use. +-----------------------------------------------------------------------------*/ uint linear; uint radius; uint regN; /* number of regions */ uint maxLbLen; /* maximum label length */ RegSpec * regSpecs; /* list of points to sample, dymamically allocated*/ - char const * formatStr; /* output color format as string */ + const char * formatStr; /* output color format as string */ uint formatId; /* the Id of the selected color format */ uint formatArg; /* the argument to the color formatting function */ - char const * infile; -} CmdlineInfo; + const char * infile; +} CmdLineInfo; /* Generic pointer to a color-formatting function. Returns the textual representation of the color <tuple> in terms of the image pointed-to by <pamP>. <param> is a generic integer parameter that depends on the specific funcion and may denote precison or maxval. */ -typedef char const * +typedef const char * (*FormatColor)(struct pam * const pamP, tuple const color, uint const param); -/* The color format specificaiton: */ typedef struct ColorFormat { - /* format id (compared against the -format command-line argument): */ +/*---------------------------------------------------------------------------- + The color format specification +-----------------------------------------------------------------------------*/ char const * id; - /* function that returns converts a color into this format: */ + /* format id (compared against the -format command-line argument) */ FormatColor const formatColor; - /* meaning of the <param> argument of <formatColor>(): */ + /* function that returns converts a color into this format */ char const * argName; - uint const defParam; /* default value of that argument */ - uint const maxParam; /* maximum value of that argument */ + /* meaning of the <param> argument of <formatColor>() */ + uint const defParam; + /* default value of that argument */ + uint const maxParam; + /* maximum value of that argument */ } ColorFormat; -static char const * +static const char * fcInt(struct pam * const pamP, tuple const color, uint const param) { -/* format <color> as an integer tuple with maxval <param> */ +/*---------------------------------------------------------------------------- + Format 'color' as an integer tuple with maxval 'param' +-----------------------------------------------------------------------------*/ return pnm_colorspec_rgb_integer(pamP, color, param); } -static char const * +static const char * fcNorm(struct pam * const pamP, tuple const color, uint const param) { -/* format <color> as normalised tuple with precision <param> */ +/*---------------------------------------------------------------------------- + Format 'color' as normalized tuple with precision 'param' +-----------------------------------------------------------------------------*/ return pnm_colorspec_rgb_norm(pamP, color, param); } -static char const * +static const char * fcX11(struct pam * const pamP, tuple const color, uint const param) { -/* format <color> as hexadecimal tuple with <param> digits*/ +/*---------------------------------------------------------------------------- + Format 'color' as hexadecimal tuple with 'param' digits +-----------------------------------------------------------------------------*/ return pnm_colorspec_rgb_x11(pamP, color, param); } -#define FormatsN 3 +static int const defaultFormat = 0; -static int const DefaultFormat = 0; /* Table with the full information about color formats */ -ColorFormat formats[ FormatsN ] = { +ColorFormat const formats[ 3 ] = { /* Id Function Argument name Default Max */ { "int", &fcInt, "maxval", 255, 65535 }, { "norm", &fcNorm, "digit count", 3, 6 }, @@ -112,12 +129,14 @@ sqri(int const v) { static RegSpec -parseRegSpec(char const * const s) { +parsedRegSpec(const char * const s) { /*---------------------------------------------------------------------------- - Parse region specification <s> from the command line and return its - structured representation. A specification is of the format <x,y[:label]. + The region specification represented by command line argument 's'. + + 's' is of the format x,y[:label]. -----------------------------------------------------------------------------*/ - char* end, *start; + char * end; + char *start; RegSpec res; start = (char *)s; @@ -146,8 +165,7 @@ parseRegSpec(char const * const s) { break; /* empty label */ return res; } - } - while (1 == 0); + } while (false); pm_error("Wrong region specification: %s", s); @@ -157,126 +175,157 @@ parseRegSpec(char const * const s) { static void -parseColorFmt(CmdlineInfo * const cmdLineP) { +parseColorFmt(const char * const formatStr, + uint * const formatIdP, + uint * const formatArgP) { /*---------------------------------------------------------------------------- - Parse the color format specificaction from the command line stored in the - <formatStr> member of <cmdLineP> and save it into members <formatId> and - <formatArg>. A format specification is <format>[:<arg>]. + Parse the color format specification string 'formatStr' as + *formatIdP and *formatArgP. + + A format specification string is of format format[:arg]. -----------------------------------------------------------------------------*/ - const int FmtNotFound = -1; - const char * ErrSpec = "Wrong color format specification: "; - const char * formatStr; - char * colonLoc; /* location of the colon in the specification */ + int const FmtNotFound = -1; + const char * const errSpec = "Wrong color format specification: "; + + const char * colonLoc; /* location of the colon in the specification */ uint n, f; - ColorFormat * formatP; + const ColorFormat * formatP; + uint formatId; - formatStr = cmdLineP->formatStr; colonLoc = strchr(formatStr, ':'); if (colonLoc != NULL) n = colonLoc - formatStr; else n = strlen(formatStr); - cmdLineP->formatId = FmtNotFound; - - for (f = 0; f < FormatsN; f++) { - if (strncmp(formatStr, formats[f].id, n) == 0) { - cmdLineP->formatId = f; - break; - } + for (f = 0, formatId = FmtNotFound; + f < ARRAY_SIZE(formats) && formatId == FmtNotFound; ++f) { + if (strncmp(formatStr, formats[f].id, n) == 0) + formatId = f; } - if (cmdLineP->formatId == FmtNotFound) { + if (formatId == FmtNotFound) pm_error("Color format not recognised."); - } - formatP = &formats[cmdLineP->formatId]; - if (colonLoc != NULL) { + + *formatIdP = formatId; + + formatP = &formats[formatId]; + + if (colonLoc) { long int arg; - char *argStart, *argEnd; + const char * argStart; + char * argEnd; argStart = colonLoc + 1; + if (*argStart == '\0') pm_error("%sthe colon should be followed by %s.", - ErrSpec, formatP->argName); + errSpec, formatP->argName); arg = strtol(argStart, &argEnd, 10); + if (*argEnd != '\0') pm_error("%sfailed to parse the %s: %s.", - ErrSpec, formatP->argName, argStart); + errSpec, formatP->argName, argStart); if (arg < 1) pm_error("%s%s must be greater than zero.", - ErrSpec, formatP->argName); + errSpec, formatP->argName); if (arg > formatP->maxParam) pm_error("%s%s cannot exceed %i.", - ErrSpec, formatP->argName, formatP->maxParam); - cmdLineP->formatArg = arg; - } - else - cmdLineP->formatArg = formatP->defParam; + errSpec, formatP->argName, formatP->maxParam); + + *formatArgP = arg; + } else + *formatArgP = formatP->defParam; } -static CmdlineInfo -parseCommandLine(int argc, char const ** argv) { -/*---------------------------------------------------------------------------- - Parse the command-line arguments and store them in a form convenient for the - program. ------------------------------------------------------------------------------*/ - int r; - uint formatSet; - CmdlineInfo cmdLine; - optStruct3 opt; - uint option_def_index = 0; +static CmdLineInfo +parsedCommandLine(int argc, + const char ** const argv) { optEntry * option_def; - MALLOCARRAY_NOFAIL(option_def, 100); + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; - cmdLine.radius = 0; + CmdLineInfo cmdLine; - opt.opt_table = option_def; - opt.short_allowed = 0; - opt.allowNegNum = 0; + uint infileSpec, radiusSpec, formatSpec, linearSpec; + + MALLOCARRAY_NOFAIL(option_def, 100); - OPTENT3(0, "infile", OPT_STRING, &cmdLine.infile, NULL, 0); - OPTENT3(0, "radius", OPT_INT, &cmdLine.radius, NULL, 0); - OPTENT3(0, "format", OPT_STRING, &cmdLine.formatStr, &formatSet, 0); - OPTENT3(0, "linear", OPT_FLAG, &cmdLine.linear, NULL, 0); - OPTENT3(0, 0, OPT_END, NULL, NULL, 0); + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "infile", OPT_STRING, &cmdLine.infile, &infileSpec, 0); + OPTENT3(0, "radius", OPT_INT, &cmdLine.radius, &radiusSpec, 0); + OPTENT3(0, "format", OPT_STRING, &cmdLine.formatStr, &formatSpec, 0); + OPTENT3(0, "linear", OPT_FLAG, &cmdLine.linear, &linearSpec, 0); + OPTENT3(0, 0, OPT_END, NULL, NULL, 0); - cmdLine.radius = 0; - cmdLine.linear = 0; - cmdLine.infile = "-"; + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); - if (formatSet) { - parseColorFmt(&cmdLine); + if (!infileSpec) + cmdLine.infile = "-"; + + if (!radiusSpec) + cmdLine.radius = 0; + + if (formatSpec) { + parseColorFmt(cmdLine.formatStr, + &cmdLine.formatId, &cmdLine.formatArg); } else { - cmdLine.formatId = DefaultFormat; - cmdLine.formatArg = formats[DefaultFormat].defParam; + cmdLine.formatId = defaultFormat; + cmdLine.formatArg = formats[defaultFormat].defParam; } - cmdLine.regN = argc - 1; - MALLOCARRAY_NOFAIL(cmdLine.regSpecs, cmdLine.regN); + if (!linearSpec) + cmdLine.radius = 0; - cmdLine.maxLbLen = 0; - if (argc < 2) + if (argc-1 < 1) pm_error("No regions specified."); - for (r = 0; r < argc - 1; r++) { - size_t lbLen; - cmdLine.regSpecs[r] = parseRegSpec(argv[r+1]); - lbLen = strlen(cmdLine.regSpecs[r].label); - if (lbLen > cmdLine.maxLbLen) - cmdLine.maxLbLen = lbLen; + cmdLine.regN = argc - 1; + + MALLOCARRAY(cmdLine.regSpecs, cmdLine.regN); + + if (!cmdLine.regSpecs) + pm_error("Could not get memory for %u region specifications", + cmdLine.regN); + + { + uint r; + uint maxLbLen; + + for (r = 0, maxLbLen = 0; r < argc - 1; ++r) { + size_t lbLen; + cmdLine.regSpecs[r] = parsedRegSpec(argv[r+1]); + lbLen = strlen(cmdLine.regSpecs[r].label); + maxLbLen = MAX(maxLbLen, lbLen); + } + cmdLine.maxLbLen = maxLbLen; } free(option_def); + return cmdLine; } +static void +freeCommandLine(CmdLineInfo const cmdLine) { + + free(cmdLine.regSpecs); +} + + + static RegData * allocRegSamples(uint n) { /*---------------------------------------------------------------------------- Allocate an array of <n> initialised region samles. The array should be @@ -299,7 +348,7 @@ static RegData * allocRegSamples(uint n) { static uint getYmax(struct pam * const pamP, - CmdlineInfo const cmdLine) { + CmdLineInfo const cmdLine) { /*---------------------------------------------------------------------------- Find the maximum row in the image that contains a pixel from a region. -----------------------------------------------------------------------------*/ @@ -330,16 +379,17 @@ readChord(RegData * const dataP, uint const x0, uint const x1) { /*---------------------------------------------------------------------------- - Update region sample <dataP> with the data from horisontal chord lying in - row <row> and going from <x0> to <x1>. <linear> denotes whether <pamP> is - true PPM or the linear variation. + Update region sample *dataP with the data from horizontal chord lying in row + 'row' and going from 'x0' to 'x1'. 'linear' means tuples in 'row' are the + intensity-linear values as opposed to normal libnetpbm gamma-adjusted + values. -----------------------------------------------------------------------------*/ uint x; - for (x = x0; x <= x1; x++) { + for (x = x0; x <= x1; ++x) { uint l; - for (l = 0; l < 3; l++) { + for (l = 0; l < 3; ++l) { double val; val = (double)row[x][l] / pamP->maxval; @@ -348,7 +398,7 @@ readChord(RegData * const dataP, val = pm_ungamma709(val); dataP->color[l] += val; } - dataP->area++; + ++dataP->area; } } @@ -358,86 +408,95 @@ static void processRow(tuple * const row, uint const y, struct pam * const pamP, - CmdlineInfo const * cmdLineP, + CmdLineInfo const * cmdLineP, RegData * const regSamples) { /*---------------------------------------------------------------------------- - Reads a row from image <pamP> into allocated tuple array <row>, and updates - region samples <regSamples[]> from it. <y> is the position of the row. + Read a row from image described by *pamP into 'row', and update region + samples regSamples[] from it. 'y' is the position of the row. -----------------------------------------------------------------------------*/ uint r; pnm_readpamrow(pamP, row); - for (r = 0; r < cmdLineP->regN; r++) { - RegSpec spec; - RegData * dataP; - uint xd, xd2; - int yd; - int x0, x1; - - spec = cmdLineP->regSpecs[r]; - dataP = ®Samples[r]; - yd = (int)spec.y - (int)y; - if (abs(yd) > cmdLineP->radius) - continue; /* to avoid the slow root operation when possible */ - xd2 = sqri(cmdLineP->radius) - sqri(yd); - xd = (int)(sqrt((double)xd2) + 0.5); - x0 = spec.x - xd; - x1 = spec.x + xd; - - /* clip horisontal chord to image boundaries: */ - if (x0 < 0) - x0 = 0; - if (x1 >= pamP->width) - x1 = pamP->width - 1; - - readChord(dataP, cmdLineP->linear, pamP, row, x0, x1); + + for (r = 0; r < cmdLineP->regN; ++r) { + RegSpec const spec = cmdLineP->regSpecs[r]; + RegData * const dataP = ®Samples[r]; + int const yd = (int)spec.y - (int)y; + + if (abs(yd) > cmdLineP->radius) { + /* Row is entirely above or below the region; Avoid the slow root + operation + */ + } else { + uint const xd2 = sqri(cmdLineP->radius) - sqri(yd); + uint const xd = ROUNDU(sqrt((double)xd2)); + + int x0, x1; + + x0 = spec.x - xd; /* initial value */ + x1 = spec.x + xd; /* initial value */ + + /* clip horizontal chord to image boundaries: */ + if (x0 < 0) + x0 = 0; + if (x1 >= pamP->width) + x1 = pamP->width - 1; + + readChord(dataP, cmdLineP->linear, pamP, row, x0, x1); + } } } static RegData * -getColors(struct pam * const pamP, - CmdlineInfo const cmdLine) { +colorsFmImage(struct pam * const pamP, + CmdLineInfo const cmdLine) { /*---------------------------------------------------------------------------- - Scans image <pamP> and collects color data for the regions. + Color data for the regions requested by 'cmdLine' in the image described by + *pamP. -----------------------------------------------------------------------------*/ uint y, ymax; - RegData * samples; + RegData * samplesP; tuple * row; - FILE * inFile; + FILE * ifP; + + ifP = pm_openr(cmdLine.infile); - inFile = pm_openr(cmdLine.infile); - pnm_readpaminit(inFile, pamP, PAM_STRUCT_SIZE(tuple_type)); + pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type)); - ymax = getYmax( pamP, cmdLine ); + ymax = getYmax(pamP, cmdLine); - samples = allocRegSamples( cmdLine.regN ); - row = pnm_allocpamrow(pamP); - y = 0; - for (y = 0; y <= ymax; y++) - processRow( row, y, pamP, &cmdLine, samples ); + samplesP = allocRegSamples(cmdLine.regN); + row = pnm_allocpamrow(pamP); + + for (y = 0; y <= ymax; ++y) + processRow(row, y, pamP, &cmdLine, samplesP); pnm_freepamrow(row); - pm_close(inFile); - return samples; + pm_close(ifP); + + return samplesP; } -static char const * -formatColor(RegData const data, - CmdlineInfo const cmdLine, - struct pam * const pamP, - tuple const tup) { +static const char * +outputColorSpec(RegData const data, + CmdLineInfo const cmdLine, + struct pam * const pamP, + tuple const tup) { /*---------------------------------------------------------------------------- - Format the color of region sample <data> according to the format specified - in <cmdLine>. The image <pamP> and tuple <tup> are required by the Netpbm - formatting functions. + Color of region sample 'data' formatted for output as requested by + 'cmdLine'. + + *pamP tells how to interpret 'data'. + + 'tup' is working space for internal use. -----------------------------------------------------------------------------*/ uint l; - for (l = 0; l < 3; l++) + for (l = 0; l < 3; ++l) tup[l] = pm_gamma709(data.color[l]/data.area) * pamP->maxval; return formats[cmdLine.formatId]. @@ -447,29 +506,37 @@ formatColor(RegData const data, static void -printColors(struct pam * const pamP, - CmdlineInfo const cmdLine, - FILE * const outChan, - RegData const regSamples[]) { +printColors(struct pam * const pamP, + CmdLineInfo const cmdLine, + FILE * const ofP, + const RegData * const regSamples) { /*---------------------------------------------------------------------------- - Prints the colors or <regSamples> to channel <outChan> in the format - specified in <cmdLine>. <pamP> is required by the formatting function. + Print the colors regSamples[] to *ofP in the format + requested by 'cmdLine'. + + *pamP tells how to interpret regSamples[] -----------------------------------------------------------------------------*/ char fmt[20]; uint r; tuple tup; tup = pnm_allocpamtuple(pamP); - sprintf(fmt, "%%%is: %%s\n", cmdLine.maxLbLen); - for (r = 0; r < cmdLine.regN; r++) { + + pm_snprintf(fmt, sizeof(fmt), "%%%is: %%s\n", cmdLine.maxLbLen); + + for (r = 0; r < cmdLine.regN; ++r) { RegSpec spec; RegData data; - char const * color; + const char * color; spec = cmdLine.regSpecs[r]; + data = regSamples[r]; - color = formatColor( data, cmdLine, pamP, tup ); - fprintf(outChan, fmt, spec.label, color); + + color = outputColorSpec(data, cmdLine, pamP, tup); + + fprintf(ofP, fmt, spec.label, color); + pm_strfree(color); } pnm_freepamtuple(tup); @@ -478,21 +545,21 @@ printColors(struct pam * const pamP, int -main(int argc, char const *argv[]) { +main(int argc, const char *argv[]) { RegData * regSamples; - CmdlineInfo cmdLine; + CmdLineInfo cmdLine; struct pam pam; pm_proginit(&argc, argv); - cmdLine = parseCommandLine(argc, argv); + cmdLine = parsedCommandLine(argc, argv); - regSamples = getColors(&pam, cmdLine); + regSamples = colorsFmImage(&pam, cmdLine); printColors(&pam, cmdLine, stdout, regSamples); - free(cmdLine.regSpecs); /* Asymmetrical: maybe write freeCommandLine() ? */ + freeCommandLine(cmdLine); free(regSamples); return 0; diff --git a/analyzer/pamsumm.c b/analyzer/pamsumm.c index 7d2c000a..9b74e789 100644 --- a/analyzer/pamsumm.c +++ b/analyzer/pamsumm.c @@ -69,18 +69,18 @@ parseCommandLine(int argc, const char ** const argv, cmdlineP->function = FN_MIN; } else if (maxSpec) { cmdlineP->function = FN_MAX; - } else + } else pm_error("You must specify one of -sum, -min, -max, or -mean"); - + if (argc-1 > 1) pm_error("Too many arguments (%d). File name is the only argument.", argc-1); if (argc-1 < 1) cmdlineP->inputFileName = "-"; - else + else cmdlineP->inputFileName = argv[1]; - + free(option_def); } @@ -122,11 +122,11 @@ aggregate(struct pam * const inpamP, unsigned int plane; for (plane = 0; plane < inpamP->depth; ++plane) { switch(function) { - case FN_ADD: - case FN_MEAN: + case FN_ADD: + case FN_MEAN: accumulatorP->u.sum += tupleRow[col][plane]; break; - case FN_MIN: + case FN_MIN: if (tupleRow[col][plane] < accumulatorP->u.min) accumulatorP->u.min = tupleRow[col][plane]; break; @@ -134,7 +134,7 @@ aggregate(struct pam * const inpamP, if (tupleRow[col][plane] > accumulatorP->u.min) accumulatorP->u.min = tupleRow[col][plane]; break; - } + } } } } @@ -150,7 +150,7 @@ printSummary(struct Accum const accumulator, bool const brief) { switch (function) { - case FN_ADD: { + case FN_ADD: { const char * const intro = brief ? "" : "the sum of all samples is "; if (mustNormalize) @@ -169,7 +169,7 @@ printSummary(struct Accum const accumulator, } break; case FN_MIN: { - const char * const intro = + const char * const intro = brief ? "" : "the minimum of all samples is "; if (mustNormalize) @@ -179,7 +179,7 @@ printSummary(struct Accum const accumulator, } break; case FN_MAX: { - const char * const intro = + const char * const intro = brief ? "" : "the maximum of all samples is "; if (mustNormalize) @@ -221,11 +221,11 @@ main(int argc, const char *argv[]) { aggregate(&inpam, inputRow, cmdline.function, &accumulator); } printSummary(accumulator, (unsigned)inpam.maxval, - inpam.height * inpam.width * inpam.depth, + inpam.height * inpam.width * inpam.depth, cmdline.function, cmdline.normalize, cmdline.brief); pnm_freepamrow(inputRow); pm_close(inpam.file); - + return 0; } diff --git a/analyzer/ppmhist.c b/analyzer/ppmhist.c index 299ab6ca..c4ab3581 100644 --- a/analyzer/ppmhist.c +++ b/analyzer/ppmhist.c @@ -172,7 +172,7 @@ universalMaxval(pixval const maxval, int const format) { /*---------------------------------------------------------------------------- A maxval that makes it impossible for a pixel to be invalid in an image that - states it maxval as 'maxval' and has format 'format'. + states its maxval as 'maxval' and has format 'format'. E.g. in a one-byte-per-sample image, it's not possible to read a sample value greater than 255, so a maxval of 255 makes it impossible for a sample diff --git a/converter/other/fiasco/lib/error.c b/converter/other/fiasco/lib/error.c index 08291ce0..394f896f 100644 --- a/converter/other/fiasco/lib/error.c +++ b/converter/other/fiasco/lib/error.c @@ -3,29 +3,19 @@ * * Written by: Stefan Frank * Ullrich Hafner - * + * * Credits: Modelled after variable argument routines from Jef - * Poskanzer's pbmplus package. + * Poskanzer's pbmplus package. * * This file is part of FIASCO (Fractal Image And Sequence COdec) * Copyright (C) 1994-2000 Ullrich Hafner - - "int dummy = " change to int dummy; dummy =" for Netpbm to avoid - unused variable warning. - - */ - -/* - * $Date: 2000/06/14 20:49:37 $ - * $Author: hafner $ - * $Revision: 5.1 $ - * $State: Exp $ */ #define _ERROR_C #include "config.h" +#include <stdbool.h> #include <stdio.h> #include <errno.h> @@ -47,7 +37,7 @@ /***************************************************************************** local variables - + *****************************************************************************/ static fiasco_verbosity_e verboselevel = FIASCO_SOME_VERBOSITY; @@ -60,63 +50,71 @@ jmp_buf env; /***************************************************************************** public code - + *****************************************************************************/ void -set_error(const char *format, ...) { +set_error(const char * const format, ...) { /*---------------------------------------------------------------------------- Set error text to given string. -----------------------------------------------------------------------------*/ va_list args; unsigned len; + bool error; const char * str; - len = 0; /* initial value */ - str = format; /* initial value */ - VA_START (args, format); - len = strlen (format); - while ((str = strchr (str, '%'))) { - ++str; - if (*str == 's') { - char * const vstring = va_arg (args, char *); - len += strlen(vstring); - } else if (*str == 'd') { - (void)va_arg(args, int); - len += 10; - } else if (*str == 'c') { - (void)va_arg(args, int); - len += 1; - } else - return; - ++str; + /* Compute how long the error text will be: 'len' */ + + for (len = strlen(format), str = &format[0], error = false; + *str && !error; ) { + + str = strchr(str, '%'); + + if (*str) { + ++str; /* Move past % */ + if (*str == 's') { + char * const vstring = va_arg (args, char *); + len += strlen(vstring); + } else if (*str == 'd') { + (void)va_arg(args, int); + len += 10; + } else if (*str == 'c') { + (void)va_arg(args, int); + len += 1; + } else + error = true; + if (!error) + ++str; + } } va_end(args); - VA_START(args, format); + if (!error) { + VA_START(args, format); - if (error_message) - Free(error_message); - error_message = Calloc(len, sizeof (char)); - - vsprintf(error_message, format, args); + if (error_message) + Free(error_message); + error_message = Calloc(len, sizeof (char)); - va_end(args); + vsprintf(error_message, format, args); + + va_end(args); + } } void -error(const char *format, ...) { +error(const char * const format, ...) { /*---------------------------------------------------------------------------- Set error text to given string. - -----------------------------------------------------------------------------*/ +-----------------------------------------------------------------------------*/ va_list args; unsigned len; const char * str; - + len = 0; /* initial value */ str = &format[0]; /* initial value */ @@ -141,7 +139,7 @@ error(const char *format, ...) { exit(1); #endif }; - + ++str; } va_end(args); @@ -151,11 +149,11 @@ error(const char *format, ...) { if (error_message) Free(error_message); error_message = Calloc(len, sizeof (char)); - + vsprintf(error_message, format, args); va_end(args); - + #if HAVE_SETJMP_H longjmp(env, 1); #else @@ -166,117 +164,123 @@ error(const char *format, ...) { const char * -fiasco_get_error_message (void) -/* - * Return value: - * Last error message of FIASCO library. - */ -{ - return error_message ? error_message : ""; +fiasco_get_error_message(void) { +/*---------------------------------------------------------------------------- + Last error message of FIASCO library. +-----------------------------------------------------------------------------*/ + return error_message ? error_message : ""; } + + const char * -get_system_error (void) -{ - return strerror (errno); +get_system_error(void) { + return strerror(errno); } + + void -file_error (const char *filename) -/* - * Print file error message and exit. - * - * No return value. - */ -{ - error ("File `%s': I/O Error - %s.", filename, get_system_error ()); +file_error(const char * const filename) { +/*---------------------------------------------------------------------------- + Print file error message and exit. +-----------------------------------------------------------------------------*/ + error("File `%s': I/O Error - %s.", filename, get_system_error ()); } -void -warning (const char *format, ...) -/* - * Issue a warning and continue execution. - * - * No return value. - */ -{ - va_list args; - VA_START (args, format); - if (verboselevel == FIASCO_NO_VERBOSITY) - return; - - fprintf (stderr, "Warning: "); - vfprintf (stderr, format, args); - fputc ('\n', stderr); +void +warning(const char * const format, ...) { +/*---------------------------------------------------------------------------- + Issue a warning. +-----------------------------------------------------------------------------*/ + va_list args; + + VA_START (args, format); - va_end (args); + if (verboselevel == FIASCO_NO_VERBOSITY) { + /* User doesn't want warnings */ + } else { + fprintf (stderr, "Warning: "); + vfprintf (stderr, format, args); + fputc ('\n', stderr); + } + va_end (args); } -void -message (const char *format, ...) -/* - * Print a message to stderr. - */ -{ - va_list args; - VA_START (args, format); - if (verboselevel == FIASCO_NO_VERBOSITY) - return; +void +message(const char * const format, ...) { +/*---------------------------------------------------------------------------- + Print a message to Standard Error +-----------------------------------------------------------------------------*/ + va_list args; - vfprintf (stderr, format, args); - fputc ('\n', stderr); - va_end (args); + VA_START (args, format); + + if (verboselevel == FIASCO_NO_VERBOSITY) { + /* User doesn't want messages */ + } else { + vfprintf (stderr, format, args); + fputc ('\n', stderr); + } + va_end (args); } -void -debug_message (const char *format, ...) -/* - * Print a message to stderr. - */ -{ - va_list args; - VA_START (args, format); - if (verboselevel < FIASCO_ULTIMATE_VERBOSITY) - return; +void +debug_message(const char * const format, ...) { +/*---------------------------------------------------------------------------- + Print a message to Standard Error if debug messages are enabled. +-----------------------------------------------------------------------------*/ + va_list args; - fprintf (stderr, "*** "); - vfprintf (stderr, format, args); - fputc ('\n', stderr); - va_end (args); + VA_START (args, format); + + if (verboselevel >= FIASCO_ULTIMATE_VERBOSITY) { + fprintf (stderr, "*** "); + vfprintf (stderr, format, args); + fputc ('\n', stderr); + } + va_end (args); } -void -info (const char *format, ...) -/* - * Print a message to stderr. Do not append a newline. - */ -{ - va_list args; - VA_START (args, format); - if (verboselevel == FIASCO_NO_VERBOSITY) - return; +void +info(const char * const format, ...) { +/*---------------------------------------------------------------------------- + Print a message to stderr. Do not append a newline. +-----------------------------------------------------------------------------*/ + va_list args; + + VA_START (args, format); - vfprintf (stderr, format, args); - fflush (stderr); - va_end (args); + if (verboselevel == FIASCO_NO_VERBOSITY) { + /* User doesn't want informational messages */ + } else { + vfprintf (stderr, format, args); + fflush (stderr); + } + va_end (args); } + + void -fiasco_set_verbosity (fiasco_verbosity_e level) -{ +fiasco_set_verbosity(fiasco_verbosity_e const level) { verboselevel = level; } + + fiasco_verbosity_e -fiasco_get_verbosity (void) -{ +fiasco_get_verbosity(void) { return verboselevel; } + + + diff --git a/doc/HISTORY b/doc/HISTORY index cf618983..7076a357 100644 --- a/doc/HISTORY +++ b/doc/HISTORY @@ -4,17 +4,34 @@ Netpbm. CHANGE HISTORY -------------- -18.09.08 BJH Release 10.83.02 +18.09.29 BJH Release 10.84.00 - pamgetcolor: fix bug: gets color of only the top half of a - region. + Add pamaltsat. Thanks Anton Shepelev <anton.txt@gmail.com>. + + Add pamtris. Thanks Lucas Brunno Luna + <lucaslunar32@hotmail.com>. + + libpbmfont, pbmtext: fix bugs with BDF file lines with + insufficient number of fields. Unknown effect. + + pbmtext: -wchar works with built-in fonts. -18.07.07 BJH Release 10.83.01 + pbmtext: improved -verbose information about BDF fonts: + include CHARSET_REGISTRY, CHARSET_ENCODING. + + libnetpbm font facilities: built-in fonts work with wide + characters. pbmtext; libnetpbm BDF font processing: fix invalid memory reference when BDF font file has invalid syntax. Broken in primordial Netpbm, ca 1993. + pamgetcolor: fix bug: gets color of only the top half of a + region. + + pnmfiasco, fiascotopnm: Fix trivial memory leak. Always broken + (programs were new in Netpbm 9.6, July 2000). + 18.06.30 BJH Release 10.83.00 Add pamlevels. Thanks Anton Shepelev <anton.txt@gmail.com>. diff --git a/editor/Makefile b/editor/Makefile index d7d71bf6..159f8ea7 100644 --- a/editor/Makefile +++ b/editor/Makefile @@ -16,7 +16,7 @@ SUBDIRS = pamflip specialty # This package is so big, it's useful even when some parts won't # build. -PORTBINARIES = pamaddnoise pambackground pamcomp pamcut \ +PORTBINARIES = pamaddnoise pamaltsat pambackground pamcomp pamcut \ pamdice pamditherbw pamedge \ pamenlarge \ pamfunc pamlevels pammasksharpen \ diff --git a/editor/pamaltsat.c b/editor/pamaltsat.c new file mode 100644 index 00000000..6d9b91e0 --- /dev/null +++ b/editor/pamaltsat.c @@ -0,0 +1,535 @@ +#include <stdbool.h> +#include <assert.h> +#include <string.h> + +#include <pam.h> +#include <pm_gamma.h> +#include <nstring.h> + +#include "shhopt.h" +#include "mallocvar.h" + +typedef unsigned int uint; +typedef unsigned char uchar; + +typedef enum {MLog, MSpectrum } Method; /* method identifiers */ + +typedef struct { + Method method; + const char * name; +} MethodTableEntry; + +MethodTableEntry methodTable[] = { + {MLog, "log"}, + {MSpectrum, "spectrum"} +}; + +/* Command-line arguments parsed: */ +typedef struct { + const char * inputFileName; + /* name of the input file. "-" for stdin */ + float strength; + uint linear; + Method method; +} CmdlineInfo; + + + + +static Method +methodFmNm(const char * const methodNm) { +/*---------------------------------------------------------------------------- + The method of saturation whose name is 'methodNm' +-----------------------------------------------------------------------------*/ + uint i; + bool found; + Method method; + + for (i = 0, found = false; i < ARRAY_SIZE(methodTable) && !found; ++i) { + if (streq(methodNm, methodTable[i].name)) { + found = true; + method = methodTable[i].method; + } + } + + if (!found) { + /* Issue error message and abort */ + char * methodList; + uint methodListLen; + uint i; + + /* Allocate a buffer to store the list of known saturation methods: */ + for (i = 0, methodListLen = 0; i < ARRAY_SIZE(methodTable); ++i) + methodListLen += strlen(methodTable[i].name) + 2; + + MALLOCARRAY(methodList, methodListLen); + + if (!methodList) + pm_error("Failed to allocate memory for %lu saturation " + "method names", (unsigned long)ARRAY_SIZE(methodTable)); + + /* Fill the list of methods: */ + for (i = 0, methodList[0] = '\0'; i < ARRAY_SIZE(methodTable); ++i) { + if (i > 0) + strcat(methodList, ", "); + strcat(methodList, methodTable[i].name); + } + + pm_error("Unknown saturation method: '%s'. Known methods are: %s", + methodNm, methodList); + + free(methodList); + } + return method; +} + + + +static CmdlineInfo +parsedCommandLine(int argc, const char ** argv) { + + CmdlineInfo cmdline; + optStruct3 opt; + + uint option_def_index; + uint methodSpec, strengthSpec, linearSpec; + const char * method; + + optEntry * option_def; + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "method", OPT_STRING, &method, &methodSpec, 0); + OPTENT3(0, "strength", OPT_FLOAT, &cmdline.strength, &strengthSpec, 0); + OPTENT3(0, "linear", OPT_FLAG, &cmdline.linear, &linearSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = 0; + opt.allowNegNum = 0; + + pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (methodSpec) + cmdline.method = methodFmNm(method); + else + cmdline.method = MSpectrum; + + if (!strengthSpec) + pm_error("You must specify -strength"); + + if (!linearSpec) + cmdline.linear = 0; + + if (argc-1 < 1) + cmdline.inputFileName = "-"; + else { + cmdline.inputFileName = argv[1]; + if (argc-1 > 1) + pm_error("Program takes at most one argument: file name"); + } + + free(option_def); + + return cmdline; +} + + + +typedef struct { + double _[3]; +} TupleD; + +typedef struct { +/*---------------------------------------------------------------------------- + Information about a color sample in linear format +-----------------------------------------------------------------------------*/ + TupleD sample; /* layer intensities */ + double maxval; /* the highest layer intensity */ + uint maxl; /* index of that layer */ + uint minl; /* index of the layer with lowest intensity */ + double intensity; /* total sample intensity */ +} LinSampleInfo; + +/* ---------------------------- Binary search ------------------------------ */ +/* ( a minimal drop-in implementation ) */ + +/* Function to search, where <data> is an arbitrary user-supplied parameter */ +typedef double (binsearchFunc)(double const x, + const void * const data); + +/* The binary-search function. Returns such <x> from [<min>, <max>] that + monotonically increasing function func(x, data) equals <value> within + precision <prec>. <dataP> is an arbitary parameter to <func>. */ +static double +binsearch(binsearchFunc func, + const void * const dataP, + double const prec, + double const minArg, + double const maxArg, + double const value + ) { + double x; + double min, max; + bool found; + + for (min = minArg, max = maxArg, found = false; !found;) { + + x = (min + max) / 2; + { + double const f = func(x, dataP); + + if ((fabs(f - value)) < prec) + found = true; + else { + assert(f != value); + + if (f > value) max = x; + else min = x; + } + } + } + return x; +} + +/* ------------- Utilities not specific to saturation methods -------------- */ + +/* Y chromaticities in Rec.709: R G B */ +static double const yCoeffs[3] = {0.3333, 0.6061, 0.0606}; + +static void +applyRatio(TupleD * const tupP, + double const ratio) { +/*---------------------------------------------------------------------------- + Multiply the components of tuple *tupP by coefficient 'ratio'. +-----------------------------------------------------------------------------*/ + uint c; + + for (c = 0; c < 3; ++c) + tupP->_[c] = tupP->_[c] * ratio; +} + + + +static void +getTupInfo(tuplen const tup, + bool const linear, + LinSampleInfo * const siP) { +/*---------------------------------------------------------------------------- + Convert PBM tuple <tup> into linear form with double precision siP->sample + and obtain also additional information required for further processing. + Return the result as *siP. +-----------------------------------------------------------------------------*/ + uint i; + double minval; + + minval = 1.1; + siP->intensity = 0; + siP->maxval = 0.0; + siP->maxl = 0; + + for (i = 0; i < 3; ++i) { + double linval; + if (!linear) + linval = pm_ungamma709(tup[i]); + else + linval = tup[i]; + + siP->sample._[i] = linval; + + if (linval > siP->maxval) { + siP->maxval = linval; + siP->maxl = i; + } + if (linval < minval) { + siP->minl = i; + minval = linval; + } + siP->intensity += linval * yCoeffs[i]; + } +} + +/* ------------------------ Logarithmic saturation ------------------------- */ + +/* Method and algorithm by Anton Shepelev. */ + +static void +tryLogSat(double const sat, + LinSampleInfo * const siP, + TupleD * const tupsatP, + double * const intRatioP, + double * const highestP) { +/*---------------------------------------------------------------------------- + Try to increase the saturation of siP->sample by a factor 'sat' and return + the result as *tupsatP. + + Also return as *intRatioP the ratio of intensities of 'tupin' and + siP->sample. + + Return as *highestP the highest component the saturated color would have if + normalized to intensity siP->intensity. + + If the return value exceeds unity saturation cannot be properly increased by + the required factor. +-----------------------------------------------------------------------------*/ + uint c; + double intSat; + + for (c = 0, intSat = 0.0; c < 3; ++c) { + tupsatP->_[c] = pow(siP->sample._[c], sat); + intSat = intSat + tupsatP->_[c] * yCoeffs[c]; + } + + { + double const intRatio = siP->intensity / intSat; + + double const maxComp = tupsatP->_[siP->maxl] * intRatio; + + *intRatioP = intRatio; + *highestP = maxComp; + } +} + + + +/* Structure for the binary search of maximum saturation: */ +typedef struct { + LinSampleInfo * siP; + /* original color with precalculated information */ + TupleD * tupsatP; + /* saturated color */ + double * intRatioP; + /* ratio of orignal and saturated intensities */ +} MaxLogSatInfo; + + + +static binsearchFunc binsearchMaxLogSat; + +static double +binsearchMaxLogSat(double const x, + const void * const dataP) { +/*---------------------------------------------------------------------------- + Target function for the generic binary search routine, for the finding + of the maximum possible saturation of a given color. 'dataP' shall point + to a MaxSatInfo structure. +-----------------------------------------------------------------------------*/ + const MaxLogSatInfo * const infoP = dataP; + + double highest; + + tryLogSat(x, infoP->siP, infoP->tupsatP, infoP->intRatioP, &highest); + + return highest; +} + + + +static void +getMaxLogSat(LinSampleInfo * const siP, + TupleD * const tupsatP, + double * const intRatioP, + double const upperLimit + ) { +/* Saturates the color <siP->sample> as much as possible and stores the result + in <tupsatP>, which must be multiplied by <*intRatioP> in order to restore + the intensity of the original color. The range of saturation search is + [1.0..<upperlimit>]. */ + const double PREC = 0.00001; /* precision of binary search */ + + MaxLogSatInfo info; + + info.siP = siP; + info.tupsatP = tupsatP; + info.intRatioP = intRatioP; + +/* Discarding return value (maximum saturation) because upon completion of + binsearch() info.tupsatP will contain the saturated color. The target value + of maximum channel intensity is decreased by PREC in order to avoid + overlow. */ + binsearch(binsearchMaxLogSat, &info, PREC, 1.0, upperLimit, 1.0 - PREC); +} + + + +static void +saturateLog(LinSampleInfo* const siP, + double const sat, + TupleD* const tupsatP) { +/*---------------------------------------------------------------------------- + Saturate linear tuple *siP using the logarithmic saturation method. +-----------------------------------------------------------------------------*/ + double intRatio; + /* ratio of original and saturated intensities */ + double maxlValSat; + /* maximum component intensity in the saturated sample */ + + tryLogSat(sat, siP, tupsatP, &intRatio, &maxlValSat); + + /* if we cannot saturate siP->sample by 'sat', use the maximum possible + saturation + */ + if (maxlValSat > 1.0) + getMaxLogSat(siP, tupsatP, &intRatio, sat); + + /* restore the original intensity: */ + applyRatio(tupsatP, intRatio); +} + + + +/* ------------------------- Spectrum saturation --------------------------- */ + +/* Method and algorithm by Anton Shepelev. */ + +static void +saturateSpectrum(LinSampleInfo * const siP, + double const sat, + TupleD * const tupsatP) { +/*---------------------------------------------------------------------------- + Saturate linear tuple *siP using the Spectrum saturation method. +-----------------------------------------------------------------------------*/ + double k; + double * sample; + + sample = siP->sample._; /* short-cut to the input sample data */ + + if (sample[siP->minl] == sample[siP->maxl]) + k = 1.0; /* Cannot saturate a neutral sample */ + else { + double const km1 = + (1.0 - siP->intensity)/(siP->maxval - siP->intensity); + /* Maximum saturation factor that keeps maximum layer intesity + within range + */ + double const km2 = siP->intensity/(siP->intensity - sample[siP->minl]); + /* Maximum saturation factor that keeps minimum layer intesity + within range + */ + + /* To satisfy both constraints, choose the strictest: */ + double const km = km1 > km2 ? km2 : km1; + + /* Ensure the saturation factor does not exceed the maximum + possible value: + */ + k = sat < km ? sat : km; + } + + { + /* Initialize the resulting sample with the input value */ + uint i; + for (i = 0; i < 3; ++i) + tupsatP->_[i] = sample[i]; + } + + applyRatio(tupsatP, k); /* apply the saturation factor */ + + { + /* restore the original intensity */ + uint i; + for (i = 0; i < 3; ++i) + tupsatP->_[i] = tupsatP->_[i] - siP->intensity * (k - 1.0); + } +} + + + +/* --------------------- General saturation algorithm ---------------------- */ + +static void +saturateTup(Method const method, + double const sat, + bool const linear, + tuplen const tup) { +/*---------------------------------------------------------------------------- + Saturate black and white tuple 'tup' +-----------------------------------------------------------------------------*/ + LinSampleInfo si; + + getTupInfo(tup, linear, &si); + + if (sat < 1.0 || /* saturation can always be decresed */ + si.maxval < 1.0 ) { /* there is room for increase */ + + TupleD tupsat; + + /* Dispatch saturation methods: + (There seems too little benefit in using a table of + function pointers, so a manual switch should suffice) + */ + switch (method) { + case MLog: saturateLog (&si, sat, &tupsat); break; + case MSpectrum: saturateSpectrum(&si, sat, &tupsat); break; + } + + /* Put the processed tuple back in the tuple row, gamma-adjusting it + if required. + */ + { + uint i; + + for (i = 0; i < 3; ++i) + tup[i] = linear ? tupsat._[i] : pm_gamma709(tupsat._[i]); + } + } +} + + + +static void +pamaltsat(CmdlineInfo const cmdline, + FILE * const ofP) { + + struct pam inPam, outPam; + tuplen * tuplerown; + FILE * ifP; + uint row; + + ifP = pm_openr(cmdline.inputFileName); + + pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type)); + + outPam = inPam; + outPam.file = ofP; + + tuplerown = pnm_allocpamrown(&inPam); + + pnm_writepaminit(&outPam); + + for (row = 0; row < inPam.height; ++row) { + pnm_readpamrown(&inPam, tuplerown); + + if (inPam.depth >= 3) { + uint col; + + for (col = 0; col < inPam.width; ++col) + saturateTup(cmdline.method, cmdline.strength, cmdline.linear, + tuplerown[col]); + } + + pnm_writepamrown(&outPam, tuplerown); + } + + pnm_freepamrown(tuplerown); + pm_close(ifP); +} + + + +int +main(int argc, const char ** argv) { + + CmdlineInfo cmdline; + + pm_proginit(&argc, argv); + + cmdline = parsedCommandLine(argc, argv); + + pamaltsat(cmdline, stdout); + + return 0; +} + + + diff --git a/editor/pamscale.c b/editor/pamscale.c index 433d5cee..27902dbf 100644 --- a/editor/pamscale.c +++ b/editor/pamscale.c @@ -24,6 +24,7 @@ #define _XOPEN_SOURCE 500 /* get M_PI in math.h */ +#include <stdbool.h> #include <stdlib.h> #include <stdio.h> #include <math.h> @@ -359,23 +360,23 @@ typedef struct { static filter Filters[] = { - { "point", filter_box, radius_point, FALSE }, - { "box", filter_box, radius_box, FALSE }, - { "triangle", filter_triangle, radius_triangle, FALSE }, - { "quadratic", filter_quadratic, radius_quadratic, FALSE }, - { "cubic", filter_cubic, radius_cubic, FALSE }, - { "catrom", filter_catrom, radius_catrom, FALSE }, - { "mitchell", filter_mitchell, radius_mitchell, FALSE }, - { "gauss", filter_gauss, radius_gauss, FALSE }, - { "sinc", filter_sinc, radius_sinc, TRUE }, - { "bessel", filter_bessel, radius_bessel, TRUE }, - { "hanning", filter_hanning, radius_hanning, FALSE }, - { "hamming", filter_hamming, radius_hamming, FALSE }, - { "blackman", filter_blackman, radius_blackman, FALSE }, - { "kaiser", filter_kaiser, radius_kaiser, FALSE }, - { "normal", filter_normal, radius_normal, FALSE }, - { "hermite", filter_hermite, radius_hermite, FALSE }, - { "lanczos", filter_lanczos, radius_lanczos, FALSE }, + { "point", filter_box, radius_point, false }, + { "box", filter_box, radius_box, false }, + { "triangle", filter_triangle, radius_triangle, false }, + { "quadratic", filter_quadratic, radius_quadratic, false }, + { "cubic", filter_cubic, radius_cubic, false }, + { "catrom", filter_catrom, radius_catrom, false }, + { "mitchell", filter_mitchell, radius_mitchell, false }, + { "gauss", filter_gauss, radius_gauss, false }, + { "sinc", filter_sinc, radius_sinc, true }, + { "bessel", filter_bessel, radius_bessel, true }, + { "hanning", filter_hanning, radius_hanning, false }, + { "hamming", filter_hamming, radius_hamming, false }, + { "blackman", filter_blackman, radius_blackman, false }, + { "kaiser", filter_kaiser, radius_kaiser, false }, + { "normal", filter_normal, radius_normal, false }, + { "hermite", filter_hermite, radius_hermite, false }, + { "lanczos", filter_lanczos, radius_lanczos, false }, { NULL }, }; @@ -452,12 +453,12 @@ lookupFilterByName(const char * const filtername, unsigned int i; bool found; - found = FALSE; /* initial assumption */ + found = false; /* initial assumption */ for (i=0; Filters[i].name; ++i) { if (strcmp(filtername, Filters[i].name) == 0) { *filterP = Filters[i]; - found = TRUE; + found = true; } } if (!found) { @@ -641,7 +642,8 @@ parseCommandLine(int argc, int xsize, ysize, pixels; int reduce; float xscale, yscale; - const char *filterOpt, *window; + const char * filterOpt; + const char * window; unsigned int filterSpec, windowSpec; unsigned int xscaleSpec, yscaleSpec, xsizeSpec, ysizeSpec; unsigned int pixelsSpec, reduceSpec; @@ -667,8 +669,8 @@ parseCommandLine(int argc, OPTENT3(0, "linear", OPT_FLAG, NULL, &cmdlineP->linear, 0); opt.opt_table = option_def; - opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ - opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + opt.short_allowed = false; /* We have no short (old-fashioned) options */ + opt.allowNegNum = false; /* We have no parms that are negative numbers */ pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ @@ -1440,7 +1442,7 @@ static bool scanbufContainsTheRows(SCAN const scanbuf, WLIST const rowWeights) { /*---------------------------------------------------------------------------- - Return TRUE iff scanbuf 'scanbuf' contains every row mentioned in + Return true iff scanbuf 'scanbuf' contains every row mentioned in 'rowWeights'. It might contain additional rows besides. @@ -1448,7 +1450,7 @@ scanbufContainsTheRows(SCAN const scanbuf, bool missingRow; unsigned int i; - for (i = 0, missingRow = FALSE; + for (i = 0, missingRow = false; i < rowWeights.nWeight && !missingRow; ++i) { unsigned int const inputRow = rowWeights.Weight[i].position; @@ -1461,7 +1463,7 @@ scanbufContainsTheRows(SCAN const scanbuf, /* Nope, this slot has some other row or no row at all. So the row we're looking for isn't in the scanbuf. */ - missingRow = TRUE; + missingRow = true; } } return !missingRow; @@ -1569,7 +1571,7 @@ resample(struct pam * const inpamP, /* Output all the rows we can make out of the current contents of the scanbuf. Might be none. */ - needMoreInput = FALSE; /* initial assumption */ + needMoreInput = false; /* initial assumption */ while (outputRow < outpamP->height && !needMoreInput) { WLIST const rowWeights = vertWeight[outputRow]; /* The description of what makes up our current output row; @@ -1583,7 +1585,7 @@ resample(struct pam * const inpamP, horizWeight, line, weight); ++outputRow; } else - needMoreInput = TRUE; + needMoreInput = true; } } @@ -2213,7 +2215,7 @@ main(int argc, const char **argv ) { ifP = pm_openr(cmdline.inputFileName); - eof = FALSE; + eof = false; while (!eof) { pamscale(ifP, stdout, cmdline); pnm_nextimage(ifP, &eof); diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c index a7aba2e7..6ee6897b 100644 --- a/editor/ppmbrighten.c +++ b/editor/ppmbrighten.c @@ -31,11 +31,11 @@ struct cmdlineInfo { static void -parseCommandLine(int argc, char ** argv, +parseCommandLine(int argc, const char ** argv, struct cmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- parse program command line described in Unix standard form by argc - and argv. Return the information in the options as *cmdlineP. + and argv. Return the information in the options as *cmdlineP. If command line is internally inconsistent (invalid options, etc.), issue error message to stderr and abort program. @@ -68,9 +68,9 @@ parseCommandLine(int argc, char ** argv, opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ - pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0); + pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ - + if (saturationSpec) { if (saturationOpt < -100) pm_error("Saturation reduction cannot be more than 100%%. " @@ -106,17 +106,17 @@ mod(int const dividend, unsigned int const divisor) { if (remainder < 0) return divisor + remainder; - else + else return (unsigned int) remainder; } -static void +static void RGBtoHSV(pixel const color, pixval const maxval, - unsigned int * const hP, - unsigned int * const sP, + unsigned int * const hP, + unsigned int * const sP, unsigned int * const vP) { unsigned int const R = (MULTI * PPM_GETR(color) + maxval - 1) / maxval; @@ -161,12 +161,12 @@ RGBtoHSV(pixel const color, static void -HSVtoRGB(unsigned int const h, - unsigned int const s, - unsigned int const v, +HSVtoRGB(unsigned int const h, + unsigned int const s, + unsigned int const v, pixval const maxval, pixel * const colorP) { - + unsigned int R, G, B; if (s == 0) { @@ -222,7 +222,7 @@ HSVtoRGB(unsigned int const h, pm_error("Invalid H value passed to HSVtoRGB: %u/%u", h, MULTI); } } - PPM_ASSIGN(*colorP, + PPM_ASSIGN(*colorP, (R * maxval) / MULTI, (G * maxval) / MULTI, (B * maxval) / MULTI); @@ -267,7 +267,7 @@ getMinMax(FILE * const ifP, int -main(int argc, char * argv[]) { +main(int argc, const char ** argv) { struct cmdlineInfo cmdline; FILE * ifP; @@ -276,7 +276,7 @@ main(int argc, char * argv[]) { pixval maxval; int rows, cols, format, row; - ppm_init(&argc, argv); + pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); @@ -311,7 +311,7 @@ main(int argc, char * argv[]) { unsigned int H, S, V; RGBtoHSV(pixelrow[col], maxval, &H, &S, &V); - + if (cmdline.normalize) { V -= minValue; V = (V * MULTI) / @@ -335,3 +335,6 @@ main(int argc, char * argv[]) { */ return 0; } + + + diff --git a/editor/ppmcolormask.c b/editor/ppmcolormask.c index d9f68b68..3812ac86 100644 --- a/editor/ppmcolormask.c +++ b/editor/ppmcolormask.c @@ -66,10 +66,10 @@ parseColorOpt(const char * const colorOpt, char * colorOptWork; char * cursor; bool eol; - + colorOptWork = strdup(colorOpt); cursor = &colorOptWork[0]; - + eol = FALSE; /* initial value */ colorCt = 0; /* initial value */ while (!eol && colorCt < ARRAY_SIZE(cmdlineP->maskColor)) { @@ -150,7 +150,7 @@ parseCommandLine(int argc, const char ** argv, cmdlineP->inputFilename = "-"; /* he wants stdin */ else if (argc-1 == 2) cmdlineP->inputFilename = argv[2]; - else + else pm_error("Too many arguments. The only arguments accepted " "are the mask color and optional input file name"); } @@ -191,7 +191,7 @@ isBkColor(tuple const comparator, /* TODO: keep a cache of the bk color for each color in a colorhash_table. */ - + assert(pamP->depth >= 3); PPM_ASSIGN(comparatorPixel, @@ -281,7 +281,7 @@ main(int argc, const char *argv[]) { if (colorIsInSet(inputRow[col], &inPam, cmdline)) { maskRow[col][0] = PAM_BLACK; ++numPixelsMasked; - } else + } else maskRow[col][0] = PAM_BW_WHITE; } pnm_writepamrow(&outPam, maskRow); diff --git a/editor/ppmdraw.c b/editor/ppmdraw.c index b2ed39ca..c76489c9 100644 --- a/editor/ppmdraw.c +++ b/editor/ppmdraw.c @@ -1,5 +1,5 @@ #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 500 /* Make sure M_PI is in math.h, strdup is in string.h */ #define _BSD_SOURCE /* Make sure strdup is in string.h (alternate) */ @@ -50,7 +50,7 @@ parseCommandLine (int argc, const char ** argv, struct cmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- parse program command line described in Unix standard form by argc - and argv. Return the information in the options as *cmdlineP. + and argv. Return the information in the options as *cmdlineP. If command line is internally inconsistent (invalid options, etc.), issue error message to stderr and abort program. @@ -84,7 +84,7 @@ parseCommandLine (int argc, const char ** argv, pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ - + if (!scriptSpec && !scriptfileSpec) pm_error("You must specify either -script or -scriptfile"); @@ -282,7 +282,7 @@ struct drawCommand { static void freeDrawCommand(const struct drawCommand * const commandP) { - + switch (commandP->verb) { case VERB_SETPOS: break; @@ -313,7 +313,7 @@ freeDrawCommand(const struct drawCommand * const commandP) { pm_strfree(commandP->u.textArg.text); break; } - + free((void *) commandP); } @@ -361,21 +361,21 @@ doFilledCircle(pixel ** const pixels, struct fillobj * fhP; fhP = ppmd_fill_create(); - + ppmd_circle(pixels, cols, rows, maxval, commandP->u.circleArg.cx, commandP->u.circleArg.cy, commandP->u.circleArg.radius, ppmd_fill_drawproc, fhP); - + ppmd_fill(pixels, cols, rows, maxval, fhP, PPMD_NULLDRAWPROC, &drawStateP->color); ppmd_fill_destroy(fhP); -} +} @@ -386,7 +386,7 @@ doTextHere(pixel ** const pixels, pixval const maxval, const struct drawCommand * const commandP, struct drawState * const drawStateP) { - + ppmd_text(pixels, cols, rows, maxval, drawStateP->currentPos.x, drawStateP->currentPos.y, @@ -395,14 +395,14 @@ doTextHere(pixel ** const pixels, commandP->u.textArg.text, PPMD_NULLDRAWPROC, &drawStateP->color); - + { int left, top, right, bottom; - + ppmd_text_box(commandP->u.textArg.height, 0, commandP->u.textArg.text, &left, &top, &right, &bottom); - + drawStateP->currentPos.x += ROUND((right-left) * cosdeg(commandP->u.textArg.angle)); @@ -529,10 +529,10 @@ executeScript(struct script * const scriptP, struct tokenSet { - + const char * token[10]; unsigned int count; - + }; @@ -571,7 +571,7 @@ parseDrawCommand(struct tokenSet const commandTokens, if (streq(typeArg, "normal")) drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL; else if (streq(typeArg, "normal")) - drawCommandP->u.setlinetypeArg.type = + drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NODIAGS; else pm_error("Invalid type"); @@ -610,7 +610,7 @@ parseDrawCommand(struct tokenSet const commandTokens, drawCommandP->u.lineArg.y0 = atoi(commandTokens.token[2]); drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]); drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]); - } + } } else if (streq(verb, "line_here")) { drawCommandP->verb = VERB_LINE_HERE; if (commandTokens.count < 3) @@ -621,7 +621,7 @@ parseDrawCommand(struct tokenSet const commandTokens, &drawCommandP->u.lineHereArg; argP->right = atoi(commandTokens.token[1]); argP->down = atoi(commandTokens.token[2]); - } + } } else if (streq(verb, "spline3")) { drawCommandP->verb = VERB_SPLINE3; if (commandTokens.count < 7) @@ -636,7 +636,7 @@ parseDrawCommand(struct tokenSet const commandTokens, argP->y1 = atoi(commandTokens.token[4]); argP->x2 = atoi(commandTokens.token[5]); argP->y2 = atoi(commandTokens.token[6]); - } + } } else if (streq(verb, "circle")) { drawCommandP->verb = VERB_CIRCLE; if (commandTokens.count < 4) @@ -647,7 +647,7 @@ parseDrawCommand(struct tokenSet const commandTokens, argP->cx = atoi(commandTokens.token[1]); argP->cy = atoi(commandTokens.token[2]); argP->radius = atoi(commandTokens.token[3]); - } + } } else if (streq(verb, "filledcircle")) { drawCommandP->verb = VERB_FILLEDCIRCLE; if (commandTokens.count < 4) @@ -658,7 +658,7 @@ parseDrawCommand(struct tokenSet const commandTokens, argP->cx = atoi(commandTokens.token[1]); argP->cy = atoi(commandTokens.token[2]); argP->radius = atoi(commandTokens.token[3]); - } + } } else if (streq(verb, "filledrectangle")) { drawCommandP->verb = VERB_FILLEDRECTANGLE; if (commandTokens.count < 5) @@ -671,7 +671,7 @@ parseDrawCommand(struct tokenSet const commandTokens, argP->y = atoi(commandTokens.token[2]); argP->width = atoi(commandTokens.token[3]); argP->height = atoi(commandTokens.token[4]); - } + } } else if (streq(verb, "text")) { drawCommandP->verb = VERB_TEXT; if (commandTokens.count < 6) @@ -713,9 +713,9 @@ disposeOfCommandTokens(struct tokenSet * const tokenSetP, /* We've got a whole command in 'tokenSet'. Parse it into *scriptP and reset tokenSet to empty. */ - + struct commandListElt * commandListEltP; - + MALLOCVAR(commandListEltP); if (commandListEltP == NULL) pm_error("Out of memory allocating command list element frame"); @@ -747,12 +747,14 @@ processToken(const char * const scriptText, struct script * const scriptP, struct tokenSet * const tokenSetP) { - char * token; unsigned int const tokenLength = cursor - tokenStart; + + char * token; + MALLOCARRAY_NOFAIL(token, tokenLength + 1); memcpy(token, &scriptText[tokenStart], tokenLength); token[tokenLength] = '\0'; - + if (streq(token, ";")) { disposeOfCommandTokens(tokenSetP, scriptP); free(token); @@ -779,7 +781,7 @@ parseScript(const char * const scriptText, */ bool quotedToken; /* Current token is a quoted string. Meaningless if 'intoken' - is false + is false */ struct tokenSet tokenSet; @@ -803,7 +805,7 @@ parseScript(const char * const scriptText, while (scriptText[cursor] != '\0') { char const scriptChar = scriptText[cursor]; - + if (intoken) { if ((quotedToken && scriptChar == '"') || (!quotedToken && (isspace(scriptChar) || scriptChar == ';'))) { @@ -835,7 +837,7 @@ parseScript(const char * const scriptText, } } ++cursor; - } + } } if (intoken) { @@ -879,7 +881,7 @@ getScript(struct cmdlineInfo const cmdline, pm_strfree(scriptText); } - + static void doOneImage(FILE * const ifP, @@ -888,13 +890,13 @@ doOneImage(FILE * const ifP, pixel ** pixels; pixval maxval; int rows, cols; - + pixels = ppm_readppm(ifP, &cols, &rows, &maxval); - + executeScript(scriptP, pixels, cols, rows, maxval); - + ppm_writeppm(stdout, pixels, cols, rows, maxval, 0); - + ppm_freearray(pixels, rows); } diff --git a/generator/Makefile b/generator/Makefile index 5120008c..d54a6cc5 100644 --- a/generator/Makefile +++ b/generator/Makefile @@ -7,6 +7,8 @@ VPATH=.:$(SRCDIR)/$(SUBDIR) include $(BUILDDIR)/config.mk +SUBDIRS = pamtris + # We tend to separate out the build targets so that we don't have # any more dependencies for a given target than it really needs. # That way, if there is a problem with a dependency, we can still @@ -39,6 +41,6 @@ OBJECTS = $(BINARIES:%=%.o) MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2) .PHONY: all -all: $(BINARIES) +all: $(BINARIES) $(SUBDIRS:%=%/all) include $(SRCDIR)/common.mk diff --git a/generator/pamtris/Makefile b/generator/pamtris/Makefile new file mode 100644 index 00000000..d27606e3 --- /dev/null +++ b/generator/pamtris/Makefile @@ -0,0 +1,27 @@ +ifeq ($(SRCDIR)x,x) + SRCDIR = $(CURDIR)/../.. + BUILDDIR = $(SRCDIR) +endif +SUBDIR = generator/pamtris +VPATH=.:$(SRCDIR)/$(SUBDIR) + +include $(BUILDDIR)/config.mk + +PORTBINARIES = pamtris + +MERGEBINARIES = $(PORTBINARIES) + +BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES) + +ADDL_OBJECTS = boundaries.o framebuffer.o input.o triangle.o utils.o + +OBJECTS = pamtris.o $(ADDL_OBJECTS) + +MERGE_OBJECTS = pamtris.o2 $(ADDL_OBJECTS) + +.PHONY: all +all: $(BINARIES) + +pamtris:%:%.o $(ADDL_OBJECTS) + +include $(SRCDIR)/common.mk diff --git a/generator/pamtris/boundaries.c b/generator/pamtris/boundaries.c new file mode 100644 index 00000000..8ea28682 --- /dev/null +++ b/generator/pamtris/boundaries.c @@ -0,0 +1,334 @@ +/*============================================================================= + boundaries.c +=============================================================================== + Boundary buffer functions + + New triangles are drawn one scanline at a time, and for every such scanline + we have left and right boundary columns within the frame buffer such that + the fraction of the triangle's area within that scanline is enclosed + between those two points (inclusive). Those coordinates may correspond to + columns outside the frame buffer's actual limits, in which case proper + post-processing should be made wherever such coordinates are used to + actually plot anything into the frame buffer. +=============================================================================*/ + +#include <stdlib.h> + +#include <netpbm/mallocvar.h> +#include <netpbm/pm.h> + +#include "utils.h" +#include "fract.h" + + +#include "boundaries.h" + + + +static fract +make_pos_fract(int32_t const quotient, + int32_t const remainder) { + + fract retval; + + retval.q = quotient; + retval.r = remainder; + retval.negative_flag = 0; + + return retval; +} + + + +void +init_boundary_buffer(boundary_info * const bi, + int16_t const height) { + + MALLOCARRAY(bi->buffer, height * 2); + + if (!bi->buffer) + pm_error("Unable to get memory for %u-row high boundary buffer", + height); +} + + + +void +free_boundary_buffer(boundary_info * bi) { + free(bi->buffer); +} + + + +bool +gen_triangle_boundaries(Xy const xy, + boundary_info * const bi, + int16_t const width, + int16_t const height) { +/*---------------------------------------------------------------------------- + Generate an entry in the boundary buffer for the boundaries of every + VISIBLE row of a particular triangle. In case there is no such row, + start_row is accordingly set to -1. The argument is a 3-element array + of pairs of int16_t's representing the coordinates of the vertices of + a triangle. Those vertices MUST be already sorted in order from the + uppermost to the lowermost vertex (which is what draw_triangle, the + only function which uses this one, does with the help of sort3). + + The return value indicates whether the middle vertex is to the left of the + line connecting the top vertex to the bottom vertex or not. +-----------------------------------------------------------------------------*/ + int16_t leftmost_x; + int16_t rightmost_x; + int mid_is_to_the_left; + fract left_x; + fract right_x; + bool no_upper_part; + int32_t top2mid_delta; + int32_t top2bot_delta; + int32_t mid2bot_delta; + fract top2mid_step; + fract top2bot_step; + fract mid2bot_step; + fract* upper_left_step; + fract* lower_left_step; + fract* upper_right_step; + fract* lower_right_step; + int32_t upper_left_delta; + int32_t lower_left_delta; + int32_t upper_right_delta; + int32_t lower_right_delta; + fract* left_step[2]; + fract* right_step[2]; + int32_t left_delta[2]; + int32_t right_delta[2]; + int16_t* num_rows_ptr[2]; + int32_t y; + int32_t i; + uint8_t k; + + leftmost_x = xy._[0][0]; /* initial value */ + rightmost_x = xy._[0][0]; /* initial value */ + + bi->start_scanline = -1; + bi->num_upper_rows = 0; + bi->num_lower_rows = 0; + + if (xy._[2][1] < 0 || xy._[0][1] >= height) { + /* Triangle is either completely above the topmost scanline or + completely below the bottom scanline. + */ + + return false; /* Actual value doesn't matter. */ + } + + { + unsigned int i; + + for (i = 1; i < 3; i++) { + if (xy._[i][0] < leftmost_x) { + leftmost_x = xy._[i][0]; + } + + if (xy._[i][0] > rightmost_x) { + rightmost_x = xy._[i][0]; + } + } + } + if (rightmost_x < 0 || leftmost_x >= width) { + /* Triangle is either completely to the left of the leftmost + framebuffer column or completely to the right of the rightmost + framebuffer column. + */ + return false; /* Actual value doesn't matter. */ + } + + if (xy._[0][1] == xy._[1][1] && xy._[1][1] == xy._[2][1]) { + /* Triangle is degenarate: its visual representation consists only of + a horizontal straight line. + */ + + bi->start_scanline = xy._[0][1]; + + return false; /* Actual value doesn't matter. */ + } + + mid_is_to_the_left = 2; + + left_x = make_pos_fract(xy._[0][0], 0); + right_x = make_pos_fract(xy._[0][0], 0); + + if (xy._[0][1] == xy._[1][1]) { + /* Triangle has only a lower part. */ + + mid_is_to_the_left = 0; + + right_x.q = xy._[1][0]; + } else if (xy._[1][1] == xy._[2][1]) { + /* Triangle has only an upper part (plus the row of the middle + vertex). + */ + + mid_is_to_the_left = 1; + } + + no_upper_part = (xy._[1][1] == xy._[0][1]); + + top2mid_delta = xy._[1][1] - xy._[0][1] + !no_upper_part; + top2bot_delta = xy._[2][1] - xy._[0][1] + 1; + mid2bot_delta = xy._[2][1] - xy._[1][1] + no_upper_part; + + gen_steps(&xy._[0][0], &xy._[1][0], &top2mid_step, 1, top2mid_delta); + gen_steps(&xy._[0][0], &xy._[2][0], &top2bot_step, 1, top2bot_delta); + gen_steps(&xy._[1][0], &xy._[2][0], &mid2bot_step, 1, mid2bot_delta); + + if (mid_is_to_the_left == 2) { + if (top2bot_step.negative_flag) { + if (top2mid_step.negative_flag) { + if (top2mid_step.q == top2bot_step.q) { + mid_is_to_the_left = + top2mid_step.r * top2bot_delta > + top2bot_step.r * top2mid_delta; + } else { + mid_is_to_the_left = top2mid_step.q < top2bot_step.q; + } + } else { + mid_is_to_the_left = 0; + } + } else { + if (!top2mid_step.negative_flag) { + if (top2mid_step.q == top2bot_step.q) { + mid_is_to_the_left = + top2mid_step.r * top2bot_delta < + top2bot_step.r * top2mid_delta; + } else { + mid_is_to_the_left = top2mid_step.q < top2bot_step.q; + } + } else { + mid_is_to_the_left = 1; + } + } + } + if (mid_is_to_the_left) { + upper_left_step = &top2mid_step; + lower_left_step = &mid2bot_step; + upper_right_step = &top2bot_step; + lower_right_step = upper_right_step; + + upper_left_delta = top2mid_delta; + lower_left_delta = mid2bot_delta; + upper_right_delta = top2bot_delta; + lower_right_delta = upper_right_delta; + } else { + upper_right_step = &top2mid_step; + lower_right_step = &mid2bot_step; + upper_left_step = &top2bot_step; + lower_left_step = upper_left_step; + + upper_right_delta = top2mid_delta; + lower_right_delta = mid2bot_delta; + upper_left_delta = top2bot_delta; + lower_left_delta = upper_left_delta; + } + + left_step[0] = upper_left_step; + left_step[1] = lower_left_step; + right_step[0] = upper_right_step; + right_step[1] = lower_right_step; + left_delta[0] = upper_left_delta; + left_delta[1] = lower_left_delta; + right_delta[0] = upper_right_delta; + right_delta[1] = lower_right_delta; + num_rows_ptr[0] = &bi->num_upper_rows; + num_rows_ptr[1] = &bi->num_lower_rows; + + y = xy._[0][1]; + + i = 0; + k = 0; + + if (no_upper_part) { + k = 1; + + right_x.q = xy._[1][0]; + } + + step_up(&left_x, left_step[k], 1, left_delta[k]); + step_up(&right_x, right_step[k], 1, right_delta[k]); + + while (k < 2) { + int32_t end; + + end = xy._[k + 1][1] + k; /* initial value */ + + if (y < 0) { + int32_t delta; + + if (end > 0) { + delta = -y; + } else { + delta = xy._[k + 1][1] - y; + } + + y += delta; + + multi_step_up(&left_x, left_step[k], 1, delta, left_delta[k]); + multi_step_up(&right_x, right_step[k], 1, delta, right_delta[k]); + + if (y < 0) { + k++; + continue; + } + } else if(y >= height) { + return mid_is_to_the_left; + } + + if (end > height) { + end = height; + } + + while (y < end) { + if (left_x.q >= width || right_x.q < 0) { + if (bi->start_scanline > -1) { + return mid_is_to_the_left; + } + } else { + if (bi->start_scanline == -1) { + bi->start_scanline = y; + } + + bi->buffer[i++] = left_x.q; + bi->buffer[i++] = right_x.q; + + (*(num_rows_ptr[k]))++; + } + + step_up(&left_x, left_step[k], 1, left_delta[k]); + step_up(&right_x, right_step[k], 1, right_delta[k]); + + y++; + } + k++; + } + return mid_is_to_the_left; +} + + + +void +get_triangle_boundaries(uint16_t const row_index, + int32_t * const left, + int32_t * const right, + const boundary_info * const bi) { +/*---------------------------------------------------------------------------- + Return the left and right boundaries for a given VISIBLE triangle row (the + row index is relative to the first visible row). These values may be out of + the horizontal limits of the frame buffer, which is necessary in order to + compute correct attribute interpolations. +-----------------------------------------------------------------------------*/ + uint32_t const i = row_index << 1; + + *left = bi->buffer[i]; + *right = bi->buffer[i + 1]; +} + + diff --git a/generator/pamtris/boundaries.h b/generator/pamtris/boundaries.h new file mode 100644 index 00000000..d41719cb --- /dev/null +++ b/generator/pamtris/boundaries.h @@ -0,0 +1,71 @@ +#ifndef BOUNDARIES_H_INCLUDED +#define BOUNDARIES_H_INCLUDED + +#include <stdint.h> + +#include "triangle.h" + +typedef struct boundary_info { +/*---------------------------------------------------------------------------- + Information about visible triangle rows' boundaries. Also see the + "boundary buffer functions" below. + + A "visible" triangle row is one which: + + 1. Corresponds to a frame buffer row whose index (from top to bottom) is + equal to or greater than 0 and smaller than the image height; and + + 2. Has at least some of its pixels between the frame buffer columns whose + index (from left to right) is equal to or greater than 0 and smaller + than the image width. +-----------------------------------------------------------------------------*/ + int16_t start_scanline; + /* Index of the frame buffer scanline which contains the first visible + row of the current triangle, if there is any such row. If not, it + contains the value -1. + */ + + int16_t num_upper_rows; + /* The number of visible rows in the upper part of the triangle. The + upper part of a triangle is composed of all the rows starting from + the top vertex down to the middle vertex, but not including this + last one. + */ + + int16_t num_lower_rows; + /* The number of visible rows in the lower part of the triangle. The + lower part of a triangle is composed of all the rows from the + middle vertex to the bottom vertex -- all inclusive. + */ + + int16_t * buffer; + /* This is the "boundary buffer": a pointer to an array of int16_t's + where each consecutive pair of values indicates, in this order, the + columns of the left and right boundary pixels for a particular + visible triangle row. Those boundaries are inclusive on both sides + and may be outside the limits of the frame buffer. This field is + initialized and freed by the functions "init_boundary_buffer" and + "free_boundary_buffer", respectively. + */ +} boundary_info; + +void +init_boundary_buffer(boundary_info * const bdi, + int16_t const height); + +void +free_boundary_buffer(boundary_info *); + +bool +gen_triangle_boundaries(Xy const xy, + boundary_info * const bdi, + int16_t const width, + int16_t const height); + +void +get_triangle_boundaries(uint16_t const row_index, + int32_t * const left, + int32_t * const right, + const boundary_info * const bdi); + +#endif diff --git a/generator/pamtris/fract.h b/generator/pamtris/fract.h new file mode 100644 index 00000000..ff3c4402 --- /dev/null +++ b/generator/pamtris/fract.h @@ -0,0 +1,54 @@ +#ifndef FRACT_H_INCLUDED +#define FRACT_H_INCLUDED + +#include <stdbool.h> +#include <stdint.h> + + +typedef struct { +/*---------------------------------------------------------------------------- + This struct and the functions that manipulate variables of this type act + as a substitute for floating point computations. Here, whenever we need a + value with a fractional component, we represent it using two parts: 1. An + integer part, called the "quotient", and 2. A fractional part, which is + itself composed of a "remainder" (or "numerator") and a "divisor" (or + "denominator"). The fract struct provides storage for the quotient and the + remainder, but the divisor must be given separately (because it often + happens in this program that whenever we are dealing with one variable of + type fract, we are dealing with more of them at the same time, and they + all have the same divisor). + + To be more precise, the way we actually use variables of this type works + like this: We read integer values through standard input; When drawing + triangles, we need need to calculate differences between some pairs of + these input values and divide such differences by some other integer, + which is the above mentioned divisor. That result is then used to compute + successive interpolations between the two values for which we had + originally calculated the difference, and is therefore called the + "interpolation step". The values between which we wish to take successive + interpolations are called the "initial value" and the "final value". The + interpolation procedure works like this: First, we transform the initial + value into a fract variable by equating the quotient of that variable to + the initial value and assigning 0 to its remainder. Then, we successivelly + apply the interpolation step to that variable through successive calls to + step_up and/or multi_step_up until the quotient of the variable equals the + final value. Each application of step_up or multi_step_up yields a + particular linear interpolation between the initial and final values. + + If and only if a particular fract variable represents an interpolation + step, the "negative_flag" field indicates whether the step is negative + (i. e. negative_flag == true) or not (negative_flag == false). This is + necessary in order to make sure that variables are "stepped up" in the + appropriate direction, so to speak, as the field which stores the + remainder in any fract variable, "r", is always equal to or above 0, and + the quotient of a step may be 0, so the actual sign of the step value is + not always discoverable through a simple examination of the sign of the + quotient. On the other hand, if the variable does not represent an + interpolation step, the negative_flag is meaningless. +-----------------------------------------------------------------------------*/ + int32_t q; /* Quotient */ + int32_t r: 31; /* Remainder */ + bool negative_flag: 1; +} fract; + +#endif diff --git a/generator/pamtris/framebuffer.c b/generator/pamtris/framebuffer.c new file mode 100644 index 00000000..03cd720c --- /dev/null +++ b/generator/pamtris/framebuffer.c @@ -0,0 +1,334 @@ +/*============================================================================= + framebuffer.c +=============================================================================== + Frame buffer functions + + Every drawing operation is applied on an internal "frame buffer", which is + simply an "image buffer" which represents the picture currently being drawn, + along with a "Z-Buffer" which contains the depth values for every pixel in + the image buffer. Once all desired drawing operations for a particular + picture are effected, a function is provided to print the current contents + of the image buffer as a PAM image on standard output. Another function is + provided to clear the contents of the frame buffer (i. e. set all image + samples and Z-Buffer entries to 0), with the option of only clearing either + the image buffer or the Z-Buffer individually. + + The Z-Buffer works as follows: Every pixel in the image buffer has a + corresponding entry in the Z-Buffer. Initially, every entry in the Z-Buffer + is set to 0. Every time we desire to plot a pixel at some particular + position in the frame buffer, the current value of the corresponding entry + in the Z-Buffer is compared against the the Z component of the incoming + pixel. If MAX_Z minus the value of the Z component of the incoming pixel is + equal to or greater than the current value of the corresponding entry in the + Z-Buffer, the frame buffer is changed as follows: + + 1. All the samples but the last of the corresponding position in the + image buffer are set to equal those of the incoming pixel. + + 2. The last sample, that is, the A-component of the corresponding position + in the image buffer is set to equal the maxval. + + 3. The corresponding entry in the Z-Buffer is set to equal MAX_Z minus the + value of the Z component of the incoming pixel. + + Otherwise, no changes are made on the frame buffer. +=============================================================================*/ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "utils.h" +#include "fract.h" +#include "limits_pamtris.h" + +#include "framebuffer.h" + + + +int +set_tupletype(const char * const str, + char * const tupletype) { +/*---------------------------------------------------------------------------- + Set the tuple type for the output PAM images given a string ("str") of 255 + characters or less. If the string has more than 255 characters, the function + returns 0. Otherwise, it returns 1. If NULL is given for the "str" argument, + the tuple type is set to a null string. This function is called during + program initialization and whenever a "r" command is executed. The second + argument must point to the tuple_type member of the "outpam" field in the + framebuffer_info struct. +-----------------------------------------------------------------------------*/ + if (str == NULL) { + memset(tupletype, 0, 256); + + return 1; + } else { + size_t len; + + len = strlen(str); /* initial value */ + + if (len > 255) { + return 0; + } + + if (len > 0) { + memcpy(tupletype, str, len); + } + + tupletype[len--] = '\0'; + + while(len > 0 && isspace(tupletype[len])) { + tupletype[len--] = '\0'; + } + + return 1; + } +} + + + +int +init_framebuffer(framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + + uint32_t const elements = fbi->width * fbi->height; + + fbi->img.bytes = elements * (num_planes * sizeof(uint16_t)); + fbi->z.bytes = elements * sizeof(uint32_t); + + fbi->img.buffer = + calloc(fbi->img.bytes / sizeof(uint16_t), sizeof(uint16_t)); + fbi->z.buffer = + calloc(fbi->z.bytes / sizeof(uint32_t), sizeof(uint32_t)); + + if(fbi->img.buffer == NULL || fbi->z.buffer == NULL) { + free(fbi->img.buffer); + free(fbi->z.buffer); + + return 0; + } + + fbi->outpam.size = sizeof(struct pam); + fbi->outpam.len = sizeof(struct pam); + fbi->outpam.file = stdout; + fbi->outpam.format = PAM_FORMAT; + fbi->outpam.plainformat = 0; + fbi->outpam.height = fbi->height; + fbi->outpam.width = fbi->width; + fbi->outpam.depth = num_planes; + fbi->outpam.maxval = fbi->maxval; + fbi->outpam.allocation_depth = 0; + fbi->outpam.comment_p = NULL; + + fbi->pamrow = NULL; + fbi->pamrow = pnm_allocpamrow(&fbi->outpam); + + if (fbi->pamrow == NULL) { + free(fbi->img.buffer); + free(fbi->z.buffer); + + return 0; + } + + return 1; +} + + + +void +free_framebuffer(framebuffer_info * const fbi) { + + free(fbi->img.buffer); + free(fbi->z.buffer); + + pnm_freepamrow(fbi->pamrow); +} + + + +int +realloc_image_buffer(int32_t const new_maxval, + int32_t const new_num_attribs, + framebuffer_info * const fbi) { +/*---------------------------------------------------------------------------- + Reallocate the image buffer with a new maxval and depth, given the struct + with information about the framebuffer. The fields variables "maxval" and + "num_attribs". + + From the point this function is called onwards, new PAM images printed on + standard output will have the new maxval for the maxval and num_attribs + 1 + for the depth. + + This function does *not* check whether the new maxval and num_attribs are + within the proper allowed limits. That is done inside the input processing + function "process_next_command", which is the only function that calls this + one. + + If the function suceeds, the image buffer is left in cleared state. The + Z-Buffer, however, is not touched at all. + + If the new depth is equal to the previous one, no actual reallocation is + performed: only the global variable "maxval" is changed. But the image + buffer is nonetheless left in cleared state regardless. +-----------------------------------------------------------------------------*/ + uint8_t num_planes; + + pnm_freepamrow(fbi->pamrow); + fbi->pamrow = NULL; + + num_planes = fbi->num_attribs + 1; /* initial value */ + + if (new_num_attribs != fbi->num_attribs) { + fbi->num_attribs = new_num_attribs; + num_planes = fbi->num_attribs + 1; + + fbi->img.bytes = + fbi->width * fbi->height * (num_planes * sizeof(uint16_t)); + + { + uint16_t * const new_ptr = + realloc(fbi->img.buffer, fbi->img.bytes); + + if (new_ptr == NULL) { + free(fbi->img.buffer); + fbi->img.buffer = NULL; + + return 0; + } + fbi->img.buffer = new_ptr; + } + } + + fbi->maxval = new_maxval; + + fbi->outpam.size = sizeof(struct pam); + fbi->outpam.len = sizeof(struct pam); + fbi->outpam.file = stdout; + fbi->outpam.format = PAM_FORMAT; + fbi->outpam.plainformat = 0; + fbi->outpam.height = fbi->height; + fbi->outpam.width = fbi->width; + fbi->outpam.depth = num_planes; + fbi->outpam.maxval = fbi->maxval; + fbi->outpam.allocation_depth = 0; + fbi->outpam.comment_p = NULL; + + fbi->pamrow = pnm_allocpamrow(&fbi->outpam); + + if (fbi->pamrow == NULL) { + free(fbi->img.buffer); + fbi->img.buffer = NULL; + + return 0; + } + + memset(fbi->img.buffer, 0, fbi->img.bytes); + + return 1; +} + + + +void +print_framebuffer(framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + uint32_t const end = fbi->width * fbi->height; + + uint32_t i; + + pnm_writepaminit(&fbi->outpam); + + for (i = 0; i != end; ) { + int j; + for (j = 0; j < fbi->width; j++) { + uint32_t const k = (i + j) * num_planes; + + unsigned int l; + + for (l = 0; l < num_planes; l++) { + fbi->pamrow[j][l] = fbi->img.buffer[k + l]; + } + } + + pnm_writepamrow(&fbi->outpam, fbi->pamrow); + + i += fbi->width; + } +} + + + +void +clear_framebuffer(bool const clear_image_buffer, + bool const clear_z_buffer, + framebuffer_info * const fbi) { + + if (clear_image_buffer) { + memset(fbi->img.buffer, 0, fbi->img.bytes); + } + + if (clear_z_buffer) { + memset(fbi->z.buffer, 0, fbi->z.bytes); + } +} + + + +void +draw_span(uint32_t const base, + uint16_t const length, + fract * const attribs_start, + const fract * const attribs_steps, + int32_t const div, + framebuffer_info * const fbi) { +/*---------------------------------------------------------------------------- + Draw a horizontal span of "length" pixels into the frame buffer, performing + the appropriate depth tests. "base" must equal the row of the frame buffer + where one desires to draw the span *times* the image width, plus the column + of the first pixel in the span. + + This function does not perform any kind of bounds checking. +-----------------------------------------------------------------------------*/ + uint8_t const num_planes = fbi->num_attribs + 1; + + unsigned int i; + + /* Process each pixel in the span: */ + + for (i = 0; i < length; i++) { + int32_t const z = MAX_Z - attribs_start[fbi->num_attribs].q; + uint32_t const z_mask = -(~(z - fbi->z.buffer[base + i]) >> 31); + uint32_t const n_z_mask = ~z_mask; + + uint32_t const j = base + i; + uint32_t const k = j * num_planes; + + unsigned int l; + + /* The following statements will only have any effect if the depth + test, performed above, has suceeded. I. e. if the depth test fails, + no changes will be made on the framebuffer; otherwise, the + framebuffer will be updated with the new values. + */ + fbi->z.buffer[j] = (fbi->z.buffer[j] & n_z_mask) | (z & z_mask); + + for (l = 0; l < fbi->num_attribs; l++) { + fbi->img.buffer[k + l] = + (fbi->img.buffer[k + l] & n_z_mask) | + (attribs_start[l].q & z_mask); + } + + fbi->img.buffer[k + fbi->num_attribs] = + (fbi->img.buffer[k + fbi->num_attribs] & n_z_mask) | + (fbi->maxval & z_mask); + + /* Compute the attribute values for the next pixel: */ + + step_up(attribs_start, attribs_steps, num_planes, div); + } +} + + + diff --git a/generator/pamtris/framebuffer.h b/generator/pamtris/framebuffer.h new file mode 100644 index 00000000..73ec96be --- /dev/null +++ b/generator/pamtris/framebuffer.h @@ -0,0 +1,75 @@ +#ifndef FRAMEBUFFER_H_INCLUDED +#define FRAMEBUFFER_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> +#include "fract.h" +#include "netpbm/pam.h" + +typedef struct framebuffer_info { +/*---------------------------------------------------------------------------- + Information about the frame buffer and PAM output +-----------------------------------------------------------------------------*/ + /* These fields are initialized once by reading the command line + arguments. "maxval" and "num_attribs" may be modified later + through "realloc_image_buffer". + */ + int32_t width; + int32_t height; + int32_t maxval; + int32_t num_attribs; + + /* The fields below must be initialized by "init_framebuffer" and + freed by "free_framebuffer", except for the tuple_type field in + "outpam" which is initialized once by reading the command line + arguments and may be modified later through "set_tupletype". + */ + struct { + uint16_t * buffer; + uint32_t bytes; + } img; /* Image buffer */ + + struct { + uint32_t * buffer; + uint32_t bytes; + } z; /* Z-buffer */ + + struct pam outpam; + + tuple * pamrow; +} framebuffer_info; + + + +int +set_tupletype(const char * const str, + char * const tupletype); + +int +init_framebuffer(framebuffer_info * const fbi); + +void +free_framebuffer(framebuffer_info * const fbi); + +void +print_framebuffer(framebuffer_info * const fbi); + +void +clear_framebuffer(bool const clear_image_buffer, + bool const clear_z_buffer, + framebuffer_info * const fbi); + +int +realloc_image_buffer(int32_t const new_maxval, + int32_t const new_num_attribs, + framebuffer_info * const fbi); + +void +draw_span(uint32_t const base, + uint16_t const length, + fract * const attribs_start, + const fract * const attribs_steps, + int32_t const divisor, + framebuffer_info * const fbi); + +#endif diff --git a/generator/pamtris/input.c b/generator/pamtris/input.c new file mode 100644 index 00000000..166d6db5 --- /dev/null +++ b/generator/pamtris/input.c @@ -0,0 +1,641 @@ +/*============================================================================= + input.c +=============================================================================== + Input handling functions +=============================================================================*/ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include "netpbm/mallocvar.h" + +#include "limits_pamtris.h" +#include "framebuffer.h" +#include "triangle.h" + +#include "input.h" + +#define MAX_COORD 32767 +#define MIN_COORD -MAX_COORD + +#define DRAW_MODE_TRIANGLES 1 +#define DRAW_MODE_STRIP 2 +#define DRAW_MODE_FAN 3 + +#define CMD_SET_MODE "mode" +#define CMD_SET_ATTRIBS "attribs" +#define CMD_VERTEX "vertex" +#define CMD_PRINT "print" +#define CMD_CLEAR "clear" +#define CMD_RESET "reset" +#define CMD_QUIT "quit" + +#define ARG_TRIANGLES "triangles" +#define ARG_STRIP "strip" +#define ARG_FAN "fan" +#define ARG_IMAGE "image" +#define ARG_DEPTH "depth" + +#define WARNING_EXCESS_ARGS "warning: ignoring excess arguments: line %lu." +#define SYNTAX_ERROR "syntax error: line %lu." + +typedef struct { + Xy v_xy; + /* X- and Y-coordinates of the vertices for the current triangle. + int32_t v_attribs[3][MAX_NUM_ATTRIBS + 1]; // Vertex attributes for + the current triangle. Includes the Z-coordinates. + */ + Attribs v_attribs; + /* Vertex attributes for the current triangle. Includes the + Z-coordinates. + */ + int32_t curr_attribs[MAX_NUM_ATTRIBS]; + /* Attributes that will be assigned to the next vertex. Does not + include the Z-coordinate. + */ + uint8_t next; + /* Index of the next vertex to be read. */ + bool draw; + /* If true, draws a new triangle upon reading a new vertex. */ + + uint8_t mode; + /* Drawing mode. */ + + bool initialized; +} state_info; + + + +static void +clear_attribs(state_info * const si, + int32_t const maxval, + int16_t const num_attribs) { + + unsigned int i; + + for (i = 0; i < num_attribs; i++) { + si->curr_attribs[i] = maxval; + } +} + + + +void +init_input_processor(input_info * const ii) { + + MALLOCARRAY_NOFAIL(ii->buffer, 128); + + ii->length = 128; + ii->number = 1; +} + + + +void +free_input_processor(input_info * const ii) { + free(ii->buffer); +} + + + +typedef struct { +/*---------------------------------------------------------------------------- + Indicates a whitespace-delimited input symbol. "begin" points to its first + character, and "end" points to one position past its last character. +-----------------------------------------------------------------------------*/ + char * begin; + char * end; +} token; + + + +static token +next_token(char * const startPos) { + + token retval; + char * p; + + for (p = startPos; *p && isspace(*p); ++p); + + retval.begin = p; + + for (; *p && !isspace(*p); ++p); + + retval.end = p; + + return retval; +} + + + +static bool +string_is_valid(const char * const target, + const char * const srcBegin, + const char * const srcEnd) { + + unsigned int charsMatched; + const char * p; + + for (p = srcBegin, charsMatched = 0; + p != srcEnd && target[charsMatched] != '\0'; ++p) { + + if (*p == target[charsMatched]) + ++charsMatched; + else + break; + } + + return (*p == '\0' || isspace(*p)); +} + + + +static void +init_state(state_info * const si) { + + si->next = 0; + si->draw = false; + si->mode = DRAW_MODE_TRIANGLES; +} + + + +static void +make_lowercase(token const t) { + + char * p; + + for (p = t.begin; p != t.end; ++p) + *p = tolower(*p); +} + + + +static void +remove_comments(char * const str) { + + char * p; + + for (p = &str[0]; *p; ++p) { + if (*p == '#') { + *p = '\0'; + + break; + } + } +} + + + +int +process_next_command(input_info * const line, + struct boundary_info * const bi, + framebuffer_info * const fbi) { +/*---------------------------------------------------------------------------- + Doesn't necessarily process a command, just the next line of input, which + may be empty. Always returns 1, except when it cannot read any more lines of + input, an image buffer reallocation fails, or a "q" command is found in the + input -- in such cases it returns 0. +-----------------------------------------------------------------------------*/ + static state_info state; + + token nt; + + long int i_args[MAX_NUM_ATTRIBS]; + /* For storing potential integer arguments. */ + char * strtol_end; + /* To compare against nt.end when checking for errors with strtol */ + bool unrecognized_cmd; + /* To print out an error message in case an unrecognized command was + given. + */ + bool unrecognized_arg; + /* To print out an error message in case an unrecognized argument was + given. + */ + bool must_break_out; + /* To break out of the below switch statement when an invalid argument + is found. + */ + bool ok; + /* Indicates whether the input line was OK so that we can print out a + warning in case of excess arguments. + */ + + /* initial values */ + strtol_end = NULL; + unrecognized_cmd = false; + unrecognized_arg = false; + must_break_out = false; + ok = false; + + if (!state.initialized) { + init_state(&state); + clear_attribs(&state, fbi->maxval, fbi->num_attribs); + + state.initialized = true; + } + + if (getline(&line->buffer, &line->length, stdin) == -1) { + return 0; + } + + remove_comments(line->buffer); + + nt = next_token(line->buffer); + + make_lowercase(nt); + + switch (*nt.begin) { + case 'm': + if (!string_is_valid(CMD_SET_MODE, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + + nt = next_token(nt.end); + + if (*nt.begin == '\0') { + pm_errormsg(SYNTAX_ERROR, line->number); + + break; + } + + make_lowercase(nt); + + switch(*nt.begin) { + case 't': + if (!string_is_valid(ARG_TRIANGLES, nt.begin, nt.end)) { + unrecognized_arg = true; + + break; + } + + state.mode = DRAW_MODE_TRIANGLES; + state.draw = false; + state.next = 0; + + ok = true; + + break; + case 's': + if (!string_is_valid(ARG_STRIP, nt.begin, nt.end)) { + unrecognized_arg = true; + + break; + } + + state.mode = DRAW_MODE_STRIP; + state.draw = false; + state.next = 0; + + ok = true; + + break; + case 'f': + if (!string_is_valid(ARG_FAN, nt.begin, nt.end)) { + unrecognized_arg = true; + + break; + } + + state.mode = DRAW_MODE_FAN; + state.draw = false; + state.next = 0; + + ok = true; + + break; + default: + unrecognized_arg = true; + } + + if (unrecognized_arg) { + pm_errormsg("error: unrecognized drawing mode in line %lu.", + line->number); + } + + break; + case 'a': { + uint8_t i; + if (!string_is_valid(CMD_SET_ATTRIBS, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + + for (i = 0; i < fbi->num_attribs; i++) { + nt = next_token(nt.end); + + i_args[i] = strtol(nt.begin, &strtol_end, 10); + + if (*nt.begin == '\0' || strtol_end != nt.end) { + pm_errormsg(SYNTAX_ERROR, line->number); + + must_break_out = true; + + break; + } + + if (i_args[i] < 0 || i_args[i] > fbi->maxval) { + pm_errormsg("error: argument(s) out of bounds: line %lu.", + line->number); + + must_break_out = true; + + break; + } + } + + if (must_break_out) + { + break; + } + + for (i = 0; i < fbi->num_attribs; i++) { + state.curr_attribs[i] = i_args[i]; + } + + ok = true; + + } break; + case 'v': { + uint8_t i; + + if (!string_is_valid(CMD_VERTEX, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + + for (i = 0; i < 3; i++) { + nt = next_token(nt.end); + + i_args[i] = strtol(nt.begin, &strtol_end, 10); + + if (*nt.begin == '\0' || strtol_end != nt.end) { + pm_errormsg(SYNTAX_ERROR, line->number); + + must_break_out = true; + + break; + } + + if (i < 2) { + if (i_args[i] < MIN_COORD || i_args[i] > MAX_COORD) { + pm_errormsg( + "error: coordinates out of bounds: line %lu.", + line->number); + + must_break_out = true; + + break; + } + } else { + if (i_args[i] < 0 || i_args[i] > MAX_Z) { + pm_errormsg( + "error: Z component out of bounds: line %lu.", + line->number); + + must_break_out = true; + + break; + } + } + } + + if (must_break_out) + { + break; + } + + for (i = 0; i < fbi->num_attribs; i++) { + state.v_attribs._[state.next][i] = state.curr_attribs[i]; + } + + state.v_attribs._[state.next][fbi->num_attribs] = i_args[2]; + + state.v_xy._[state.next][0] = i_args[0]; + state.v_xy._[state.next][1] = i_args[1]; + + state.next++; + + if (!state.draw) { + if (state.next == 3) { + state.draw = true; + } + } + + if (state.draw) { + draw_triangle(state.v_xy, state.v_attribs, bi, fbi); + } + + if (state.next == 3) { + switch(state.mode) { + case DRAW_MODE_FAN: + state.next = 1; + break; + case DRAW_MODE_TRIANGLES: + state.draw = false; + case DRAW_MODE_STRIP: + default: + state.next = 0; + } + } + + ok = true; + + } break; + case 'p': + if (!string_is_valid(CMD_PRINT, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + case '!': + if (*nt.begin == '!') { + if (nt.end - nt.begin > 1) { + unrecognized_cmd = true; + + break; + } + } + + print_framebuffer(fbi); + + ok = true; + + break; + case 'c': + if (!string_is_valid(CMD_CLEAR, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + case '*': + if (*nt.begin == '*') { + if(nt.end - nt.begin > 1) { + unrecognized_cmd = true; + + break; + } + } + + nt = next_token(nt.end); + + if (*nt.begin != '\0') { + make_lowercase(nt); + + switch(*nt.begin) { + case 'i': + if (!string_is_valid("image", nt.begin, nt.end)) { + unrecognized_arg = true; + + break; + } + + clear_framebuffer(true, false, fbi); + + break; + case 'd': + if (!string_is_valid("depth", nt.begin, nt.end)) { + unrecognized_arg = true; + + break; + } + case 'z': + if (*nt.begin == 'z') { + if (nt.end - nt.begin > 1) { + unrecognized_arg = true; + + break; + } + } + + clear_framebuffer(false, true, fbi); + + break; + default: + unrecognized_arg = true; + } + + if (unrecognized_arg) { + pm_errormsg("error: unrecognized argument: line %lu.", + line->number); + + break; + } + } else { + clear_framebuffer(true, true, fbi); + } + + ok = true; + + break; + case 'r': { + uint8_t i; + + if (!string_is_valid(CMD_RESET, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + + for (i = 0; i < 2; i++) { + nt = next_token(nt.end); + + i_args[i] = strtol(nt.begin, &strtol_end, 10); + + if (*nt.begin == '\0' || nt.end != strtol_end) { + pm_errormsg(SYNTAX_ERROR, line->number); + + must_break_out = true; + + break; + } + } + + if (must_break_out) { + break; + } + + if (i_args[0] < 1 || i_args[0] > PAM_OVERALL_MAXVAL) { + pm_errormsg("error: invalid new maxval: line %lu.", + line->number); + + break; + } + + if (i_args[1] < 1 || i_args[1] > MAX_NUM_ATTRIBS) { + pm_errormsg("error: invalid new number of generic vertex " + "attributes: line %lu.", line->number); + + break; + } + + nt = next_token(nt.end); + + if (*nt.begin != '\0') { + if (!set_tupletype(nt.begin, fbi->outpam.tuple_type)) { + pm_message("warning: could not set new tuple type; " + "using a null string: line %lu.", + line->number); + + set_tupletype(NULL, fbi->outpam.tuple_type); + } + } else { + set_tupletype(NULL, fbi->outpam.tuple_type); + } + + if (!realloc_image_buffer(i_args[0], i_args[1], fbi)) { + pm_errormsg + ( + "fatal error upon reading line %lu: " + "could not reallocate image buffer -- " + "terminating pamtris.", + line->number + ); + + return 0; + } + + state.next = 0; + state.draw = false; + + clear_attribs(&state, fbi->maxval, fbi->num_attribs); + + } break; + case 'q': + if (!string_is_valid(CMD_QUIT, nt.begin, nt.end)) { + unrecognized_cmd = true; + + break; + } + + return 0; + case '\0': + break; + default: + unrecognized_cmd = true; + } + + { + char const next = *next_token(nt.end).begin; + + if (unrecognized_cmd) { + pm_errormsg("error: unrecognized command: line %lu.", + line->number); + } + else if (ok && next != '\0') { + pm_message(WARNING_EXCESS_ARGS, line->number); + } + } + line->number++; + + return 1; +} + + diff --git a/generator/pamtris/input.h b/generator/pamtris/input.h new file mode 100644 index 00000000..66969bb2 --- /dev/null +++ b/generator/pamtris/input.h @@ -0,0 +1,31 @@ +#ifndef INPUT_H_INCLUDED +#define INPUT_H_INCLUDED + +#include <stdint.h> + +struct boundary_info; +struct framebuffer_info; + +typedef struct input_info { +/*---------------------------------------------------------------------------- + Information necessary for the "process_next_command" function. It must be + initialized through "init_input_processor" and freed by + "free_input_processor". +-----------------------------------------------------------------------------*/ + char * buffer; + size_t length; + uint64_t number; +} input_info; + +void +init_input_processor(input_info * const ii); + +void +free_input_processor(input_info * const ii); + +int +process_next_command(input_info * const ii, + struct boundary_info * const bdi, + struct framebuffer_info * const fbi); + +#endif diff --git a/generator/pamtris/limits_pamtris.h b/generator/pamtris/limits_pamtris.h new file mode 100644 index 00000000..dcf1f1e6 --- /dev/null +++ b/generator/pamtris/limits_pamtris.h @@ -0,0 +1,7 @@ +#ifndef LIMITS_H_INCLUDED +#define LIMITS_H_INCLUDED + +#define MAX_NUM_ATTRIBS 20 +#define MAX_Z ((1 << 30) - 1) + +#endif diff --git a/generator/pamtris/pamtris.c b/generator/pamtris/pamtris.c new file mode 100644 index 00000000..74663531 --- /dev/null +++ b/generator/pamtris/pamtris.c @@ -0,0 +1,140 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "netpbm/mallocvar.h" +#include "netpbm/shhopt.h" +#include "netpbm/pam.h" + +#include "limits_pamtris.h" +#include "framebuffer.h" +#include "boundaries.h" +#include "input.h" + +#define MAX_METRICS 8192 + + + +static int +parse_command_line(int * const argc_ptr, + const char ** const argv, + int32_t * const width, + int32_t * const height, + int32_t * const maxval, + int32_t * const num_attribs, + char * const tupletype) { + + optEntry * option_def; + optStruct3 opt; + /* Instructions to pm_optParseOptions3 on how to parse our options */ + unsigned int option_def_index; + + char * tupletype_ptr; + + unsigned int width_spec, height_spec, maxval_spec, attribs_spec; + unsigned int tupletype_spec; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "width", OPT_INT, width, &width_spec, 0); + OPTENT3(0, "height", OPT_INT, height, &height_spec, 0); + OPTENT3(0, "maxval", OPT_INT, maxval, &maxval_spec, 0); + OPTENT3(0, "num_attribs", OPT_INT, num_attribs, &attribs_spec, 0); + OPTENT3(0, "tupletype", OPT_STRING, &tupletype_ptr, &tupletype_spec, 0); + + opt.opt_table = option_def; + opt.short_allowed = false; + opt.allowNegNum = false; + + pm_optParseOptions3(argc_ptr, (char **)argv, opt, sizeof(opt), 0); + + if (!width_spec || !height_spec || !attribs_spec) { + pm_errormsg( + "you must at least specify -width, -height and -num_attribs."); + + return 0; + } + + if (*width < 1 || *width > MAX_METRICS) { + pm_errormsg("invalid width."); + + return 0; + } + + if (*height < 1 || *height > MAX_METRICS) { + pm_errormsg("invalid height."); + + return 0; + } + + if (maxval_spec) { + if (*maxval < 1 || *maxval > PAM_OVERALL_MAXVAL) { + + + pm_errormsg("invalid maxval."); + + return 0; + } + } else { + *maxval = 255; + } + + if (*num_attribs < 1 || *num_attribs > MAX_NUM_ATTRIBS) { + pm_errormsg("invalid number of generic attributes per vertex."); + + return 0; + } + + if (tupletype_spec) { + if (!set_tupletype(tupletype_ptr, tupletype)) { + pm_errormsg("warning: invalid tuple type; using the null string."); + + set_tupletype(NULL, tupletype); + } + } + + free(option_def); + + return 1; +} + + + +int +main(int argc, const char ** argv) { + + framebuffer_info fbi; + boundary_info bi; + input_info ii; + + pm_proginit(&argc, (const char**)argv); + + set_tupletype(NULL, fbi.outpam.tuple_type); + + if (!parse_command_line(&argc, argv, + &fbi.width, &fbi.height, &fbi.maxval, + &fbi.num_attribs, fbi.outpam.tuple_type)) { + return 1; + } + + if (!init_framebuffer(&fbi)) { + pm_errormsg("out of memory."); + + return 3; + } + + init_boundary_buffer(&bi, fbi.height); + + init_input_processor(&ii); + + while (process_next_command(&ii, &bi, &fbi)); + + free_input_processor(&ii); + free_boundary_buffer(&bi); + free_framebuffer(&fbi); + + return 0; +} + + diff --git a/generator/pamtris/triangle.c b/generator/pamtris/triangle.c new file mode 100644 index 00000000..09d821e0 --- /dev/null +++ b/generator/pamtris/triangle.c @@ -0,0 +1,427 @@ +/*============================================================================= + triangle.c +=============================================================================== + Triangle functions +=============================================================================*/ +#include <stdlib.h> +#include <string.h> + +#include "netpbm/mallocvar.h" + +#include "utils.h" +#include "fract.h" +#include "boundaries.h" +#include "framebuffer.h" + +#include "triangle.h" + +static void +draw_partial_triangle( + const fract * const left_attribs_input, + const fract * const left_attribs_steps, + const fract * const rght_attribs_input, + const fract * const rght_attribs_steps, + int32_t const left_div, + int32_t const rght_div, + bool const upper_part, + const boundary_info * const bi, + framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + + fract * left_attribs; + fract * rght_attribs; + + int32_t first_row_index; + int32_t last_row_index; + + MALLOCARRAY_NOFAIL(left_attribs, num_planes); + MALLOCARRAY_NOFAIL(rght_attribs, num_planes); + + memcpy(left_attribs, left_attribs_input, num_planes * sizeof(fract)); + memcpy(rght_attribs, rght_attribs_input, num_planes * sizeof(fract)); + + if (upper_part) { + first_row_index = 0; + last_row_index = bi->num_upper_rows - 1; + } else { + first_row_index = bi->num_upper_rows; + last_row_index = bi->num_upper_rows + bi->num_lower_rows - 1; + } + + { + int32_t const row_delta = last_row_index - first_row_index; + + int32_t row; + + int32_t left_boundary; + int32_t rght_boundary; + + for (row = first_row_index; row <= last_row_index; ) { + get_triangle_boundaries(row, &left_boundary, &rght_boundary, bi); + { + int32_t const column_delta = rght_boundary - left_boundary; + int32_t start_column; + int32_t span_length; + + fract * attribs_start; + int32_t * attribs_begin; + int32_t * attribs_end; + fract * attribs_steps; + + MALLOCARRAY_NOFAIL(attribs_start, num_planes); + MALLOCARRAY_NOFAIL(attribs_begin, num_planes); + MALLOCARRAY_NOFAIL(attribs_end, num_planes); + MALLOCARRAY_NOFAIL(attribs_steps, num_planes); + + start_column = left_boundary; /* initial value */ + span_length = column_delta; /* initial value */ + + fract_to_int32_array(left_attribs, attribs_begin, num_planes); + fract_to_int32_array(rght_attribs, attribs_end, num_planes); + + int32_to_fract_array(attribs_begin, attribs_start, num_planes); + + gen_steps(attribs_begin, attribs_end, attribs_steps, + num_planes, column_delta); + + if (left_boundary < 0) { + start_column = 0; + + span_length += left_boundary; + + multi_step_up(attribs_start, attribs_steps, num_planes, + -left_boundary, column_delta); + } + + if (rght_boundary >= fbi->width) { + span_length -= rght_boundary - fbi->width; + } else { + span_length++; + } + + draw_span( + ((bi->start_scanline + row) * fbi->width) + start_column, + span_length, attribs_start, attribs_steps, column_delta, + fbi); + + if (row_delta > 0) { + step_up(left_attribs, left_attribs_steps, num_planes, + left_div); + step_up(rght_attribs, rght_attribs_steps, num_planes, + rght_div); + } + row++; + free(attribs_steps); + free(attribs_end); + free(attribs_begin); + free(attribs_start); + } + } + } + free(rght_attribs); + free(left_attribs); +} + + + +static void +draw_degenerate_horizontal(Xy const xy, + fract * const attribs_left, + fract * const attribs_mid, + const fract * const top2mid_steps, + const fract * const top2bot_steps, + const fract * const mid2bot_steps, + int32_t const top2mid_delta, + int32_t const top2bot_delta, + int32_t const mid2bot_delta, + framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + + fract * attribs_left_bkup; + + MALLOCARRAY_NOFAIL(attribs_left_bkup, num_planes); + + memcpy(attribs_left_bkup, attribs_left, num_planes * sizeof(fract)); + + { + int16_t const y = xy._[0][1]; + + int16_t x[3]; + int16_t x_start[3]; + fract * attribs[3]; + const fract * steps[3]; + int32_t span_length[3]; + unsigned int i; + + x[0] = xy._[0][0]; + x[1] = xy._[1][0]; + x[2] = xy._[2][0]; + + x_start[0] = x[0]; + x_start[1] = x[0]; + x_start[2] = x[1]; + + attribs[0] = attribs_left; + attribs[1] = attribs_left_bkup; + attribs[2] = attribs_mid; + + steps[0] = top2bot_steps; + steps[1] = top2mid_steps; + steps[2] = mid2bot_steps; + + span_length[0] = x[2] - x[0]; + span_length[1] = x[1] - x[0]; + span_length[2] = x[2] - x[1]; + + for (i = 0; i < 3; i++) { + int32_t const column_delta = span_length[i]; + + if (x_start[i] >= fbi->width || x_start[i] + span_length[i] < 0) { + continue; + } + + if (x_start[i] < 0) { + multi_step_up(attribs[i], steps[i], num_planes, -x_start[i], + column_delta); + + span_length[i] += x_start[i]; + + x_start[i] = 0; + } + + if (x_start[i] + span_length[i] >= fbi->width) { + span_length[i] -= x_start[i] + span_length[i] - fbi->width; + } else { + span_length[i]++; + } + + draw_span((y * fbi->width) + x_start[i], span_length[i], + attribs[i], steps[i], column_delta, fbi); + } + } + free(attribs_left_bkup); +} + + + +void +draw_triangle(Xy const xy_input, + Attribs const attribs_input, + boundary_info * const bi, + framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + + Xy xy; + int32_t * attribs[3]; + unsigned int i; + uint8_t index_array[3]; + int32_t y_array[3]; + int32_t x_array[3]; + + MALLOCARRAY_NOFAIL(attribs[0], num_planes); + MALLOCARRAY_NOFAIL(attribs[1], num_planes); + MALLOCARRAY_NOFAIL(attribs[2], num_planes); + + xy = xy_input; + + for (i = 0; i < 3; i++) { + memcpy(attribs[i], attribs_input._[i], num_planes * sizeof(int32_t)); + } + + /* Argument preparations for sort3: */ + + index_array[0] = 0; index_array[1] = 1; index_array[2] = 2; + y_array[0] = xy._[0][1]; y_array[1] = xy._[1][1]; y_array[2] = xy._[2][1]; + x_array[0] = xy._[0][0]; x_array[1] = xy._[1][0]; x_array[2] = xy._[2][0]; + + sort3(index_array, y_array, x_array); + + { + uint8_t const top = index_array[0]; + uint8_t const mid = index_array[1]; + uint8_t const bot = index_array[2]; + + bool mid_is_to_the_left; + + Xy xy_sorted; + + xy_sorted._[0][0] = xy._[top][0]; + xy_sorted._[0][1] = xy._[top][1]; + xy_sorted._[1][0] = xy._[mid][0]; + xy_sorted._[1][1] = xy._[mid][1]; + xy_sorted._[2][0] = xy._[bot][0]; + xy_sorted._[2][1] = xy._[bot][1]; + + mid_is_to_the_left = + gen_triangle_boundaries(xy_sorted, bi, fbi->width, fbi->height); + + if (bi->start_scanline == -1) { + /* Triangle is completely out of the bounds of the framebuffer. */ + } else { + bool const no_upper_part = + (xy_sorted._[1][1] == xy_sorted._[0][1]); + + bool const horizontal = + (xy._[0][1] == xy._[1][1] && xy._[1][1] == xy._[2][1]); + /* We are dealing with a degenerate horizontal triangle */ + + uint8_t t = ~horizontal & 1; + + int32_t top2mid_delta = xy._[mid][t] - xy._[top][t]; + int32_t top2bot_delta = xy._[bot][t] - xy._[top][t]; + int32_t mid2bot_delta = xy._[bot][t] - xy._[mid][t]; + + fract * top2mid_steps; + fract * top2bot_steps; + fract * mid2bot_steps; + + fract * upper_left_attribs_steps; + fract * lower_left_attribs_steps; + fract * upper_rght_attribs_steps; + fract * lower_rght_attribs_steps; + + int32_t upper_left_delta; + int32_t lower_left_delta; + int32_t upper_rght_delta; + int32_t lower_rght_delta; + + fract * left_attribs; + fract * rght_attribs; + + bool degenerate_horizontal; + + MALLOCARRAY_NOFAIL(top2mid_steps, num_planes); + MALLOCARRAY_NOFAIL(top2bot_steps, num_planes); + MALLOCARRAY_NOFAIL(mid2bot_steps, num_planes); + MALLOCARRAY_NOFAIL(left_attribs, num_planes); + MALLOCARRAY_NOFAIL(rght_attribs, num_planes); + + if (!horizontal) { + top2mid_delta += !no_upper_part; + top2bot_delta += 1; + mid2bot_delta += no_upper_part; + } + + gen_steps(attribs[top], attribs[mid], top2mid_steps, num_planes, + top2mid_delta); + gen_steps(attribs[top], attribs[bot], top2bot_steps, num_planes, + top2bot_delta); + gen_steps(attribs[mid], attribs[bot], mid2bot_steps, num_planes, + mid2bot_delta); + + int32_to_fract_array(attribs[top], left_attribs, num_planes); + int32_to_fract_array(attribs[top], rght_attribs, num_planes); + + if (mid_is_to_the_left) { + upper_left_attribs_steps = top2mid_steps; + lower_left_attribs_steps = mid2bot_steps; + upper_rght_attribs_steps = top2bot_steps; + lower_rght_attribs_steps = upper_rght_attribs_steps; + + upper_left_delta = top2mid_delta; + lower_left_delta = mid2bot_delta; + upper_rght_delta = top2bot_delta; + lower_rght_delta = upper_rght_delta; + } else { + upper_rght_attribs_steps = top2mid_steps; + lower_rght_attribs_steps = mid2bot_steps; + upper_left_attribs_steps = top2bot_steps; + lower_left_attribs_steps = upper_left_attribs_steps; + + upper_rght_delta = top2mid_delta; + lower_rght_delta = mid2bot_delta; + upper_left_delta = top2bot_delta; + lower_left_delta = upper_left_delta; + } + + if (no_upper_part) { + int32_to_fract_array(attribs[mid], rght_attribs, num_planes); + + if (horizontal) { + degenerate_horizontal = true; + } else { + degenerate_horizontal = false; + + step_up(left_attribs, lower_left_attribs_steps, num_planes, + lower_left_delta); + step_up(rght_attribs, lower_rght_attribs_steps, num_planes, + lower_rght_delta); + } + } else { + int32_t delta; + + degenerate_horizontal = false; + + step_up(left_attribs, upper_left_attribs_steps, num_planes, + upper_left_delta); + step_up(rght_attribs, upper_rght_attribs_steps, num_planes, + upper_rght_delta); + + if (bi->num_upper_rows > 0) { + + if (bi->start_scanline > xy._[top][1]) { + delta = bi->start_scanline - xy._[top][1]; + + multi_step_up(left_attribs, upper_left_attribs_steps, + num_planes, delta, upper_left_delta); + multi_step_up(rght_attribs, upper_rght_attribs_steps, + num_planes, delta, upper_rght_delta); + } + + draw_partial_triangle( + left_attribs, upper_left_attribs_steps, + rght_attribs, upper_rght_attribs_steps, + upper_left_delta, upper_rght_delta, + true, + bi, + fbi + ); + + delta = xy._[mid][1] - bi->start_scanline; + } else { + delta = top2mid_delta; + } + + multi_step_up(left_attribs, upper_left_attribs_steps, + num_planes, delta, upper_left_delta); + multi_step_up(rght_attribs, upper_rght_attribs_steps, + num_planes, delta, upper_rght_delta); + } + if (degenerate_horizontal) { + draw_degenerate_horizontal( + xy_sorted, + left_attribs, rght_attribs, + top2mid_steps, top2bot_steps, mid2bot_steps, + top2mid_delta, top2bot_delta, mid2bot_delta, + fbi + ); + } else { + if (bi->start_scanline > xy._[mid][1]) { + int32_t const delta = bi->start_scanline - xy._[mid][1]; + + multi_step_up(left_attribs, lower_left_attribs_steps, + num_planes, delta, lower_left_delta); + multi_step_up(rght_attribs, lower_rght_attribs_steps, + num_planes, delta, lower_rght_delta); + } + + draw_partial_triangle( + left_attribs, lower_left_attribs_steps, + rght_attribs, lower_rght_attribs_steps, + lower_left_delta, lower_rght_delta, + false, + bi, + fbi + ); + } + free(rght_attribs); free(left_attribs); + free(mid2bot_steps); free(top2bot_steps); free(top2mid_steps); + } + } + free(attribs[2]); free(attribs[1]); free(attribs[0]); +} + + diff --git a/generator/pamtris/triangle.h b/generator/pamtris/triangle.h new file mode 100644 index 00000000..79178ad0 --- /dev/null +++ b/generator/pamtris/triangle.h @@ -0,0 +1,25 @@ +#ifndef TRIANGLE_H_INCLUDED +#define TRIANGLE_H_INCLUDED + +#include <stdint.h> + +#include "limits_pamtris.h" + +struct boundary_info; +struct framebuffer_info; + +typedef struct { + int32_t _[3][2]; +} Xy; + +typedef struct { + int32_t _[3][MAX_NUM_ATTRIBS + 1]; +} Attribs; + +void +draw_triangle(Xy const xy, + Attribs const attribs, + struct boundary_info * const bdi, + struct framebuffer_info * const fbi); + +#endif diff --git a/generator/pamtris/utils.c b/generator/pamtris/utils.c new file mode 100644 index 00000000..09c9b4d0 --- /dev/null +++ b/generator/pamtris/utils.c @@ -0,0 +1,256 @@ +/*============================================================================= + utils.c +=============================================================================== + Utility functions +=============================================================================*/ + +#include <stdlib.h> +#include <stdint.h> + +#include "fract.h" + +#include "utils.h" + + + +void +step_up(fract * const vars, + const fract * const steps, + uint8_t const element_ct, + int32_t const divisor) { +/*---------------------------------------------------------------------------- + Apply interpolation steps steps[] to a collection of fract variables vars[] + once. I.e. add each steps[i] to vars[i]. + + 'element_ct' is the number of elements in 'vars' and 'steps'. + + 'divisor' is the divisor used to interpret the fractions. + + It *is* safe to pass a 0 divisor to this function. +-----------------------------------------------------------------------------*/ + unsigned int i; + + for (i = 0; i < element_ct; ++i) { + /* To add the fraction steps[i] to the fraction vars[i]: add the + quotient of step steps[i] to the quotient of variable vars[i] and + the remainder of the step to the remainder of the variable. If this + makes the agumented remainder equal to or larger than the divisor, + increment the quotient of the variable if the step is positive or + decrement it if the step is negative, and subtract the divisor from + the remainder of the variable (in either case). + */ + + vars[i].q += steps[i].q; + vars[i].r += steps[i].r; + + { + uint32_t const negative_mask = -steps[i].negative_flag; + /* (-1 if the step is negative; 1 otherwise) */ + + uint32_t const overdiv_mask = + -(((uint32_t)~(vars[i].r - divisor)) >> 31); + /* = ~0 if var->r >= div; 0 otherwise. */ + + vars[i].q += (negative_mask | 1) & overdiv_mask; + vars[i].r -= divisor & overdiv_mask; + } + } +} + + + +void +multi_step_up(fract * const vars, + const fract * const steps, + uint8_t const elements, + int32_t const times, + int32_t const div) { +/*---------------------------------------------------------------------------- + Similar to step_up, but apply the interpolation step an arbitrary number + of times, instead of just once. + + It *is* also safe to pass a 0 divisor to this function. +-----------------------------------------------------------------------------*/ + unsigned int i; + + for (i = 0; i < elements; i++) { + uint32_t const negative_mask = -steps[i].negative_flag; + + vars[i].q += times * steps[i].q; + vars[i].r += times * steps[i].r; + + if(vars[i].r >= div && div != 0) { + int32_t const r_q = vars[i].r / div; + int32_t const r_r = vars[i].r % div; + + vars[i].q += (-r_q & negative_mask) | (r_q & ~negative_mask); + /* = -r_q if the step is negative; r_q, otherwise. */ + vars[i].r = r_r; + } + } +} + + + +void +gen_steps(const int32_t * const begin, + const int32_t * const end, + fract * const out, + uint8_t const elements, + int32_t const div) { +/*---------------------------------------------------------------------------- + Generate the interpolation steps for a collection of initial and final + values. "begin" points to an array of initial values, "end" points to the + array of corresponding final values; each interpolation step is stored in + the appropriate position in the array pointed by "out"; "elements" indicates + the number of elements in each of the previously mentioned arrays and + "divisor" is the common value by which we want to divide the difference + between each element in the array pointed to by "end" and the corresponding + element in the array pointed to by "begin". After an execution of this + function, for each out[i], with 0 <= i < elements, the following will hold: + + 1. If divisor > 1: + out[i].q = (end[i] - begin[i]) / divisor + out[i].r = abs((end[i] - begin[i]) % divisor) + + 2. If divisor == 1 || divisor == 0: + out[i].q = end[i] - begin[i] + out[i].r = 0 +-----------------------------------------------------------------------------*/ + if (div > 1) { + unsigned int i; + + for (i = 0; i < elements; i++) { + int32_t const delta = end[i] - begin[i]; + + out[i].q = delta / div; + out[i].r = abs(delta % div); + out[i].negative_flag = ((uint32_t)delta) >> 31; + } + } else { + unsigned int i; + + for (i = 0; i < elements; i++) { + int32_t const delta = end[i] - begin[i]; + + out[i].q = delta; + out[i].r = 0; + out[i].negative_flag = ((uint32_t)delta) >> 31; + } + } +} + + + +void +fract_to_int32_array(const fract * const in, + int32_t * const out, + uint8_t const elements) { + + unsigned int i; + + for (i = 0; i < elements; i++) { + out[i] = in[i].q; + } +} + + + +void +int32_to_fract_array(const int32_t * const in, + fract * const out, + uint8_t const elements) { + + unsigned int i; + + for (i = 0; i < elements; i++) { + out[i].q = in[i]; + out[i].r = 0; + } +} + + + +static void +swap(uint8_t * const a, + uint8_t * const b) { +/*---------------------------------------------------------------------------- + Swap the contents pointed to by a and b. +-----------------------------------------------------------------------------*/ + uint8_t const temp = *a; + + *a = *b; + *b = temp; +} + + + +void +sort3(uint8_t * const index_array, + const int32_t * const y_array, + const int32_t * const x_array) { +/*---------------------------------------------------------------------------- + Sort an index array of 3 elements. This function is used to sort vertices + with regard to relative row from top to bottom, but instead of sorting + an array of vertices with all their coordinates, we simply sort their + indices. Each element in the array pointed to by "index_array" should + contain one of the numbers 0, 1 or 2, and each one of them should be + different. "y_array" should point to an array containing the corresponding + Y coordinates (row) of each vertex and "x_array" should point to an array + containing the corresponding X coordinates (column) of each vertex. + + If the Y coordinates are all equal, the indices are sorted with regard to + relative X coordinate from left to right. If only the top two vertex have + the same Y coordinate, the array is sorted normally with regard to relative + Y coordinate, but the first two indices are then sorted with regard to + relative X coordinate. Finally, If only the bottom two vertex have the same + Y coordinate, the array is sorted normally with regard to relative Y + coordinate, but the last two indices are then sorted with regard to relative + X coordinate. +-----------------------------------------------------------------------------*/ + uint8_t * const ia = index_array; + + const int32_t * ya; + const int32_t * xa; + + ya = y_array; /* initial value */ + xa = x_array; /* initial value */ + + if (ya[0] == ya[1] && ya[1] == ya[2]) { + /* In case the vertices represent a degenerate horizontal triangle, we + sort according to relative X coordinate, as opposed to Y. + */ + ya = xa; + } + + if (ya[ia[2]] < ya[ia[1]]) { + swap(ia, ia + 2); + if (ya[ia[2]] < ya[ia[1]]) { + swap(ia + 1, ia + 2); + if (ya[ia[1]] < ya[ia[0]]) { + swap(ia, ia + 1); + } + } + } else if (ya[ia[1]] < ya[ia[0]]) { + swap(ia, ia + 1); + if (ya[ia[2]] < ya[ia[1]]) { + swap(ia + 1, ia + 2); + } + } + + if (ya == xa) { + return; + } + + if (ya[ia[0]] == ya[ia[1]]) { + if (xa[ia[1]] < xa[ia[0]]) { + swap(ia, ia + 1); + } + } else if (ya[ia[1]] == ya[ia[2]]) { + if (xa[ia[2]] < xa[ia[1]]) { + swap(ia + 1, ia + 2); + } + } +} + + diff --git a/generator/pamtris/utils.h b/generator/pamtris/utils.h new file mode 100644 index 00000000..3b7cfbe4 --- /dev/null +++ b/generator/pamtris/utils.h @@ -0,0 +1,41 @@ +#ifndef UTIL_H_INCLUDED +#define UTIL_H_INCLUDED + +#include "fract.h" + +void +gen_steps(const int32_t * const begin, + const int32_t * const end, + fract * const out, + uint8_t const elements, + int32_t const divisor); + +void +step_up(fract * const vars, + const fract * const steps, + uint8_t const elements, + int32_t const divisor); + +void +multi_step_up(fract * const vars, + const fract * const steps, + uint8_t const elements, + int32_t const times, + int32_t const divisor); + +void +fract_to_int32_array(const fract * const in, + int32_t * const out, + uint8_t const elements); + +void +int32_to_fract_array(const int32_t * const in, + fract * const out, + uint8_t const elements); + +void +sort3(uint8_t * const index_array, + const int32_t * const y_array, + const int32_t * const x_array); + +#endif diff --git a/generator/pbmtext.c b/generator/pbmtext.c index 7d9f7cf7..e52d5199 100644 --- a/generator/pbmtext.c +++ b/generator/pbmtext.c @@ -179,59 +179,26 @@ parseCommandLine(int argc, const char ** argv, static void -reportFont(struct font2 * const fontP) { - - unsigned int n; - unsigned int c; +reportFont(const struct font2 * const fontP) { pm_message("FONT:"); - pm_message(" character dimensions: %uw x %uh", + pm_message(" Name: %s", fontP->name); + pm_message(" Encoding: %s", fontP->charset_string); + pm_message(" Origin: %s", pbmFontOrigin[fontP->load_fn]); + pm_message(" Character dimensions: %uw x %uh", fontP->maxwidth, fontP->maxheight); pm_message(" Additional vert white space: %d pixels", fontP->y); - - for (c = 0, n = 0; c <= fontP->maxglyph; ++c) { - if (fontP->glyph[c]) - ++n; - } - - pm_message(" # characters: %u", n); + pm_message(" # characters loaded: %u", fontP->chars); } -static struct font * -fontFromFile(const char * const fileName) { - - struct font * retval; - - jmp_buf jmpbuf; - int rc; - - rc = setjmp(jmpbuf); - - if (rc == 0) { - /* This is the normal program flow */ - pm_setjmpbuf(&jmpbuf); - - retval = pbm_loadfont(fileName); - - pm_setjmpbuf(NULL); - } else { - /* This is the second pass, after pbm_loadfont does a longjmp - because it fails. - */ - pm_setjmpbuf(NULL); - - pm_error("Failed to load font from file '%s'", fileName); - } - - return retval; -} static struct font2 * -font2FromFile(const char * const fileName) { +font2FromFile(const char * const fileName, + PM_WCHAR const maxmaxglyph) { struct font2 * font2P; @@ -244,7 +211,7 @@ font2FromFile(const char * const fileName) { /* This is the normal program flow */ pm_setjmpbuf(&jmpbuf); - font2P = pbm_loadbdffont2(fileName, PM_FONT2_MAXGLYPH); + font2P = pbm_loadfont2(fileName, maxmaxglyph); pm_setjmpbuf(NULL); } else { @@ -267,21 +234,14 @@ computeFont(struct CmdlineInfo const cmdline, struct font2 * font2P; - if (cmdline.wchar && cmdline.font) - font2P = font2FromFile(cmdline.font); - else { - struct font * fontP; - - if (cmdline.font) - fontP = fontFromFile(cmdline.font); - else { - if (cmdline.builtin) - fontP = pbm_defaultfont(cmdline.builtin); - else - fontP = pbm_defaultfont("bdf"); - } - font2P = pbm_expandbdffont(fontP); - } + if (cmdline.font) + font2P = font2FromFile(cmdline.font, + cmdline.wchar ? PM_FONT2_MAXGLYPH : + PM_FONT_MAXGLYPH); + else if (cmdline.builtin) + font2P = pbm_defaultfont2(cmdline.builtin); + else + font2P = pbm_defaultfont2(cmdline.wchar ? "bdf" : "bdf"); if (cmdline.verbose) reportFont(font2P); @@ -292,7 +252,7 @@ computeFont(struct CmdlineInfo const cmdline, struct Text { - PM_WCHAR ** textArray; /* malloc'ed */ + PM_WCHAR ** textArray; /* malloc'ed */ /* This is strictly characters that are in user's font - no control characters, no undefined code points. */ @@ -996,6 +956,7 @@ getText(PM_WCHAR const cmdlineText[], inputText.lineCount = 1; fixControlChars(cmdlineText, fontP, (const PM_WCHAR**)&inputText.textArray[0], fixMode); + free((void *) cmdlineText); } else { /* Read text from stdin. */ @@ -1053,6 +1014,7 @@ getText(PM_WCHAR const cmdlineText[], inputText.textArray = textArray; inputText.lineCount = lineCount; inputText.allocatedLineCount = lineCount; + free(buf); } *inputTextP = inputText; } @@ -1209,6 +1171,9 @@ renderText(unsigned int const cols, insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, space, cols, rows, lspace, fixedAdvance); + /* Free all font data */ + pbm_destroybdffont2(fontP); + { unsigned int row; @@ -1412,10 +1377,6 @@ main(int argc, const char *argv[]) { else pbmtext(cmdline, fontP, stdout); - /* Note that *fontP is unfreeable. See pbm_loadbdffont2, - pbm_expandbdffont - */ - pm_close(stdout); return 0; diff --git a/generator/ppmcie.c b/generator/ppmcie.c index 717ed13b..86325ba6 100644 --- a/generator/ppmcie.c +++ b/generator/ppmcie.c @@ -10,7 +10,7 @@ granted, without any conditions or restrictions. This software is provided "as is" without express or implied warranty. - This program was called cietoppm in Walker's original work. + This program was called cietoppm in Walker's original work. Because "cie" is not a graphics format, Bryan changed the name when he integrated it into the Netpbm package in March 2000. */ @@ -294,10 +294,10 @@ static struct colorSystem const */ static struct colorSystem Customsystem = { "Custom", - 0.64, 0.33, 0.30, 0.60, 0.15, 0.06, + 0.64, 0.33, 0.30, 0.60, 0.15, 0.06, IlluminantD65, GAMMA_REC709 }; - + static void @@ -344,11 +344,11 @@ xyz_to_rgb(const struct colorSystem * const cs, which sums to the desired chromaticity. If the requested chromaticity falls outside the Maxwell triangle (color gamut) formed by the three primaries, one of the r, g, or b weights will - be negative. + be negative. Caller can use constrain_rgb() to desaturate an outside-gamut color to the closest representation within the available - gamut. + gamut. -----------------------------------------------------------------------------*/ double xr, yr, zr, xg, yg, zg, xb, yb, zb; double xw, yw, zw; @@ -542,7 +542,7 @@ drawTongueOutline(pixel ** const pixels, computeMonochromeColorLocation(wavelength, pxcols, pxrows, upvp, &icx, &icy); - + if (wavelength > 380) ppmd_line(pixels, pixcols, pixrows, maxval, B(lx, ly), B(icx, icy), @@ -589,7 +589,7 @@ findTongue(pixel ** const pixels, int const leftEdge = i; *presentP = true; - + for (j = pxcols - 1; j >= leftEdge && PPM_GETR(Bixels(row, j)) == 0; --j); @@ -651,12 +651,12 @@ fillInTongue(pixel ** const pixels, xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb); mx = maxval; - + /* Check whether the requested color is within the gamut achievable with the given color system. If not, draw it in a reduced intensity, interpolated by desaturation to the closest within-gamut color. */ - + if (constrain_rgb(&jr, &jg, &jb)) mx = highlightGamut ? maxval : ((maxval + 1) * 3) / 4; @@ -688,7 +688,7 @@ drawYAxis(pixel ** const pixels, unsigned int const xBias, unsigned int const yBias, pixel const axisColor) { - + unsigned int const pxrows = pixrows - yBias; ppmd_line(pixels, pixcols, pixrows, maxval, @@ -706,7 +706,7 @@ drawXAxis(pixel ** const pixels, unsigned int const xBias, unsigned int const yBias, pixel const axisColor) { - + unsigned int const pxcols = pixcols - xBias; unsigned int const pxrows = pixrows - yBias; @@ -778,7 +778,7 @@ tickY(pixel ** const pixels, /* Pixel row where the top of the tick goes */ unsigned int const tickThickness = Sz(3); /* Thickness of the tick in pixels */ - + char s[20]; assert(tenth < 10); @@ -970,7 +970,7 @@ plotBlackBodyCurve(pixel ** const pixels, /* Label selected tick marks with decreasing density. */ - if (t <= 5000.1 || (t > 5000.0 && + if (t <= 5000.1 || (t > 5000.0 && ((((int) t) % 5000) == 0) && t != 20000.0)) { char bb[20]; @@ -980,7 +980,7 @@ plotBlackBodyCurve(pixel ** const pixels, B(lx - Sz(12), ly - Sz(4)), Sz(6), 0, bb, PPMD_NULLDRAWPROC, (char *) &rgbcolor); } - + } } lx = xb; @@ -998,7 +998,7 @@ overlappingLegend(bool const upvp, if (upvp) retval = (waveLength == 430 || waveLength == 640); - else + else retval = (waveLength == 460 || waveLength == 630 || waveLength == 640); return retval; } @@ -1054,7 +1054,7 @@ plotMonochromeWavelengths( /* Draw the tick mark */ PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); tx = icx + ((x < 520) ? Sz(-2) : ((x >= 535) ? Sz(2) : 0)); - ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2))); + ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2))); ppmd_line(pixels, pixcols, pixrows, maxval, B(icx, icy), B(tx, ty), PPMD_NULLDRAWPROC, (char *) &rgbcolor); @@ -1113,7 +1113,7 @@ writeLabel(pixel ** const pixels, char sysdesc[256]; PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); - + pm_snprintf(sysdesc, sizeof(sysdesc), "System: %s\n" "Primary illuminants (X, Y)\n" @@ -1198,9 +1198,9 @@ main(int argc, } else if (pm_keymatch(argv[argn], "-smpte", 2)) { cs = &SMPTEsystem; } else if (pm_keymatch(argv[argn], "-hdtv", 2)) { - cs = &HDTVsystem; + cs = &HDTVsystem; } else if (pm_keymatch(argv[argn], "-cie", 1)) { - cs = &CIEsystem; + cs = &CIEsystem; } else if (pm_keymatch(argv[argn], "-black", 3)) { showBlack = true; /* Show black body curve */ } else if (pm_keymatch(argv[argn], "-wpoint", 2)) { diff --git a/lib/Makefile b/lib/Makefile index a0f33745..65177758 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -28,7 +28,8 @@ endif LIBOBJECTS = libpm.o pmfileio.o fileio.o colorname.o \ libpamd.o \ libpbm1.o libpbm2.o libpbm3.o \ - libpbmfont.o pbmfontdata1.o pbmfontdata2.o \ + libpbmfont0.o libpbmfont1.o libpbmfont2.o \ + pbmfontdata0.o pbmfontdata1.o pbmfontdata2.o \ libpgm1.o libpgm2.o \ libppm1.o libppm2.o libppmcmap.o libppmcolor.o libppmfuzzy.o \ libppmd.o ppmdfont.o standardppmdfont.o path.o \ @@ -56,7 +57,7 @@ MANUALS3 = libnetpbm MANUALS5 = pbm pgm ppm pnm pam INTERFACE_HEADERS = colorname.h \ - pam.h pamdraw.h pammap.h pbm.h pbmfont.h \ + pam.h pamdraw.h pammap.h pbm.h pbmfont.h pbmfontdata.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 \ diff --git a/lib/libpamn.c b/lib/libpamn.c index 65c8979f..8ae57037 100644 --- a/lib/libpamn.c +++ b/lib/libpamn.c @@ -69,7 +69,8 @@ allocpamrown(const struct pam * const pamP, overflow will not occur in our calculations. NOTE: pnm_readpaminit() ensures this assumption is valid. -----------------------------------------------------------------------------*/ - int const bytes_per_tuple = allocationDepth(pamP) * sizeof(samplen); + unsigned int const bytes_per_tuple = + allocationDepth(pamP) * sizeof(samplen); tuplen * tuplerown; const char * error; @@ -643,6 +644,63 @@ pnm_unapplyopacityrown(struct pam * const pamP, +void +pnm_maketuplergbn(const struct pam * const pamP, + tuplen const tuple) { + + if (allocationDepth(pamP) < 3) + pm_error("allocation depth %u passed to pnm_maketuplergb(). " + "Must be at least 3.", allocationDepth(pamP)); + + if (pamP->depth < 3) + tuple[2] = tuple[1] = tuple[0]; +} + + + +void +pnm_makerowrgbn(const struct pam * const pamP, + tuplen * const tuplerow) { + + if (pamP->depth < 3) { + unsigned int col; + + if (allocationDepth(pamP) < 3) + pm_error("allocation depth %u passed to pnm_makerowrgb(). " + "Must be at least 3.", allocationDepth(pamP)); + + for (col = 0; col < pamP->width; ++col) { + tuplen const thisTuple = tuplerow[col]; + thisTuple[2] = thisTuple[1] = thisTuple[0]; + } + } +} + + + +void +pnm_makearrayrgbn(const struct pam * const pamP, + tuplen ** const tuples) { + + if (pamP->depth < 3) { + unsigned int row; + if (allocationDepth(pamP) < 3) + pm_error("allocation depth %u passed to pnm_makearrayrgb(). " + "Must be at least 3.", allocationDepth(pamP)); + + for (row = 0; row < pamP->height; ++row) { + tuplen * const tuplerow = tuples[row]; + unsigned int col; + for (col = 0; col < pamP->width; ++col) { + tuplen const thisTuple = tuplerow[col]; + thisTuple[2] = thisTuple[1] = thisTuple[0]; + } + } + } +} + + + static void fillInMap(pnm_transformMap const ungammaTransformMap, sample const maxval, diff --git a/lib/libpbmfont.c b/lib/libpbmfont.c deleted file mode 100644 index 24858b04..00000000 --- a/lib/libpbmfont.c +++ /dev/null @@ -1,1185 +0,0 @@ -/* -** -** Font routines. -** -** BDF font code Copyright 1993 by George Phillips. -** -** Copyright (C) 1991 by Jef Poskanzer. -** -** 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. -** -** BDF font specs available from: -** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf -** Glyph Bitmap Distribution Format (BDF) Specification -** Version 2.2 -** 22 March 1993 -** Adobe Developer Support -*/ - -#include <assert.h> -#include <string.h> -#include <ctype.h> - -#include "netpbm/pm_c_util.h" -#include "netpbm/mallocvar.h" -#include "netpbm/nstring.h" - -#include "pbmfont.h" -#include "pbm.h" - -static unsigned int const firstCodePoint = 32; - /* This is the code point of the first character in a pbmfont font. - In ASCII, it is a space. - */ - -static unsigned int const nCharsInFont = 96; - /* The number of characters in a pbmfont font. A pbmfont font defines - characters at position 32 (ASCII space) through 127, so that's 96. - */ - - -struct font * -pbm_defaultfont(const char * const name) { -/*---------------------------------------------------------------------------- - Generate the built-in font with name 'name'. ------------------------------------------------------------------------------*/ - struct font * retval; - - if (streq(name, "bdf")) - retval = &pbm_defaultBdffont; - else if (streq(name, "fixed")) - retval = &pbm_defaultFixedfont; - else - pm_error( "built-in font name unknown, try 'bdf' or 'fixed'"); - - return retval; -} - - - -static void -findFirstBlankRow(const bit ** const font, - unsigned int const fcols, - unsigned int const frows, - unsigned int * const browP) { - - unsigned int row; - bool foundBlank; - - for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) { - unsigned int col; - bit col0Value = font[row][0]; - bool rowIsBlank; - rowIsBlank = true; /* initial assumption */ - for (col = 1; col < fcols; ++col) - if (font[row][col] != col0Value) - rowIsBlank = false; - - if (rowIsBlank) { - foundBlank = true; - *browP = row; - } - } - - if (!foundBlank) - pm_error("couldn't find blank pixel row in font"); -} - - - -static void -findFirstBlankCol(const bit ** const font, - unsigned int const fcols, - unsigned int const frows, - unsigned int * const bcolP) { - - unsigned int col; - bool foundBlank; - - for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) { - unsigned int row; - bit row0Value = font[0][col]; - bool colIsBlank; - colIsBlank = true; /* initial assumption */ - for (row = 1; row < frows; ++row) - if (font[row][col] != row0Value) - colIsBlank = false; - - if (colIsBlank) { - foundBlank = true; - *bcolP = col; - } - } - - if (!foundBlank) - pm_error("couldn't find blank pixel column in font"); -} - - - -static void -computeCharacterSize(const bit ** const font, - unsigned int const fcols, - unsigned int const frows, - unsigned int * const cellWidthP, - unsigned int * const cellHeightP, - unsigned int * const charWidthP, - unsigned int * const charHeightP) { - - unsigned int firstBlankRow; - unsigned int firstBlankCol; - unsigned int heightLast11Rows; - - findFirstBlankRow(font, fcols, frows, &firstBlankRow); - - findFirstBlankCol(font, fcols, frows, &firstBlankCol); - - heightLast11Rows = frows - firstBlankRow; - - if (heightLast11Rows % 11 != 0) - pm_error("The rows of characters in the font do not appear to " - "be all the same height. The last 11 rows are %u pixel " - "rows high (from pixel row %u up to %u), " - "which is not a multiple of 11.", - heightLast11Rows, firstBlankRow, frows); - else { - unsigned int widthLast15Cols; - - *cellHeightP = heightLast11Rows / 11; - - widthLast15Cols = fcols - firstBlankCol; - - if (widthLast15Cols % 15 != 0) - pm_error("The columns of characters in the font do not appear to " - "be all the same width. " - "The last 15 columns are %u pixel " - "columns wide (from pixel col %u up to %u), " - "which is not a multiple of 15.", - widthLast15Cols, firstBlankCol, fcols); - else { - *cellWidthP = widthLast15Cols / 15; - - *charWidthP = firstBlankCol; - *charHeightP = firstBlankRow; - } - } -} - - - -struct font* -pbm_dissectfont(const bit ** const font, - unsigned int const frows, - unsigned int const fcols) { - /* - This routine expects a font bitmap representing the following text: - - (0,0) - M ",/^_[`jpqy| M - - / !"#$%&'()*+ / - < ,-./01234567 < - > 89:;<=>?@ABC > - @ DEFGHIJKLMNO @ - _ PQRSTUVWXYZ[ _ - { \]^_`abcdefg { - } hijklmnopqrs } - ~ tuvwxyz{|}~ ~ - - M ",/^_[`jpqy| M - - The bitmap must be cropped exactly to the edges. - - The characters in the border you see are irrelevant except for - character size compuations. The 12 x 8 array in the center is - the font. The top left character there belongs to code point - 0, and the code points increase in standard reading order, so - the bottom right character is code point 127. You can't define - code points < 32 or > 127 with this font format. - - The characters in the top and bottom border rows must include a - character with the lowest reach of any in the font (e.g. "y", - "_") and one with the highest reach (e.g. '"'). The characters - in the left and right border columns must include characters - with the rightmost and leftmost reach of any in the font - (e.g. "M" for both). - - The border must be separated from the font by one blank text - row or text column. - - The dissection works by finding the first blank row and column; - i.e the lower right corner of the "M" in the upper left corner - of the matrix. That gives the height and width of the - maximum-sized character, which is not too useful. But the - distance from there to the opposite side is an integral - multiple of the cell size, and that's what we need. Then it's - just a matter of filling in all the coordinates. */ - - unsigned int cellWidth, cellHeight; - /* Dimensions in pixels of each cell of the font -- that - includes the glyph and the white space above and to the - right of it. Each cell is a tile of the font image. The - top character row and left character row don't count -- - those cells are smaller because they are missing the white - space. - */ - unsigned int charWidth, charHeight; - /* Maximum dimensions of glyph itself, inside its cell */ - - int row, col, ch, r, c, i; - struct font * fn; - struct glyph * glyph; - char* bmap; - - computeCharacterSize(font, fcols, frows, - &cellWidth, &cellHeight, &charWidth, &charHeight); - - /* Now convert to a general font */ - - MALLOCVAR(fn); - if (fn == NULL) - pm_error("out of memory allocating font structure"); - - fn->maxwidth = charWidth; - fn->maxheight = charHeight; - fn->x = fn->y = 0; - - fn->oldfont = font; - fn->frows = frows; - fn->fcols = fcols; - - /* Initialize all character positions to "undefined." Those that - are defined in the font will be filled in below. - */ - for (i = 0; i < PM_FONT_MAXGLYPH + 1; i++) - fn->glyph[i] = NULL; - - MALLOCARRAY(glyph, nCharsInFont); - if ( glyph == NULL ) - pm_error( "out of memory allocating glyphs" ); - - bmap = (char*) malloc( fn->maxwidth * fn->maxheight * nCharsInFont ); - if ( bmap == (char*) 0) - pm_error( "out of memory allocating glyph data" ); - - /* Now fill in the 0,0 coords. */ - row = cellHeight * 2; - col = cellWidth * 2; - for (i = 0; i < firstCodePoint; ++i) - fn->glyph[i] = NULL; - - for ( ch = 0; ch < nCharsInFont; ++ch ) { - glyph[ch].width = fn->maxwidth; - glyph[ch].height = fn->maxheight; - glyph[ch].x = glyph[ch].y = 0; - glyph[ch].xadd = cellWidth; - - for ( r = 0; r < glyph[ch].height; ++r ) - for ( c = 0; c < glyph[ch].width; ++c ) - bmap[r * glyph[ch].width + c] = font[row + r][col + c]; - - glyph[ch].bmap = bmap; - bmap += glyph[ch].width * glyph[ch].height; - - fn->glyph[firstCodePoint + ch] = &glyph[ch]; - - col += cellWidth; - if ( col >= cellWidth * 14 ) { - col = cellWidth * 2; - row += cellHeight; - } - } - for (i = firstCodePoint + nCharsInFont; i < PM_FONT_MAXGLYPH + 1; ++i) - fn->glyph[i] = NULL; - - return fn; -} - - - -struct font * -pbm_loadfont(const char * const filename) { - - FILE * fileP; - struct font * fontP; - char line[10] = "\0\0\0\0\0\0\0\0\0\0"; - /* Initialize to suppress Valgrind error which is reported when file - is empty or very small. - */ - - fileP = pm_openr(filename); - fgets(line, 10, fileP); - pm_close(fileP); - - if (line[0] == PBM_MAGIC1 && - (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) { - fontP = pbm_loadpbmfont(filename); - } else if (!strncmp(line, "STARTFONT", 9)) { - fontP = pbm_loadbdffont(filename); - if (!fontP) - pm_error("could not load BDF font file"); - } else { - pm_error("font file not in a recognized format. Does not start " - "with the signature of a PBM file or BDF font file"); - assert(false); - fontP = NULL; /* defeat compiler warning */ - } - return fontP; -} - - - -struct font * -pbm_loadpbmfont(const char * const filename) { - - FILE * ifP; - bit ** font; - int fcols, frows; - - ifP = pm_openr(filename); - - font = pbm_readpbm(ifP, &fcols, &frows); - - if ((fcols - 1) / 16 >= pbm_maxfontwidth() || - (frows - 1) / 12 >= pbm_maxfontheight()) - pm_error("Absurdly large PBM font file: %s", filename); - else if (fcols < 31 || frows < 23) { - /* Need at least one pixel per character, and this is too small to - have that. - */ - pm_error("PBM font file '%s' too small to be a font file: %u x %u. " - "Minimum sensible size is 31 x 23", - filename, fcols, frows); - } - - pm_close(ifP); - - return pbm_dissectfont((const bit **)font, frows, fcols); -} - - - -void -pbm_dumpfont(struct font * const fontP, - FILE * const ofP) { -/*---------------------------------------------------------------------------- - Dump out font as C source code. ------------------------------------------------------------------------------*/ - unsigned int i; - unsigned int ng; - - if (fontP->oldfont) - pm_message("Netpbm no longer has the capability to generate " - "a font in long hexadecimal data format"); - - for (i = 0, ng = 0; i < PM_FONT_MAXGLYPH +1; ++i) { - if (fontP->glyph[i]) - ++ng; - } - - printf("static struct glyph _g[%d] = {\n", ng); - - for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) { - struct glyph * const glyphP = fontP->glyph[i]; - if (glyphP) { - unsigned int j; - printf(" { %d, %d, %d, %d, %d, \"", glyphP->width, glyphP->height, - glyphP->x, glyphP->y, glyphP->xadd); - - for (j = 0; j < glyphP->width * glyphP->height; ++j) { - if (glyphP->bmap[j]) - printf("\\1"); - else - printf("\\0"); - } - --ng; - printf("\" }%s\n", ng ? "," : ""); - } - } - printf("};\n"); - - printf("struct font XXX_font = { %d, %d, %d, %d, {\n", - fontP->maxwidth, fontP->maxheight, fontP->x, fontP->y); - - { - unsigned int i; - - for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) { - if (fontP->glyph[i]) - printf(" _g + %d", ng++); - else - printf(" NULL"); - - if (i != PM_FONT_MAXGLYPH) printf(","); - printf("\n"); - } - } - - printf(" }\n};\n"); -} - - -/*---------------------------------------------------------------------------- - Routines for loading a BDF font file ------------------------------------------------------------------------------*/ - - -/* The following are not recognized in individual glyph data; library - routines do a pm_error if they see one: - - Vertical writing systems: DWIDTH1, SWIDTH1, VVECTOR, METRICSET, - CONTENTVERSION. - - The following is not recognized and is thus ignored at the global level: - DWIDTH -*/ - - -#define MAXBDFLINE 1024 - -/* Official Adobe document says max length of string is 65535 characters. - However the value 1024 is sufficient for practical uses. -*/ - -typedef struct { -/*---------------------------------------------------------------------------- - This is an object for reading lines of a font file. It reads and tokenizes - them into words. ------------------------------------------------------------------------------*/ - FILE * ifP; - - char line[MAXBDFLINE+1]; - /* 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[6]; - /* 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 wordsSz) { -/*---------------------------------------------------------------------------- - Chop up 's' into words by changing space characters to NUL. Return as - 'words' an array of pointers to the beginnings of those words in 's'. - Terminate the words[] list with a null pointer. - - 'wordsSz' is the number of elements of space in 'words'. If there are more - words in 's' than will fit in that space (including the terminating null - pointer), ignore the excess on the right. ------------------------------------------------------------------------------*/ - unsigned int n; /* Number of words in words[] so far */ - char * p; - - p = &s[0]; - n = 0; - - while (*p) { - if (ISSPACE(*p)) - *p++ = '\0'; - else { - words[n++] = p; - if (n >= wordsSz - 1) - break; - while (*p && !ISSPACE(*p)) - ++p; - } - } - assert(n <= wordsSz - 1); - words[n] = NULL; -} - - - -static void -readline_read(Readline * const readlineP, - bool * const eofP) { -/*---------------------------------------------------------------------------- - Read a nonblank line from the file. Make its contents available - as readlineP->arg[]. - - Return *eofP == true iff there is no nonblank line before EOF or we - are unable to read the file. ------------------------------------------------------------------------------*/ - bool gotLine; - bool error; - - for (gotLine = false, error = false; !gotLine && !error; ) { - char * rc; - - rc = fgets(readlineP->line, MAXBDFLINE+1, readlineP->ifP); - if (rc == NULL) - error = true; - else { - tokenize(readlineP->line, - readlineP->arg, ARRAY_SIZE(readlineP->arg)); - if (readlineP->arg[0] != NULL) - gotLine = true; - } - } - *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(Readline * const readlineP, - unsigned int const glyphWidth, - unsigned int const glyphHeight, - const char * const charName, - unsigned char * const bmap) { - - int n; - unsigned int bmapIndex; - - bmapIndex = 0; - - for (n = glyphHeight; n > 0; --n) { - bool eof; - const char * error; - - readline_read(readlineP, &eof); - - if (eof) - pm_error("End of file in bitmap for character '%s' in BDF " - "font file.", charName); - - 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); - } - } -} - - - -static void -createBmap(unsigned int const glyphWidth, - unsigned int const glyphHeight, - Readline * const readlineP, - const char * const charName, - const char ** const bmapP) { - - unsigned char * bmap; - bool eof; - - if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight) - pm_error("Ridiculously large glyph"); - - MALLOCARRAY(bmap, glyphWidth * glyphHeight); - - if (!bmap) - pm_error("no memory for font glyph byte map"); - - readline_read(readlineP, &eof); - if (eof) - pm_error("End of file encountered reading font glyph byte map from " - "BDF font file."); - - if (streq(readlineP->arg[0], "ATTRIBUTES")) { - /* ATTRIBUTES is defined in Glyph Bitmap Distribution Format (BDF) - Specification Version 2.1, but not in Version 2.2. - */ - bool eof; - readline_read(readlineP, &eof); - if (eof) - pm_error("End of file encountered after ATTRIBUTES in BDF " - "font file."); - } - if (!streq(readlineP->arg[0], "BITMAP")) - pm_error("'%s' found where BITMAP expected in definition of " - "character '%s' in BDF font file.", - readlineP->arg[0], charName); - - assert(streq(readlineP->arg[0], "BITMAP")); - - readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap); - - *bmapP = (char *)bmap; -} - - - -static void -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; - - readline_read(readlineP, &eof); - - if (eof) - pm_error("EOF in BDF font file where '%s' expected", 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(Readline * const readlineP) { -/*---------------------------------------------------------------------------- - 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) { - 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(readlineP->arg[0], "ENDCHAR"); - } -} - - - -static void -interpEncoding(const char ** const arg, - unsigned int * const codepointP, - bool * const badCodepointP, - PM_WCHAR const maxglyph) { -/*---------------------------------------------------------------------------- - With arg[] being the ENCODING statement from the font, return as - *codepointP the codepoint that it indicates (code point is the character - code, e.g. in ASCII, 48 is '0'). - - But if the statement doesn't give an acceptable codepoint return - *badCodepointP == TRUE. - - 'maxglyph' is the maximum codepoint in the font. ------------------------------------------------------------------------------*/ - bool gotCodepoint; - bool badCodepoint; - unsigned int codepoint; - - if (!arg[1]) - pm_error("Invalid ENCODING statement - no arguments"); - if (atoi(arg[1]) >= 0) { - codepoint = atoi(arg[1]); - gotCodepoint = true; - } else { - if (atoi(arg[1]) == -1 && arg[2] != NULL) { - codepoint = atoi(arg[2]); - gotCodepoint = true; - } else - gotCodepoint = false; - } - if (gotCodepoint) { - if (codepoint > maxglyph) - badCodepoint = true; - else - badCodepoint = false; - } else - badCodepoint = true; - - *badCodepointP = badCodepoint; - *codepointP = codepoint; -} - - - -static void -readEncoding(Readline * const readlineP, - unsigned int * const codepointP, - bool * const badCodepointP, - PM_WCHAR const maxglyph) { - - readExpectedStatement(readlineP, "ENCODING"); - - interpEncoding(readlineP->arg, codepointP, badCodepointP, maxglyph); -} - - - -static void -validateFontLimits(const struct font2 * const fontP) { - - assert(pbm_maxfontheight() > 0 && pbm_maxfontwidth() > 0); - - if (fontP->maxwidth <= 0 || - fontP->maxheight <= 0 || - fontP->maxwidth > pbm_maxfontwidth() || - fontP->maxheight > pbm_maxfontheight() || - -fontP->x + 1 > fontP->maxwidth || - -fontP->y + 1 > fontP->maxheight || - fontP->x > fontP->maxwidth || - fontP->y > fontP->maxheight || - fontP->x + fontP->maxwidth > pbm_maxfontwidth() || - fontP->y + fontP->maxheight > pbm_maxfontheight() - ) { - - pm_error("Global font metric(s) out of bounds."); - } - - if (fontP->maxglyph > PM_FONT2_MAXGLYPH) - pm_error("Internal error. Glyph table too large: %u glyphs; " - "Maximum possible in Netpbm is %u", - fontP->maxglyph, PM_FONT2_MAXGLYPH); -} - - - -static void -validateGlyphLimits(const struct font2 * const fontP, - const struct glyph * const glyphP, - const char * const charName) { - - /* Some BDF files code space with zero width and height, - no bitmap data and just the xadd value. - We allow zero width and height, iff both are zero. - */ - - if (((glyphP->width == 0 || glyphP->height == 0) && - !(glyphP->width == 0 && glyphP->height == 0)) || - glyphP->width > fontP->maxwidth || - glyphP->height > fontP->maxheight || - glyphP->x < fontP->x || - glyphP->y < fontP->y || - glyphP->x + (int) glyphP->width > fontP->x + fontP->maxwidth || - glyphP->y + (int) glyphP->height > fontP->y + fontP->maxheight || - glyphP->xadd > pbm_maxfontwidth() || - glyphP->xadd + MAX(glyphP->x,0) + (int) glyphP->width > - pbm_maxfontwidth() - ) { - - pm_error("Font metric(s) for char '%s' out of bounds.\n", charName); - } -} - - - -static void -processChars(Readline * const readlineP, - struct font2 * const fontP, - PM_WCHAR const maxglyph ) { -/*---------------------------------------------------------------------------- - 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 (!readlineP->arg[1]) - pm_error("Invalid CHARS line - no arguments"); - - nCharacters = atoi(readlineP->arg[1]); - - nCharsDone = 0; - - while (nCharsDone < nCharacters) { - bool eof; - - readline_read(readlineP, &eof); - if (eof) - pm_error("End of file after CHARS reading BDF font file"); - - if (streq(readlineP->arg[0], "COMMENT")) { - /* ignore */ - } else if (!streq(readlineP->arg[0], "STARTCHAR")) - pm_error("no STARTCHAR after CHARS in BDF font file"); - else if (!readlineP->arg[1]) - pm_error("Invalid STARTCHAR - no arguments"); - else { - const char * const charName = pm_strdup(readlineP->arg[1]); - - struct glyph * glyphP; - unsigned int codepoint; - bool badCodepoint; - - assert(streq(readlineP->arg[0], "STARTCHAR")); - - MALLOCVAR(glyphP); - - if (glyphP == NULL) - pm_error("no memory for font glyph for '%s' character", - charName); - - readEncoding(readlineP, &codepoint, &badCodepoint, maxglyph); - - if (badCodepoint) - skipCharacter(readlineP); - else if (fontP->glyph[codepoint] != NULL) - pm_error("Multiple definition of code point %d " - "in font file", (unsigned int) codepoint); - else { - readExpectedStatement(readlineP, "SWIDTH"); - - 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]); - - validateGlyphLimits(fontP, glyphP, charName); - - createBmap(glyphP->width, glyphP->height, readlineP, charName, - &glyphP->bmap); - - - readExpectedStatement(readlineP, "ENDCHAR"); - - assert(codepoint <= maxglyph); /* Ensured by readEncoding() */ - - fontP->glyph[codepoint] = glyphP; - pm_strfree(charName); - } - ++nCharsDone; - } - } -} - - - -static void -processBdfFontLine(Readline * const readlineP, - struct font2 * const fontP, - bool * const endOfFontP, - PM_WCHAR const maxglyph) { -/*---------------------------------------------------------------------------- - Process a nonblank line just read from a BDF font file. - - This processing may involve reading more lines. ------------------------------------------------------------------------------*/ - *endOfFontP = FALSE; /* initial assumption */ - - assert(readlineP->arg[0] != NULL); /* Entry condition */ - - if (streq(readlineP->arg[0], "COMMENT")) { - /* ignore */ - } else if (streq(readlineP->arg[0], "SIZE")) { - /* ignore */ - } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) { - /* Read off the properties and ignore them all */ - unsigned int propCount; - unsigned int i; - - if (!readlineP->arg[1]) - pm_error("Invalid STARTPROPERTIES statement - no arguments"); - propCount = atoi(readlineP->arg[1]); - - for (i = 0; i < propCount; ++i) { - bool eof; - readline_read(readlineP, &eof); - if (eof) - pm_error("End of file after STARTPROPERTIES in BDF font file"); - } - } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) { - if (!readlineP->arg[1]) - pm_error("Invalid FONTBOUNDINGBOX statement - no arguments"); - fontP->maxwidth = atoi(readlineP->arg[1]); - if (!readlineP->arg[2]) - pm_error("Invalid FONTBOUNDINGBOX statement - only 1 argument"); - fontP->maxheight = atoi(readlineP->arg[2]); - if (!readlineP->arg[3]) - pm_error("Invalid FONTBOUNDINGBOX statement - only 2 arguments"); - fontP->x = atoi(readlineP->arg[3]); - if (!readlineP->arg[4]) - pm_error("Invalid FONTBOUNDINGBOX statement - only 3 arguments"); - fontP->y = atoi(readlineP->arg[4]); - validateFontLimits(fontP); - } else if (streq(readlineP->arg[0], "ENDPROPERTIES")) { - if (fontP->maxwidth ==0) - pm_error("Encountered ENDPROPERTIES before FONTBOUNDINGBOX " - "in BDF font file"); - } else if (streq(readlineP->arg[0], "ENDFONT")) { - *endOfFontP = true; - } else if (streq(readlineP->arg[0], "CHARS")) { - if (fontP->maxwidth ==0) - pm_error("Encountered CHARS before FONTBOUNDINGBOX " - "in BDF font file"); - else - processChars(readlineP, fontP, maxglyph); - } else { - /* ignore */ - } -} - - - -struct font2 * -pbm_loadbdffont2(const char * const filename, - PM_WCHAR const maxglyph) { -/*---------------------------------------------------------------------------- - Read a BDF font file "filename" as a 'font2' structure. A 'font2' - structure is more expressive than a 'font' structure, most notably in that - it can handle wide code points and many more glyphs. - - Codepoints up to maxglyph inclusive are valid in the file. - - The returned object is in new malloc'ed storage, in many pieces, and - cannot be destroyed. ------------------------------------------------------------------------------*/ - /* For our return object to be destroyable, we need to supply a destroy - function, and it needs to return glyph and raster memory, and - struct font needs to manage glyph memory separately from mapping - code points to glyphs. - */ - FILE * ifP; - Readline readline; - struct font2 * font2P; - bool endOfFont; - - ifP = fopen(filename, "rb"); - if (!ifP) - pm_error("Unable to open BDF font file name '%s'. errno=%d (%s)", - filename, errno, strerror(errno)); - - readline_init(&readline, ifP); - - MALLOCVAR(font2P); - if (font2P == NULL) - pm_error("no memory for font"); - - MALLOCARRAY(font2P->glyph, maxglyph + 1); - if (font2P->glyph == NULL) - pm_error("no memory for font glyphs"); - - font2P->maxglyph = maxglyph; - - font2P->oldfont = NULL; - { - /* Initialize all characters to nonexistent; we will fill the ones we - find in the bdf file later. - */ - PM_WCHAR i; - for (i = 0; i <= maxglyph; ++i) - font2P->glyph[i] = NULL; - } - - font2P->maxwidth = font2P->maxheight = font2P->x = font2P->y = 0; - - readExpectedStatement(&readline, "STARTFONT"); - - endOfFont = FALSE; - - while (!endOfFont) { - bool eof; - readline_read(&readline, &eof); - if (eof) - pm_error("End of file before ENDFONT statement in BDF font file"); - - processBdfFontLine(&readline, font2P, &endOfFont, maxglyph); - } - return font2P; -} - - - -struct font * -pbm_loadbdffont(const char * const filename) { -/*---------------------------------------------------------------------------- - Read a BDF font file "filename" into a traditional font structure. - - Codepoints up to 255 (PM_FONT_MAXGLYPH) are valid. - - Can handle ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-15, etc. - - The returned object is in new malloc'ed storage, in many pieces, and - cannot be destroyed. ------------------------------------------------------------------------------*/ - /* For our return object to deep copy the glyphs and fonts from - the struct font2be destroyable, we need to supply a destroy - function, and it needs to return glyph and raster memory, and - struct font needs to manage glyph memory separately from mapping - code points to glyphs. - */ - struct font * fontP; - struct font2 * font2P; - unsigned int codePoint; - - MALLOCVAR(fontP); - if (fontP == NULL) - pm_error("no memory for font"); - - font2P = pbm_loadbdffont2(filename, PM_FONT_MAXGLYPH); - - fontP->maxwidth = font2P->maxwidth; - fontP->maxheight = font2P->maxheight; - - fontP->x = font2P->x; - fontP->y = font2P->y; - - for (codePoint = 0; codePoint < PM_FONT_MAXGLYPH + 1; ++codePoint) - fontP->glyph[codePoint] = font2P->glyph[codePoint]; - - fontP->oldfont = NULL; - - fontP->fcols = 0; - fontP->frows = 0; - - /* Note that *fontP2 is unfreeable. See pbm_loadbdffont2. And even if it - were, we hooked *fontP into it above, so that would have to turn into a - deep copy before we could free *fontP2. - */ - - return fontP; -} - - - -struct font2 * -pbm_expandbdffont(const struct font * const fontP) { -/*---------------------------------------------------------------------------- - Convert a traditional font structure into an expanded font2 structure. - - This function depends upon the fact that *fontP, like any struct font, - cannot be destroyed. The returned object refers to memory that belongs - to *fontP. - - The returned object is in new malloc'ed storage, in many pieces, and - cannot be destroyed. ------------------------------------------------------------------------------*/ - /* If we ever make struct font destroyable, this function needs to - copy the glyphs and rasters, and struct font and struct font2 need - to manage glyph memory separately from mapping code points to the - glyphs. - */ - PM_WCHAR const maxglyph = PM_FONT_MAXGLYPH; - - struct font2 * font2P; - unsigned int codePoint; - - MALLOCVAR(font2P); - if (font2P == NULL) - pm_error("no memory for font"); - - MALLOCARRAY(font2P->glyph, maxglyph + 1); - if (font2P->glyph == NULL) - pm_error("no memory for font glyphs"); - - font2P->maxwidth = fontP->maxwidth; - font2P->maxheight = fontP->maxheight; - - font2P->x = fontP->x; - font2P->y = fontP->y; - - font2P->maxglyph = maxglyph; - - for (codePoint = 0; codePoint < maxglyph + 1; ++codePoint) - font2P->glyph[codePoint] = fontP->glyph[codePoint]; - - font2P->oldfont = fontP->oldfont; - - font2P->fcols = fontP->fcols; - font2P->frows = fontP->frows; - - return font2P; -} - - diff --git a/lib/libpbmfont0.c b/lib/libpbmfont0.c new file mode 100644 index 00000000..add08047 --- /dev/null +++ b/lib/libpbmfont0.c @@ -0,0 +1,335 @@ +/* +** +** Font routines. +** +** Wide character stuff written by Akira Urushibata in 2018 and contributed +** to the public domain. +** +** Also copyright (C) 1991 by Jef Poskanzer and licensed to the public as +** follows. +** +** 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. +*/ + +#include <assert.h> +#include <string.h> + +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + +#include "pbm.h" +#include "pbmfont.h" +#include "pbmfontdata.h" + + +struct font * +pbm_defaultfont(const char * const name) { +/*---------------------------------------------------------------------------- + Generate the built-in font with name 'name'. +-----------------------------------------------------------------------------*/ + struct font * retval; + + if (streq(name, "bdf")) + retval = &pbm_defaultBdffont; + else if (streq(name, "fixed")) + retval = &pbm_defaultFixedfont; + else + pm_error( "built-in font name unknown, try 'bdf' or 'fixed'"); + + return retval; +} + + + +struct font2 * +pbm_defaultfont2(const char * const requestedFontName) { + + struct font2 * font2P; + struct font2 * retval = NULL; /* initial value */ + unsigned int i; + + for (i = 0; retval == NULL; ++i) { + const char * longName; + const char * shortName; + font2P = (struct font2 * ) pbm_builtinFonts[i]; + if (font2P == NULL) + break; + + longName = font2P->name; + shortName = &longName[strlen("builtin ")]; + + if (streq(shortName, requestedFontName)) + retval = font2P; + } + + if (retval == NULL) + pm_error("No builtin font named %s", requestedFontName); + + return retval; +} + + + +static void +selectFontType(const char * const filename, + PM_WCHAR const maxmaxglyph, + unsigned int const isWide, + struct font ** const fontPP, + struct font2 ** const font2PP) { + + FILE * fileP; + struct font * fontP = NULL; /* initial value */ + struct font2 * font2P = NULL; /* initial value */ + char line[10] = "\0\0\0\0\0\0\0\0\0\0"; + /* Initialize to suppress Valgrind error which is reported when file + is empty or very small. + */ + + fileP = pm_openr(filename); + fgets(line, 10, fileP); + pm_close(fileP); + + if (line[0] == PBM_MAGIC1 && + (line[1] == PBM_MAGIC2 || line[1] == RPBM_MAGIC2)) { + if (isWide == TRUE) + font2P = pbm_loadpbmfont2(filename); + else + fontP = pbm_loadpbmfont(filename); + if (fontP == NULL && font2P == NULL) + pm_error("could not load PBM font file"); + + } else if (!strncmp(line, "STARTFONT", 9)) { + if (isWide == TRUE) + font2P = pbm_loadbdffont2(filename, maxmaxglyph); + else + fontP = pbm_loadbdffont(filename); + if (fontP == NULL && font2P == NULL) + pm_error("could not load BDF font file"); + + } else { + pm_error("font file not in a recognized format. Does not start " + "with the signature of a PBM file or BDF font file"); + assert(false); + fontP = NULL; /* defeat compiler warning */ + } + + if (isWide) + *font2PP = font2P; + else + *fontPP = fontP; +} + + + +struct font * +pbm_loadfont(const char * const filename) { + + struct font * fontP; + struct font2 * font2P; + + selectFontType(filename, PM_FONT_MAXGLYPH, FALSE, &fontP, &font2P); + return fontP; +} + + + +struct font2 * +pbm_loadfont2(const char * const filename, + PM_WCHAR const maxmaxglyph) { + + struct font * fontP; + struct font2 * font2P; + + selectFontType(filename, maxmaxglyph, TRUE, &fontP, &font2P); + return font2P; +} + + + +void +pbm_createbdffont2_base(struct font2 ** const font2PP, + PM_WCHAR const maxmaxglyph) { + + struct font2 * font2P; + + MALLOCVAR(font2P); + if (font2P == NULL) + pm_error("no memory for font"); + + MALLOCARRAY(font2P->glyph, maxmaxglyph + 1); + if (font2P->glyph == NULL) + pm_error("no memory for font glyphs"); + + /* Initialize */ + font2P->size = sizeof (struct font2); + font2P->len = PBM_FONT2_STRUCT_SIZE(charset_string); + + /* Caller should overwrite following fields as necessary */ + font2P->oldfont = NULL; + font2P->fcols = font2P->frows = 0; + font2P->selector = NULL; + font2P->default_char = 0; + font2P->default_char_defined = FALSE; + font2P->total_chars = font2P->chars = 0; + font2P->name = NULL; + font2P->charset = ENCODING_UNKNOWN; + font2P->charset_string = NULL; + + *font2PP = font2P; +} + + + +static void +destroyGlyphData(struct glyph ** const glyph, + PM_WCHAR const maxglyph) { +/*---------------------------------------------------------------------------- + Free glyph objects and bitmap objects. + + This does not work when an object is "shared" through multiple pointers + referencing an identical address and thus pointing to a common glyph + or bitmap object. +-----------------------------------------------------------------------------*/ + + PM_WCHAR i; + + for(i = 0; i <= maxglyph; ++i) { + if (glyph[i]!=NULL) { + free((void *) (glyph[i]->bmap)); + free(glyph[i]); + } + } +} + + +void +pbm_destroybdffont2_base(struct font2 * const font2P) { +/*---------------------------------------------------------------------------- + Free font2 structure, but not the glyph data +---------------------------------------------------------------------------- */ + + free(font2P->selector); + + free(font2P->name); + free(font2P->charset_string); + free(font2P->glyph); + + if (font2P->oldfont !=NULL) + pbm_freearray(font2P->oldfont, font2P->frows); + + free((void *)font2P); + +} + + + +void +pbm_destroybdffont2(struct font2 * const font2P) { +/*---------------------------------------------------------------------------- + Free font2 structure and glyph data + + Examines the 'load_fn' field to check whether the object is fixed data. + Do nothing if 'load_fn' is 'FIXED_DATA'. +---------------------------------------------------------------------------- */ + + if (font2P->load_fn != FIXED_DATA) { + destroyGlyphData(font2P->glyph, font2P->maxglyph); + pbm_destroybdffont2_base(font2P); + } +} + + + +void +pbm_destroybdffont(struct font * const fontP) { +/*---------------------------------------------------------------------------- + Free font structure and glyph data. + + For freeing a structure created by pbm_loadbdffont() or pbm_loadpbmfont(). +---------------------------------------------------------------------------- */ + + destroyGlyphData(fontP->glyph, PM_FONT_MAXGLYPH); + + if (fontP->oldfont !=NULL) + pbm_freearray(fontP->oldfont, fontP->frows); + + free(fontP); +} + + + +struct font2 * +pbm_expandbdffont(const struct font * const fontP) { +/*---------------------------------------------------------------------------- + Convert a traditional 'font' structure into an expanded 'font2' structure. + + After calling this function *fontP may be freed, but not the individual + glyph data: fontP->glyph[0...255] . + + Using this function on static data is not recommended. Rather add + the extra fields to make a font2 structure. See file pbmfontdata1.c + for an example. + + The returned object is in new malloc'ed storage, in many pieces. + + Destroy with pbm_destroybdffont2() if *fontP is read from a file. + + Destroy with pbm_destroybdffont2_base() if *fontP is static data + and you desire to defy the above-stated recommendation. + + The general function for conversion in the opposite direction + 'font2' => 'font' is font2ToFont() in libpbmfont2.c . It is currently + declared as static. + ---------------------------------------------------------------------------*/ + PM_WCHAR maxglyph, codePoint; + unsigned int nCharacters; + struct font2 * font2P; + + pbm_createbdffont2_base(&font2P, PM_FONT_MAXGLYPH); + + font2P->maxwidth = fontP->maxwidth; + font2P->maxheight = fontP->maxheight; + + font2P->x = fontP->x; + font2P->y = fontP->y; + + /* Hunt for max non-NULL entry in glyph table */ + for (codePoint = PM_FONT_MAXGLYPH; + fontP->glyph[codePoint] == NULL && codePoint > 0; --codePoint) + ; + + maxglyph = font2P->maxglyph = codePoint; + assert (0 <= maxglyph && maxglyph <= PM_FONT_MAXGLYPH); + + if (maxglyph == 0 && fontP->glyph[0] == NULL) + pm_error("no glyphs loaded"); + + REALLOCARRAY(font2P->glyph, font2P->maxglyph + 1); + + for (codePoint = 0; codePoint <= maxglyph; ++codePoint) { + font2P->glyph[codePoint] = fontP->glyph[codePoint]; + + if (font2P->glyph[codePoint] != NULL) + ++nCharacters; + } + + font2P->oldfont = fontP->oldfont; + font2P->fcols = fontP->fcols; + font2P->frows = fontP->frows; + + font2P->bit_format = PBM_FORMAT; + font2P->total_chars = font2P->chars = nCharacters; + font2P->load_fn = CONVERTED_TYPE1_FONT; + /* Caller should be overwrite the above to a more descriptive + value */ + return font2P; +} + + + diff --git a/lib/libpbmfont1.c b/lib/libpbmfont1.c new file mode 100644 index 00000000..2b0993a9 --- /dev/null +++ b/lib/libpbmfont1.c @@ -0,0 +1,359 @@ +/* +** +** Routines for loading a PBM sheet font file +** +** Copyright (C) 1991 by Jef Poskanzer. +** +** 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. +*/ + +#include <assert.h> +#include <string.h> + +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + +#include "pbm.h" +#include "pbmfont.h" + + +/*---------------------------------------------------------------------------- + + The routines in this file reads a font bitmap representing + the following text: + + (0,0) + M ",/^_[`jpqy| M + + / !"#$%&'()*+ / + < ,-./01234567 < + > 89:;<=>?@ABC > + @ DEFGHIJKLMNO @ + _ PQRSTUVWXYZ[ _ + { \]^_`abcdefg { + } hijklmnopqrs } + ~ tuvwxyz{|}~ ~ + + M ",/^_[`jpqy| M + + The bitmap must be cropped exactly to the edges. + + The characters in the border you see are irrelevant except for + character size compuations. The 12 x 8 array in the center is + the font. The top left character there belongs to code point + 0, and the code points increase in standard reading order, so + the bottom right character is code point 127. You can't define + code points < 32 or > 127 with this font format. + + The characters in the top and bottom border rows must include a + character with the lowest reach of any in the font (e.g. "y", + "_") and one with the highest reach (e.g. '"'). The characters + in the left and right border columns must include characters + with the rightmost and leftmost reach of any in the font + (e.g. "M" for both). + + The border must be separated from the font by one blank text + row or text column. +-----------------------------------------------------------------------------*/ + + +static unsigned int const firstCodePoint = 32; + /* This is the code point of the first character in a pbmfont font. + In ASCII, it is a space. + */ + +static unsigned int const nCharsInFont = 96; + /* The number of characters in a pbmfont font. A pbmfont font defines + characters at position 32 (ASCII space) through 127, so that's 96. + */ + + +static void +findFirstBlankRow(const bit ** const font, + unsigned int const fcols, + unsigned int const frows, + unsigned int * const browP) { + + unsigned int row; + bool foundBlank; + + for (row = 0, foundBlank = false; row < frows / 6 && !foundBlank; ++row) { + unsigned int col; + bit col0Value = font[row][0]; + bool rowIsBlank; + rowIsBlank = true; /* initial assumption */ + for (col = 1; col < fcols; ++col) + if (font[row][col] != col0Value) + rowIsBlank = false; + + if (rowIsBlank) { + foundBlank = true; + *browP = row; + } + } + + if (!foundBlank) + pm_error("couldn't find blank pixel row in font"); +} + + + +static void +findFirstBlankCol(const bit ** const font, + unsigned int const fcols, + unsigned int const frows, + unsigned int * const bcolP) { + + unsigned int col; + bool foundBlank; + + for (col = 0, foundBlank = false; col < fcols / 6 && !foundBlank; ++col) { + unsigned int row; + bit row0Value = font[0][col]; + bool colIsBlank; + colIsBlank = true; /* initial assumption */ + for (row = 1; row < frows; ++row) + if (font[row][col] != row0Value) + colIsBlank = false; + + if (colIsBlank) { + foundBlank = true; + *bcolP = col; + } + } + + if (!foundBlank) + pm_error("couldn't find blank pixel column in font"); +} + + + +static void +computeCharacterSize(const bit ** const font, + unsigned int const fcols, + unsigned int const frows, + unsigned int * const cellWidthP, + unsigned int * const cellHeightP, + unsigned int * const charWidthP, + unsigned int * const charHeightP) { + + unsigned int firstBlankRow; + unsigned int firstBlankCol; + unsigned int heightLast11Rows; + + findFirstBlankRow(font, fcols, frows, &firstBlankRow); + + findFirstBlankCol(font, fcols, frows, &firstBlankCol); + + heightLast11Rows = frows - firstBlankRow; + + if (heightLast11Rows % 11 != 0) + pm_error("The rows of characters in the font do not appear to " + "be all the same height. The last 11 rows are %u pixel " + "rows high (from pixel row %u up to %u), " + "which is not a multiple of 11.", + heightLast11Rows, firstBlankRow, frows); + else { + unsigned int widthLast15Cols; + + *cellHeightP = heightLast11Rows / 11; + + widthLast15Cols = fcols - firstBlankCol; + + if (widthLast15Cols % 15 != 0) + pm_error("The columns of characters in the font do not appear to " + "be all the same width. " + "The last 15 columns are %u pixel " + "columns wide (from pixel col %u up to %u), " + "which is not a multiple of 15.", + widthLast15Cols, firstBlankCol, fcols); + else { + *cellWidthP = widthLast15Cols / 15; + + *charWidthP = firstBlankCol; + *charHeightP = firstBlankRow; + } + } +} + + + +struct font* +pbm_dissectfont(const bit ** const fontsheet, + unsigned int const frows, + unsigned int const fcols) { +/*---------------------------------------------------------------------------- + Dissect PBM sheet font data, create a font structre, + load bitmap data into it. + + Return value is a pointer to the newly created font structure + + The input bitmap data is in memory, in one byte per pixel format. + + The dissection works by finding the first blank row and column; + i.e the lower right corner of the "M" in the upper left corner + of the matrix. That gives the height and width of the + maximum-sized character, which is not too useful. But the + distance from there to the opposite side is an integral + multiple of the cell size, and that's what we need. Then it's + just a matter of filling in all the coordinates. + + Struct font has fields 'oldfont', 'fcols', 'frows' for backward + compability. If there is any need to load data stored in this format + feed the above three, in order, as arguments to this function: + + pbm_dissectfont(oldfont, fcols, frows); + ----------------------------------------------------------------------------*/ + + unsigned int cellWidth, cellHeight; + /* Dimensions in pixels of each cell of the font -- that + includes the glyph and the white space above and to the + right of it. Each cell is a tile of the font image. The + top character row and left character row don't count -- + those cells are smaller because they are missing the white + space. + */ + unsigned int charWidth, charHeight; + /* Maximum dimensions of glyph itself, inside its cell */ + + int row, col, ch, r, c, i; + struct font * fn; + + computeCharacterSize(fontsheet, fcols, frows, + &cellWidth, &cellHeight, &charWidth, &charHeight); + + /* Now convert to a general font */ + + MALLOCVAR(fn); + if (fn == NULL) + pm_error("out of memory allocating font structure"); + + fn->maxwidth = charWidth; + fn->maxheight = charHeight; + fn->x = fn->y = 0; + + fn->oldfont = fontsheet; + fn->frows = frows; + fn->fcols = fcols; + + /* Now fill in the 0,0 coords. */ + row = cellHeight * 2; + col = cellWidth * 2; + + /* Load individual glyphs */ + for ( ch = 0; ch < nCharsInFont; ++ch ) { + /* Allocate memory separately for each glyph. + pbm_loadbdffont2() does this in exactly the same manner. + */ + struct glyph * const glyph = + (struct glyph *) malloc (sizeof (struct glyph)); + char * const bmap = (char*) malloc(fn->maxwidth * fn->maxheight); + + if ( bmap == NULL || glyph == NULL ) + pm_error( "out of memory allocating glyph data" ); + + glyph->width = fn->maxwidth; + glyph->height = fn->maxheight; + glyph->x = glyph->y = 0; + glyph->xadd = cellWidth; + + for ( r = 0; r < glyph->height; ++r ) + for ( c = 0; c < glyph->width; ++c ) + bmap[r * glyph->width + c] = fontsheet[row + r][col + c]; + + glyph->bmap = bmap; + fn->glyph[firstCodePoint + ch] = glyph; + + col += cellWidth; + if ( col >= cellWidth * 14 ) { + col = cellWidth * 2; + row += cellHeight; + } + } + + /* Initialize all remaining character positions to "undefined." */ + for (i = 0; i < firstCodePoint; ++i) + fn->glyph[i] = NULL; + + for (i = firstCodePoint + nCharsInFont; i <= PM_FONT_MAXGLYPH; ++i) + fn->glyph[i] = NULL; + + return fn; +} + + + + +struct font * +pbm_loadpbmfont(const char * const filename) { +/*---------------------------------------------------------------------------- + Read PBM font sheet data from file 'filename'. + Load data into font structure. + + When done with object, free with pbm_destroybdffont(). +-----------------------------------------------------------------------------*/ + + FILE * ifP; + bit ** fontsheet; + int fcols, frows; + struct font * retval; + + ifP = pm_openr(filename); + + fontsheet = pbm_readpbm(ifP, &fcols, &frows); + + if ((fcols - 1) / 16 >= pbm_maxfontwidth() || + (frows - 1) / 12 >= pbm_maxfontheight()) + pm_error("Absurdly large PBM font file: %s", filename); + else if (fcols < 31 || frows < 23) { + /* Need at least one pixel per character, and this is too small to + have that. + */ + pm_error("PBM font file '%s' too small to be a font file: %u x %u. " + "Minimum sensible size is 31 x 23", + filename, fcols, frows); + } + + pm_close(ifP); + + retval = pbm_dissectfont((const bit **)fontsheet, frows, fcols); + return (retval); + +} + + + +struct font2 * +pbm_loadpbmfont2(const char * const filename) { +/*---------------------------------------------------------------------------- + Like pbm_loadpbmfont, but return a pointer to struct font2. + + When done with object, free with pbm_destroybdffont2(). +-----------------------------------------------------------------------------*/ + + const struct font * const pbmfont = pbm_loadpbmfont(filename); + struct font2 * const retval = pbm_expandbdffont(pbmfont); + + free ((void *)pbmfont); + + /* Overwrite some fields */ + + retval->load_fn = LOAD_PBMSHEET; + retval->default_char = (PM_WCHAR) ' '; + retval->default_char_defined = TRUE; + retval->name = strdup("(PBM sheet font has no name)"); + retval->charset = ISO646_1991_IRV; + retval->charset_string = strdup("ASCII"); + retval->total_chars = retval->chars = nCharsInFont; + + return(retval); +} + + + diff --git a/lib/libpbmfont2.c b/lib/libpbmfont2.c new file mode 100644 index 00000000..0ee7169c --- /dev/null +++ b/lib/libpbmfont2.c @@ -0,0 +1,1041 @@ +/* +** +** Font routines. +** +** Wide character stuff written by Akira Urushibata in 2018 and contributed +** to the public domain. +** +** BDF font code by George Phillips, copyright 1993 +** +** 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. +** +** BDF font specs available from: +** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf +** Glyph Bitmap Distribution Format (BDF) Specification +** Version 2.2 +** 22 March 1993 +** Adobe Developer Support +*/ + +#include <assert.h> +#include <string.h> + +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + +#include "pbmfont.h" +#include "pbm.h" + +/*---------------------------------------------------------------------------- + Routines for loading a BDF font file +-----------------------------------------------------------------------------*/ + +/* The following are not recognized in individual glyph data; library + routines do a pm_error if they see one: + + Vertical writing systems: DWIDTH1, SWIDTH1, VVECTOR, METRICSET, + CONTENTVERSION. + + The following is not recognized and is thus ignored at the global level: + DWIDTH +*/ + + +#define MAXBDFLINE 1024 + +/* Official Adobe document says max length of string is 65535 characters. + However the value 1024 is sufficient for practical uses. +*/ + +typedef struct { +/*---------------------------------------------------------------------------- + This is an object for reading lines of a font file. It reads and tokenizes + them into words. +-----------------------------------------------------------------------------*/ + FILE * ifP; + + char line[MAXBDFLINE+1]; + /* 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[7]; + /* These are the words; each entry is a pointer into line[] (above) */ + + unsigned int wordCt; +} Readline; + + + +static void +readline_init(Readline * const readlineP, + FILE * const ifP) { + + readlineP->ifP = ifP; + + readlineP->arg[0] = NULL; + readlineP->wordCt = 0; +} + + + +static void +tokenize(char * const s, + const char ** const words, + unsigned int const wordsSz, + unsigned int * const wordCtP) { +/*---------------------------------------------------------------------------- + Chop up 's' into words by changing space characters to NUL. Return as + 'words' an array of pointers to the beginnings of those words in 's'. + Terminate the words[] list with a null pointer. + + 'wordsSz' is the number of elements of space in 'words'. If there are more + words in 's' than will fit in that space (including the terminating null + pointer), ignore the excess on the right. + + '*wordCtP' is the number elements actually found. +-----------------------------------------------------------------------------*/ + unsigned int n; /* Number of words in words[] so far */ + char * p; + + p = &s[0]; + n = 0; + + while (*p) { + if (!ISGRAPH(*p)) { + if(!ISSPACE(*p)) { + /* Control chars excluding 09 - 0d (space), 80-ff */ + pm_message("Warning: non-ASCII character '%x' in " + "BDF font file", *p); + } + *p++ = '\0'; + } + else { + words[n++] = p; + if (n >= wordsSz - 1) + break; + while (*p && ISGRAPH(*p)) + ++p; + } + } + assert(n <= wordsSz - 1); + words[n] = NULL; + *wordCtP = n; +} + + + +static void +readline_read(Readline * const readlineP, + bool * const eofP) { +/*---------------------------------------------------------------------------- + Read a nonblank line from the file. Make its contents available + as readlineP->arg[]. + + Return *eofP == true iff there is no nonblank line before EOF or we + are unable to read the file. +-----------------------------------------------------------------------------*/ + bool gotLine; + bool error; + + for (gotLine = false, error = false; !gotLine && !error; ) { + char * rc; + + rc = fgets(readlineP->line, MAXBDFLINE+1, readlineP->ifP); + if (rc == NULL) + error = true; + else { + tokenize(readlineP->line, readlineP->arg, + ARRAY_SIZE(readlineP->arg), &readlineP->wordCt); + if (readlineP->arg[0] != NULL) + gotLine = true; + } + } + *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(Readline * const readlineP, + unsigned int const glyphWidth, + unsigned int const glyphHeight, + const char * const charName, + unsigned char * const bmap) { + + int n; + unsigned int bmapIndex; + + bmapIndex = 0; + + for (n = glyphHeight; n > 0; --n) { + bool eof; + const char * error; + + readline_read(readlineP, &eof); + + if (eof) + pm_error("End of file in bitmap for character '%s' in BDF " + "font file.", charName); + + 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); + } + } +} + + + +static void +createBmap(unsigned int const glyphWidth, + unsigned int const glyphHeight, + Readline * const readlineP, + const char * const charName, + const char ** const bmapP) { + + unsigned char * bmap; + bool eof; + + if (glyphWidth > 0 && UINT_MAX / glyphWidth < glyphHeight) + pm_error("Ridiculously large glyph"); + + MALLOCARRAY(bmap, glyphWidth * glyphHeight); + + if (!bmap) + pm_error("no memory for font glyph byte map"); + + readline_read(readlineP, &eof); + if (eof) + pm_error("End of file encountered reading font glyph byte map from " + "BDF font file."); + + if (streq(readlineP->arg[0], "ATTRIBUTES")) { + /* ATTRIBUTES is defined in Glyph Bitmap Distribution Format (BDF) + Specification Version 2.1, but not in Version 2.2. + */ + bool eof; + readline_read(readlineP, &eof); + if (eof) + pm_error("End of file encountered after ATTRIBUTES in BDF " + "font file."); + } + if (!streq(readlineP->arg[0], "BITMAP")) + pm_error("'%s' found where BITMAP expected in definition of " + "character '%s' in BDF font file.", + readlineP->arg[0], charName); + + assert(streq(readlineP->arg[0], "BITMAP")); + + readBitmap(readlineP, glyphWidth, glyphHeight, charName, bmap); + + *bmapP = (char *)bmap; +} + + + +static void +validateWordCount(Readline * const readlineP, + unsigned int const nWords) { + + if( readlineP->wordCt != nWords ) + pm_error("Wrong number of arguments in '%s' line in BDF font file", + readlineP->arg[0]); + + /* We assume that the first word in line 'arg[0]' is a valid string */ + +} + + +static void +readExpectedStatement(Readline * const readlineP, + const char * const expected, + unsigned int const nWords) { +/*---------------------------------------------------------------------------- + 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"). Check for the number + of words: 'nWords'. If either condition is not met, fail the program. +-----------------------------------------------------------------------------*/ + + bool eof; + + readline_read(readlineP, &eof); + + if (eof) + pm_error("EOF in BDF font file where '%s' expected", 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); + + validateWordCount(readlineP, nWords); + +} + + + +static void +skipCharacter(Readline * const readlineP) { +/*---------------------------------------------------------------------------- + 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) { + 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(readlineP->arg[0], "ENDCHAR"); + } +} + + + +static int +wordToInt(const char * const word) { + + unsigned int absValue; + + int retval; + + const char * error; + const int sign = (word[0] == '-') ? -1 : +1; + const char * const absString = (sign == -1) ? &word[1] : word; + /* No leading spaces allowed in 'word' */ + + if (!ISDIGIT(absString[0])) + error = "Non-digit character encountered"; + + else { + pm_string_to_uint(absString, &absValue, &error); + if (error == NULL && absValue > INT_MAX) + error = "Out of range"; + } + + if (error != NULL) + pm_error ("Error reading numerical argument in " + "BDF font file: %s %s %s", error, word, absString); + + retval = sign * absValue; + assert (INT_MIN < retval && retval < INT_MAX); + + return retval; +} + + + +static void +interpEncoding(const char ** const arg, + unsigned int * const codepointP, + bool * const badCodepointP, + PM_WCHAR const maxmaxglyph) { +/*---------------------------------------------------------------------------- + With arg[] being the ENCODING statement from the font, return as + *codepointP the codepoint that it indicates (code point is the character + code, e.g. in ASCII, 48 is '0'). + + But if the statement doesn't give an acceptable codepoint return + *badCodepointP == TRUE. + + 'maxmaxglyph' is the maximum codepoint in the font. +-----------------------------------------------------------------------------*/ + bool gotCodepoint; + bool badCodepoint; + unsigned int codepoint; + + if (wordToInt(arg[1]) >= 0) { + codepoint = wordToInt(arg[1]); + gotCodepoint = true; + } else { + if (wordToInt(arg[1]) == -1 && arg[2] != NULL) { + codepoint = wordToInt(arg[2]); + gotCodepoint = true; + } else + gotCodepoint = false; + } + if (gotCodepoint) { + if (codepoint > maxmaxglyph) + badCodepoint = true; + else + badCodepoint = false; + } else + badCodepoint = true; + + *badCodepointP = badCodepoint; + *codepointP = codepoint; +} + + + +static void +readEncoding(Readline * const readlineP, + unsigned int * const codepointP, + bool * const badCodepointP, + PM_WCHAR const maxmaxglyph) { + + bool eof; + const char * expected = "ENCODING"; + + readline_read(readlineP, &eof); + + if (eof) + pm_error("EOF in BDF font file where '%s' expected", 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); + else if(readlineP->wordCt != 2 && readlineP->wordCt != 3) + pm_error("Wrong number of arguments in '%s' line in BDF font file", + readlineP->arg[0]); + + interpEncoding(readlineP->arg, codepointP, badCodepointP, maxmaxglyph); +} + + + +static void +validateFontLimits(const struct font2 * const font2P) { + + assert(pbm_maxfontheight() > 0 && pbm_maxfontwidth() > 0); + + if (font2P->maxwidth <= 0 || + font2P->maxheight <= 0 || + font2P->maxwidth > pbm_maxfontwidth() || + font2P->maxheight > pbm_maxfontheight() || + -font2P->x + 1 > font2P->maxwidth || + -font2P->y + 1 > font2P->maxheight || + font2P->x > font2P->maxwidth || + font2P->y > font2P->maxheight || + font2P->x + font2P->maxwidth > pbm_maxfontwidth() || + font2P->y + font2P->maxheight > pbm_maxfontheight() + ) { + + pm_error("Global font metric(s) out of bounds."); + } + + if (font2P->maxglyph > PM_FONT2_MAXGLYPH) + pm_error("Internal error. Glyph table too large: %u glyphs; " + "Maximum possible in Netpbm is %u", + (unsigned int) font2P->maxglyph, PM_FONT2_MAXGLYPH); +} + + + +static void +validateGlyphLimits(const struct font2 * const font2P, + const struct glyph * const glyphP, + const char * const charName) { + + /* Some BDF files code space with zero width and height, + no bitmap data and just the xadd value. + We allow zero width and height, iff both are zero. + + Some BDF files have individual glyphs with a BBX value which + exceeds the global maximum stated by FONTBOUNDINGBOX. + Abort with error when this is encountered. + It seems some programs including emacs and bdftopcf tolerate + this violation. + */ + + if (((glyphP->width == 0 || glyphP->height == 0) && + !(glyphP->width == 0 && glyphP->height == 0)) || + glyphP->width > font2P->maxwidth || + glyphP->height > font2P->maxheight || + glyphP->x < font2P->x || + glyphP->y < font2P->y || + glyphP->x + (int) glyphP->width > font2P->x + font2P->maxwidth || + glyphP->y + (int) glyphP->height > font2P->y + font2P->maxheight || + glyphP->xadd > pbm_maxfontwidth() || + glyphP->xadd + MAX(glyphP->x,0) + (int) glyphP->width > + pbm_maxfontwidth() + ) { + + pm_error("Font metric(s) for char '%s' out of bounds.\n", charName); + } +} + + + +static void +processChars(Readline * const readlineP, + struct font2 * const font2P) { +/*---------------------------------------------------------------------------- + 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 *font2P. +-----------------------------------------------------------------------------*/ + unsigned int const nCharacters = wordToInt(readlineP->arg[1]); + + unsigned int nCharsDone; + unsigned int nCharsValid; + + for (nCharsDone = 0, nCharsValid = 0; + nCharsDone < nCharacters; ) { + + bool eof; + + readline_read(readlineP, &eof); + if (eof) + pm_error("End of file after CHARS reading BDF font file"); + + if (streq(readlineP->arg[0], "COMMENT")) { + /* ignore */ + } else if (!streq(readlineP->arg[0], "STARTCHAR")) + pm_error("%s detected where \'STARTCHAR\' expected " + "in BDF font file", readlineP->arg[0] ); + else { + const char * charName; + + struct glyph * glyphP; + unsigned int codepoint; + bool badCodepoint; + + if (readlineP->wordCt < 2) + pm_error("Wrong number of arguments in STARTCHAR line " + "in BDF font file"); + /* Character name may contain spaces: there may be more than + three words in the line. + */ + charName = pm_strdup(readlineP->arg[1]); + + assert(streq(readlineP->arg[0], "STARTCHAR")); + + MALLOCVAR(glyphP); + + if (glyphP == NULL) + pm_error("no memory for font glyph for '%s' character", + charName); + + readEncoding(readlineP, &codepoint, &badCodepoint, + font2P->maxmaxglyph); + + if (badCodepoint) + skipCharacter(readlineP); + else { + if (codepoint < font2P->maxglyph) { + if (font2P->glyph[codepoint] != NULL) + pm_error("Multiple definition of code point %u " + "in BDF font file", (unsigned int) codepoint); + else + pm_message("Reverse order detected in BDF file. " + "Code point %u defined after %u", + (unsigned int) codepoint, + (unsigned int) font2P->maxglyph); + } else { + /* Initialize all characters in the gap to nonexistent */ + unsigned int i; + unsigned int const oldMaxglyph = font2P->maxglyph; + unsigned int const newMaxglyph = codepoint; + + for (i = oldMaxglyph + 1; i < newMaxglyph; ++i) + font2P->glyph[i] = NULL; + + font2P->maxglyph = newMaxglyph; + } + + readExpectedStatement(readlineP, "SWIDTH", 3); + + readExpectedStatement(readlineP, "DWIDTH", 3); + glyphP->xadd = wordToInt(readlineP->arg[1]); + + readExpectedStatement(readlineP, "BBX", 5); + glyphP->width = wordToInt(readlineP->arg[1]); + glyphP->height = wordToInt(readlineP->arg[2]); + glyphP->x = wordToInt(readlineP->arg[3]); + glyphP->y = wordToInt(readlineP->arg[4]); + + validateGlyphLimits(font2P, glyphP, charName); + + createBmap(glyphP->width, glyphP->height, readlineP, charName, + &glyphP->bmap); + + readExpectedStatement(readlineP, "ENDCHAR", 1); + + assert(codepoint <= font2P->maxmaxglyph); + /* Ensured by readEncoding() */ + + font2P->glyph[codepoint] = glyphP; + pm_strfree(charName); + + ++nCharsValid; + } + ++nCharsDone; + } + } + font2P->chars = nCharsValid; + font2P->total_chars = nCharacters; +} + + + +static void +processBdfFontNameLine(Readline * const readlineP, + struct font2 * const font2P) { + + if (font2P->name != NULL) + pm_error("Multiple FONT lines in BDF font file"); + + font2P->name = malloc (MAXBDFLINE+1); + if (font2P->name == NULL) + pm_error("No memory for font name"); + + if (readlineP->wordCt == 1) + strcpy(font2P->name, "(no name)"); + + else { + unsigned int tokenCt; + + font2P->name[0] ='\0'; + + for (tokenCt=1; + tokenCt < ARRAY_SIZE(readlineP->arg) && + readlineP->arg[tokenCt] != NULL; ++tokenCt) { + strcat(font2P->name, " "); + strcat(font2P->name, readlineP->arg[tokenCt]); + } + } +} + + +static void +loadCharsetString(const char * const registry, + const char * const encoding, + char ** const string) { + + unsigned int inCt, outCt; + char * const dest = malloc (strlen(registry) + strlen(encoding) + 1); + if (dest == NULL) + pm_error("no memory to load CHARSET_REGISTRY and CHARSET_ENCODING " + "from BDF file"); + + for (inCt = outCt = 0; inCt < strlen(registry); ++inCt) { + char const c = registry[inCt]; + if (isgraph(c) && c != '"') + dest[outCt++] = c; + } + dest[outCt++] = '-'; + + for (inCt = 0; inCt < strlen(encoding); ++inCt) { + char const c = encoding[inCt]; + if (isgraph(c) && c != '"') + dest[outCt++] = c; + } + + dest[outCt] = '\0'; + *string = dest; +} + + + + +static unsigned int const maxTokenLen = 60; + + + +static void +doCharsetRegistry(Readline * const readlineP, + bool * const gotRegistryP, + const char ** const registryP) { + + if (*gotRegistryP) + pm_error("Multiple CHARSET_REGISTRY lines in BDF font file"); + else if (readlineP->arg[2] != NULL) + pm_message("CHARSET_REGISTRY in BDF font file is not " + "a single word. Ignoring extra element(s) %s ...", + readlineP->arg[2]); + else if (strlen(readlineP->arg[1]) > maxTokenLen) + pm_message("CHARSET_REGISTRY in BDF font file is too long. " + "Truncating"); + + *registryP = strndup(readlineP->arg[1], maxTokenLen); + *gotRegistryP = true; +} + + + +static void +doCharsetEncoding(Readline * const readlineP, + bool * const gotEncodingP, + const char ** const encodingP) { + + if (*gotEncodingP) + pm_error("Multiple CHARSET_ENCODING lines in BDF font file"); + else if (readlineP->arg[2] != NULL) + pm_message("CHARSET_ENCODING in BDF font file is not " + "a single word. Ignoring extra element(s) %s ...", + readlineP->arg[2]); + else if (strlen(readlineP->arg[1]) > maxTokenLen) + pm_message("CHARSET_ENCODING in BDF font file is too long. " + "Truncating"); + + *encodingP = strndup(readlineP->arg[1], maxTokenLen); + *gotEncodingP = true; +} + + + +static void +doDefaultChar(Readline * const readlineP, + bool * const gotDefaultCharP, + PM_WCHAR * const defaultCharP) { + + if (*gotDefaultCharP) + pm_error("Multiple DEFAULT_CHAR lines in BDF font file"); + else if (readlineP->arg[1] == NULL) + pm_error("Malformed DEFAULT_CHAR line in BDF font file"); + else { + *defaultCharP = (PM_WCHAR) wordToInt(readlineP->arg[1]); + *gotDefaultCharP = true; + } +} + + + +static void +processBdfPropertyLine(Readline * const readlineP, + struct font2 * const font2P) { + + bool gotRegistry; + const char * registry; + bool gotEncoding; + const char * encoding; + bool gotDefaultChar; + PM_WCHAR defaultChar; + unsigned int propCt; + unsigned int commentCt; + unsigned int propTotal; + + validateWordCount(readlineP, 2); /* STARTPROPERTIES n */ + + propTotal = wordToInt(readlineP->arg[1]); + + gotRegistry = false; /* initial value */ + gotEncoding = false; /* initial value */ + gotDefaultChar = false; /* initial value */ + + propCt = 0; /* initial value */ + commentCt = 0; /* initial value */ + + do { + bool eof; + + readline_read(readlineP, &eof); + if (eof) + pm_error("End of file after STARTPROPERTIES in BDF font file"); + else if (streq(readlineP->arg[0], "CHARSET_REGISTRY") && + readlineP->arg[1] != NULL) { + doCharsetRegistry(readlineP, &gotRegistry, ®istry); + } else if (streq(readlineP->arg[0], "CHARSET_ENCODING") && + readlineP->arg[1] != NULL) { + doCharsetEncoding(readlineP, &gotEncoding, &encoding); + } else if (streq(readlineP->arg[0], "DEFAULT_CHAR")) { + doDefaultChar(readlineP, &gotDefaultChar, &defaultChar); + } else if (streq(readlineP->arg[0], "COMMENT")) { + ++commentCt; + } + ++propCt; + + } while (!streq(readlineP->arg[0], "ENDPROPERTIES")); + + --propCt; /* Subtract one for ENDPROPERTIES line */ + + if (propCt != propTotal && propCt - commentCt != propTotal) + /* Some BDF files have COMMENTs in the property section and leave + them out of the count. + Others just give a wrong count. + */ + pm_message ("Note: wrong number of property lines in BDF font file. " + "STARTPROPERTIES line says %u, actual count: %u. " + "Proceeding.", + propTotal, propCt); + + + if (gotRegistry && gotEncoding) + loadCharsetString(registry, encoding, &font2P->charset_string); + else if (gotRegistry != gotEncoding) { + pm_message ("CHARSET_%s absent or incomplete in BDF font file. " + "Ignoring CHARSET_%s.", + gotEncoding ? "REGISTRY" : "ENCODING", + gotEncoding ? "ENCODING" : "REGISTRY"); + } + if (gotRegistry) + pm_strfree(registry); + if (gotEncoding) + pm_strfree(encoding); + + if (gotDefaultChar) { + font2P->default_char = defaultChar; + font2P->default_char_defined = true; + } + +} + + +static void +processBdfFontLine(Readline * const readlineP, + struct font2 * const font2P, + 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 */ + + assert(readlineP->arg[0] != NULL); /* Entry condition */ + + if (streq(readlineP->arg[0], "FONT")) { + processBdfFontNameLine(readlineP, font2P); + } else if (streq(readlineP->arg[0], "COMMENT")) { + /* ignore */ + } else if (streq(readlineP->arg[0], "SIZE")) { + /* ignore */ + } else if (streq(readlineP->arg[0], "STARTPROPERTIES")) { + if (font2P->maxwidth == 0) + pm_error("Encountered STARTROPERTIES before FONTBOUNDINGBOX " + "in BDF font file"); + else + processBdfPropertyLine(readlineP, font2P); + } else if (streq(readlineP->arg[0], "FONTBOUNDINGBOX")) { + validateWordCount(readlineP,5); + + font2P->maxwidth = wordToInt(readlineP->arg[1]); + font2P->maxheight = wordToInt(readlineP->arg[2]); + font2P->x = wordToInt(readlineP->arg[3]); + font2P->y = wordToInt(readlineP->arg[4]); + validateFontLimits(font2P); + } else if (streq(readlineP->arg[0], "ENDFONT")) { + *endOfFontP = true; + } else if (streq(readlineP->arg[0], "CHARS")) { + if (font2P->maxwidth == 0) + pm_error("Encountered CHARS before FONTBOUNDINGBOX " + "in BDF font file"); + else { + validateWordCount(readlineP, 2); /* CHARS n */ + processChars(readlineP, font2P); + } + } else { + /* ignore */ + } + +} + + + +struct font2 * +pbm_loadbdffont2(const char * const filename, + PM_WCHAR const maxmaxglyph) { +/*---------------------------------------------------------------------------- + Read a BDF font file "filename" as a 'font2' structure. A 'font2' + structure is more expressive than a 'font' structure, most notably in that + it can handle wide code points and many more glyphs. + + Codepoints up to maxmaxglyph inclusive are valid in the file. + + The returned object is in new malloc'ed storage, in many pieces. + When done with, destroy with pbm_destroybdffont2(). +-----------------------------------------------------------------------------*/ + + FILE * ifP; + Readline readline; + struct font2 * font2P; + bool endOfFont; + + ifP = fopen(filename, "rb"); + if (!ifP) + pm_error("Unable to open BDF font file name '%s'. errno=%d (%s)", + filename, errno, strerror(errno)); + + readline_init(&readline, ifP); + + pbm_createbdffont2_base(&font2P, maxmaxglyph); + + font2P->maxglyph = 0; + /* Initial value. Increases as new characters are loaded */ + font2P->glyph[0] = NULL; + /* Initial value. Overwrite later if codepoint 0 is defined. */ + + font2P->maxmaxglyph = maxmaxglyph; + + /* Initialize some values - to be overwritten if actual values are + stated in BDF file */ + font2P->maxwidth = font2P->maxheight = font2P->x = font2P->y = 0; + font2P->name = font2P->charset_string = NULL; + font2P->chars = font2P->total_chars = 0; + font2P->default_char = 0; + font2P->default_char_defined = FALSE; + + readExpectedStatement(&readline, "STARTFONT", 2); + + endOfFont = FALSE; + + while (!endOfFont) { + bool eof; + readline_read(&readline, &eof); + if (eof) + pm_error("End of file before ENDFONT statement in BDF font file"); + + processBdfFontLine(&readline, font2P, &endOfFont); + } + fclose(ifP); + + if(font2P->chars == 0) + pm_error("No glyphs found in BDF font file " + "in codepoint range 0 - %u", (unsigned int) maxmaxglyph); + + REALLOCARRAY(font2P->glyph, font2P->maxglyph + 1); + + font2P->bit_format = PBM_FORMAT; + font2P->load_fn = LOAD_BDFFILE; + font2P->charset = ENCODING_UNKNOWN; + font2P->oldfont = NULL; /* Legacy field */ + font2P->fcols = font2P->frows = 0; /* Legacy fields */ + + return font2P; +} + + +static struct font * +font2ToFont(const struct font2 * const font2P) { + struct font * fontP; + unsigned int codePoint; + + MALLOCVAR(fontP); + if (fontP == NULL) + pm_error("no memory for font"); + + fontP->maxwidth = font2P->maxwidth; + fontP->maxheight = font2P->maxheight; + + fontP->x = font2P->x; + fontP->y = font2P->y; + + for (codePoint = 0; codePoint <= font2P->maxglyph; ++codePoint) + fontP->glyph[codePoint] = font2P->glyph[codePoint]; + + /* font2P->maxglyph is typically 255 (PM_FONT_MAXGLYPH) or larger. + But in some rare cases it is smaller. + If an ASCII-only font is read, it will be 126 or 127. + + Set remaining codepoints up to PM_FONT_MAXGLYPH, if any, to NULL + */ + + for ( ; codePoint <= PM_FONT_MAXGLYPH; ++codePoint) + fontP->glyph[codePoint] = NULL; + + /* Give values to legacy fields */ + fontP->oldfont = font2P->oldfont; + fontP->fcols = font2P->fcols; + fontP->frows = font2P->frows; + + return fontP; +} + + + +struct font * +pbm_loadbdffont(const char * const filename) { +/*---------------------------------------------------------------------------- + Read a BDF font file "filename" into a traditional font structure. + + Codepoints up to 255 (PM_FONT_MAXGLYPH) are valid. + + Can handle ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-15, etc. + + The returned object is in new malloc'ed storage, in many pieces. + Destroy with pbm_destroybdffont(). +-----------------------------------------------------------------------------*/ + struct font * fontP; + struct font2 * const font2P = pbm_loadbdffont2(filename, PM_FONT_MAXGLYPH); + + fontP = font2ToFont(font2P); + + /* Free the base structure which was created by pbm_loadbdffont2() */ + pbm_destroybdffont2_base(font2P); + + return fontP; +} + + + diff --git a/lib/libpbmfontdump.c b/lib/libpbmfontdump.c new file mode 100644 index 00000000..f0c950f7 --- /dev/null +++ b/lib/libpbmfontdump.c @@ -0,0 +1,96 @@ +/* +** +** Font routines. +** +** BDF font code Copyright 1993 by George Phillips. +** +** Copyright (C) 1991 by Jef Poskanzer. +** +** 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. +** +** BDF font specs available from: +** https://partners.adobe.com/public/developer/en/font/5005.BDF_Spec.pdf +** Glyph Bitmap Distribution Format (BDF) Specification +** Version 2.2 +** 22 March 1993 +** Adobe Developer Support +*/ + +#include <assert.h> +#include <string.h> +#include <ctype.h> + +#include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" + +#include "pbmfont.h" +#include "pbm.h" + + +void +pbm_dumpfont(struct font * const fontP, + FILE * const ofP) { +/*---------------------------------------------------------------------------- + Dump out font as C source code. +-----------------------------------------------------------------------------*/ + unsigned int i; + unsigned int ng; + + if (fontP->oldfont) + pm_message("Netpbm no longer has the capability to generate " + "a font in long hexadecimal data format"); + + for (i = 0, ng = 0; i < PM_FONT_MAXGLYPH +1; ++i) { + if (fontP->glyph[i]) + ++ng; + } + + printf("static struct glyph _g[%d] = {\n", ng); + + for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) { + struct glyph * const glyphP = fontP->glyph[i]; + if (glyphP) { + unsigned int j; + printf(" { %d, %d, %d, %d, %d, \"", glyphP->width, glyphP->height, + glyphP->x, glyphP->y, glyphP->xadd); + + for (j = 0; j < glyphP->width * glyphP->height; ++j) { + if (glyphP->bmap[j]) + printf("\\1"); + else + printf("\\0"); + } + --ng; + printf("\" }%s\n", ng ? "," : ""); + } + } + printf("};\n"); + + printf("struct font XXX_font = { %d, %d, %d, %d, {\n", + fontP->maxwidth, fontP->maxheight, fontP->x, fontP->y); + + { + unsigned int i; + + for (i = 0; i < PM_FONT_MAXGLYPH + 1; ++i) { + if (fontP->glyph[i]) + printf(" _g + %d", ng++); + else + printf(" NULL"); + + if (i != PM_FONT_MAXGLYPH) printf(","); + printf("\n"); + } + } + + printf(" }\n};\n"); +} + + + diff --git a/lib/pam.h b/lib/pam.h index c2cfb4c7..74b20f46 100644 --- a/lib/pam.h +++ b/lib/pam.h @@ -482,6 +482,18 @@ void pnm_unapplyopacityrown(struct pam * const pamP, tuplen * const tuplenrow); +void +pnm_maketuplergbn(const struct pam * const pamP, + tuplen const tuple); + +void +pnm_makerowrgbn(const struct pam * const pamP, + tuplen * const tuplerow); + +void +pnm_makearrayrgbn(const struct pam * const pamP, + tuplen ** const tuples); + pnm_transformMap * pnm_creategammatransform(const struct pam * const pamP); diff --git a/lib/pbmfont.h b/lib/pbmfont.h index ad5d3acf..57f19ddc 100644 --- a/lib/pbmfont.h +++ b/lib/pbmfont.h @@ -13,11 +13,14 @@ extern "C" { /* Maximum dimensions for fonts */ -#define pbm_maxfontwidth() 65536 -#define pbm_maxfontheight() 65536 +#define pbm_maxfontwidth() 65535 +#define pbm_maxfontheight() 65535 /* These limits are not in the official Adobe BDF definition, but should never be a problem for practical purposes, considering that - a 65536 x 65536 glyph occupies 4G pixels. + a 65536 x 65536 glyph occupies 4G pixels. + + Note that the maximum line length allowed in a BDF file imposes + another restriction. */ typedef wchar_t PM_WCHAR; @@ -38,6 +41,39 @@ typedef wchar_t PM_WCHAR; As of Unicode v. 11.0.0 planes up to 16 are defined. */ +enum pbmFontLoad { FIXED_DATA = 0, + LOAD_PBMSHEET = 1, + LOAD_BDFFILE = 2, + CONVERTED_TYPE1_FONT = 9 }; + +static const char * const pbmFontOrigin[10] = + {"Fixed data", /* 0 */ + "Loaded from PBM sheet by libnetpbm", /* 1 */ + "Loaded from BDF file by libnetpbm", /* 2 */ + NULL, NULL, NULL, NULL, NULL, NULL, + "Expanded from type 1 font structure by libnetpbm"}; /* 9 */ + +enum pbmFontEncoding { ENCODING_UNKNOWN = 0, + ISO646_1991_IRV = 1, /* ASCII */ + ISO_8859_1 = 1000, ISO_8859_2, ISO_8859_3, ISO_8859_4, + ISO_8859_5, ISO_8859_6, ISO_8859_7, ISO_8859_8, + ISO_8859_9, ISO_8859_10, ISO_8859_11, ISO_8859_12, + ISO_8859_13, ISO_8859_14, ISO_8859_15, ISO_8859_16, + ISO_10646 = 2000 }; + +/* For future use */ + +/* In addition to the above, the following CHARSET_REGISTRY-CHARSET_ENCODING + values have been observed in actual BDF files: + + ADOBE-FONTSPECIFIC, DEC-DECTECH, GOST19768.74-1, IS13194-DEVANAGARI, + JISX0201.1976-0, KOI8-C, KOI8-R, MISC-FONTSPECIFIC, + MULEARABIC-0, MULEARABIC-1, MULEARABIC-2, MULEIPA-1, MULELAO-1, + OMRON_UDC_ZH-0, TIS620.2529-0, TIS620.2529-1, VISCII1-1, VISCII1.1-1, + XTIS-0 + */ + + struct glyph { /* A glyph consists of white borders and the "central glyph" which can be anything, but normally does not have white borders because @@ -69,9 +105,14 @@ struct glyph { the top half and white on the bottom, this is an array of 800 bytes, with the first 400 having value 0x01 and the last 400 having value 0x00. + + Do not share bmap objects among glyphs if using + pbm_destroybdffont() or pbm_destroybdffont2() to free + the font/font2 structure. */ }; + struct font { /* This describes a combination of font and character set. Given an code point in the range 0..255, this structure describes the @@ -86,7 +127,9 @@ struct font { this font. Can be negative. */ struct glyph * glyph[256]; - /* glyph[i] is the glyph for code point i */ + /* glyph[i] is the glyph for code point i. + Glyph objects must be unique for pbm_destroybdffont() to work. + */ const bit ** oldfont; /* for compatibility with old pbmtext routines */ /* oldfont is NULL if the font is BDF derived */ @@ -95,34 +138,162 @@ struct font { struct font2 { - /* Font structure for expanded character set. Code point is in the - range 0..maxglyph . + /* Font structure for expanded character set. + Code points in the range 0...maxmaxglyph are loaded. + Loaded code point is in the range 0..maxglyph . + */ + + /* 'size' and 'len' are necessary in order to provide forward and + backward compatibility between library functions and calling programs + as this structure grows. See struct pam in pam.h. */ + unsigned int size; + /* The storage size of this entire structure, in bytes */ + + unsigned int len; + /* The length, in bytes, of the information in this structure. + The information starts in the first byte and is contiguous. + This cannot be greater than 'size' + */ + int maxwidth, maxheight; int x; - /* The minimum value of glyph.font. The left edge of the glyph - in the glyph set which advances furthest to the left. */ + /* The minimum value of glyph.font. The left edge of the glyph in + the glyph set which advances furthest to the left. + */ int y; - /* Amount of white space that should be added between lines of - this font. Can be negative. + /* Amount of white space that should be added between lines of this + font. Can be negative. */ + struct glyph ** glyph; - /* glyph[i] is the glyph for code point i */ + /* glyph[i] is the glyph for code point i + + Glyph objects must be unique for pbm_destroybdffont2() to work. + For example space and non-break-space are often identical at the + image data level; they must be loaded into separate memory + locations if using pbm_destroybdffont2(). + */ PM_WCHAR maxglyph; - /* max code point for glyphs, including vacant slots */ + /* max code point for glyphs, including vacant slots max value of + above i + */ + + void * selector; + /* Reserved + + Bit array or structure indicating which code points to load. + + When NULL, all available code points up to maxmaxglyph, inclusive + are loaded. + */ + + PM_WCHAR maxmaxglyph; + /* Code points above this value are not loaded, even if they occur + in the BDF font file + */ const bit ** oldfont; - /* for compatibility with old pbmtext routines */ - /* oldfont is NULL if the font is BDF derived */ + /* For compatibility with old pbmtext routines. + Valid only when data is in the form of a PBM sheet + */ unsigned int fcols, frows; + /* For compatibility with old pbmtext routines. + Valid only when oldfont is non-NULL + */ + + unsigned int bit_format; + /* PBM_FORMAT: glyph data: 1 byte per pixel (like P1, but not ASCII) + RPBM_FORMAT: glyph data: 1 bit per pixel + Currently only PBM_FORMAT is possible + */ + + unsigned int total_chars; + /* Number of glyphs defined in font file, as stated in the CHARS line + of the BDF file PBM sheet font. Always 96 + */ + + unsigned int chars; + /* Number of glyphs actually loaded into structure + + Maximum: total_chars + + Less than total_chars when a subset of the file is loaded + PBM sheet font: always 96 */ + + enum pbmFontLoad load_fn; + /* Description of the function that created the structure and loaded + the glyph data + + Used to choose a string to show in verbose messages. + + FIXED_DATA (==0) means memory for this structure was not + dynamically allocated by a function; all data is hardcoded in + source code and resides in static data. See file pbmfontdata1.c + */ + + PM_WCHAR default_char; + /* Code index of what to show when there is no glyph for a requested + code Available in many BDF fonts between STARPROPERTIES - + ENDPROPERTIES. + + Set to value read from BDF font file. + + Common values are 0, 32, 8481, 32382, 33, 159, 255. + */ + + unsigned int default_char_defined; + /* boolean + TRUE: above field is valid; DEFAULT_CHAR is defined in font file. + FALSE: font file has no DEFAULT_CHAR field. + */ + + char * name; + /* Name of the font. Available in BDF fonts. + NULL means no name. + */ + + enum pbmFontEncoding charset; + /* Reserved for future use. + Set by analyzing following charset_string. + */ + + char * charset_string; + /* Charset registry and encoding. + Available in most BDF fonts between STARPROPERTIES - ENDPROPERTIES. + NULL means no name. + */ }; + +/* PBM_FONT2_STRUCT_SIZE(x) tells you how big a struct font2 is up + through the member named x. This is useful in conjunction with the + 'len' value to determine which fields are present in the structure. +*/ + +/* Some compilers are really vigilant and recognize it as an error + to cast a 64 bit address to a 32 bit type. Hence the roundabout + casting. See PAM_MEMBER_OFFSET in pam.h . +*/ + + +#define PBM_FONT2_MEMBER_OFFSET(mbrname) \ + ((size_t)(unsigned long)(char*)&((struct font2 *)0)->mbrname) +#define PBM_FONT2_MEMBER_SIZE(mbrname) \ + sizeof(((struct font2 *)0)->mbrname) +#define PBM_FONT2_STRUCT_SIZE(mbrname) \ + (PBM_FONT2_MEMBER_OFFSET(mbrname) + PBM_FONT2_MEMBER_SIZE(mbrname)) + + struct font * pbm_defaultfont(const char* const which); +struct font2 * +pbm_defaultfont2(const char* const which); + struct font * pbm_dissectfont(const bit ** const font, unsigned int const frows, @@ -131,15 +302,40 @@ pbm_dissectfont(const bit ** const font, struct font * pbm_loadfont(const char * const filename); +struct font2 * +pbm_loadfont2(const char * const filename, + PM_WCHAR const maxmaxglyph); + struct font * pbm_loadpbmfont(const char * const filename); +struct font2 * +pbm_loadpbmfont2(const char * const filename); + struct font * pbm_loadbdffont(const char * const filename); struct font2 * pbm_loadbdffont2(const char * const filename, - PM_WCHAR const maxglyph); + PM_WCHAR const maxmaxglyph); + +struct font2 * +pbm_loadbdffont2_select(const char * const filename, + PM_WCHAR const maxmaxglyph, + const void * const selector); + +void +pbm_createbdffont2_base(struct font2 ** const font2P, + PM_WCHAR const maxmaxglyph); + +void +pbm_destroybdffont(struct font * const fontP); + +void +pbm_destroybdffont2_base(struct font2 * const font2P); + +void +pbm_destroybdffont2(struct font2 * const font2P); struct font2 * pbm_expandbdffont(const struct font * const font); @@ -148,9 +344,6 @@ void pbm_dumpfont(struct font * const fontP, FILE * const ofP); -extern struct font pbm_defaultFixedfont; -extern struct font pbm_defaultBdffont; - #ifdef __cplusplus } #endif diff --git a/lib/pbmfontdata.h b/lib/pbmfontdata.h new file mode 100644 index 00000000..7ac63abc --- /dev/null +++ b/lib/pbmfontdata.h @@ -0,0 +1,7 @@ +extern struct font pbm_defaultFixedfont; +extern struct font pbm_defaultBdffont; + +extern struct font2 const pbm_defaultFixedfont2; +extern struct font2 const pbm_defaultBdffont2; + +extern struct font2 const * pbm_builtinFonts[]; diff --git a/lib/pbmfontdata0.c b/lib/pbmfontdata0.c new file mode 100644 index 00000000..dfafc317 --- /dev/null +++ b/lib/pbmfontdata0.c @@ -0,0 +1,9 @@ +#include "pbm.h" +#include "pbmfont.h" +#include "pbmfontdata.h" + +struct font2 const * pbm_builtinFonts[] = { + &pbm_defaultFixedfont2, + &pbm_defaultBdffont2, + NULL, +}; diff --git a/lib/pbmfontdata1.c b/lib/pbmfontdata1.c index 8552d29e..ab6ce28d 100644 --- a/lib/pbmfontdata1.c +++ b/lib/pbmfontdata1.c @@ -1,4 +1,5 @@ #include "pbmfont.h" +#include "pbmfontdata.h" /* Default fixed-width font All glyphs fit into a 7 x 12 rectangular cell. @@ -20,7 +21,7 @@ */ static struct glyph glFxd[96] = { -/* 32 character */ +/* 32 character */ {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" }, /* 33 character ! */ {7,12,0,0,7,"\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" }, @@ -210,9 +211,9 @@ static struct glyph glFxd[96] = { {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\0\1\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0" }, /* 126 character ~ */ {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\1\0\0\1\0\1\0\1\0\0\1\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" }, -/* 127 character (?) */ +/* 127 character (Control character, retained for backward compatibility) */ {7,12,0,0,7,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" } -}; +}; @@ -246,7 +247,22 @@ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, NULL, 0, 0 +}; + +struct font2 const pbm_defaultFixedfont2 = { + sizeof(pbm_defaultFixedfont2), /* len */ + PBM_FONT2_STRUCT_SIZE(charset_string), /* size */ + 7, 12, 0, 0, /* maxwidth, maxheight, x, y */ + pbm_defaultFixedfont.glyph, /* glyph table */ + 255, NULL, 255, /* maxglyph, selector, maxmaxglyph */ + NULL, 0, 0, /* oldfont, fcols, frows */ + PBM_FORMAT, /* bit_format */ + 96, 96, /* total_chars, chars */ + FIXED_DATA, /* load_fn */ + 32, 1, /* default_char, default_char_defined */ + (char *) "builtin fixed", /* name */ + ISO646_1991_IRV, (char *)"ASCII" /* charset, charset_string */ }; diff --git a/lib/pbmfontdata2.c b/lib/pbmfontdata2.c index 336fc773..11dd84e6 100644 --- a/lib/pbmfontdata2.c +++ b/lib/pbmfontdata2.c @@ -1,4 +1,5 @@ #include "pbmfont.h" +#include "pbmfontdata.h" /* Default proportional font. BDF-style advance value, bounding box dimensions and point of origin. @@ -15,7 +16,7 @@ from a libnetpbm font file or builtin font. */ -static struct glyph glBdf[190] = { +static struct glyph glBdf[191] = { /* 32 character */ { 1, 1, 0, 0, 3, "\0" }, /* 33 character ! */ @@ -204,8 +205,10 @@ static struct glyph glBdf[190] = { { 1, 9, 1, 0, 3, "\1\1\1\1\1\1\1\1\1" }, /* 125 character } */ { 4, 12, 0, -3, 6, "\1\1\0\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\0\1\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\1\0\1\1\0\0" }, -/* 160 */ +/* 126 character ~ */ { 6, 2, 0, 3, 7, "\0\1\1\0\0\1\1\0\0\1\1\0" }, +/* 160 */ +{ 1, 1, 0, 0, 3, "\0" }, /* 161 */ { 1, 9, 1, -3, 4, "\1\0\1\1\1\1\1\1\1" }, /* 162 */ @@ -421,7 +424,7 @@ glBdf+84, glBdf+85, glBdf+86, glBdf+87, glBdf+88, glBdf+89, glBdf+90, glBdf+91, glBdf+92, glBdf+93, glBdf+94, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, glBdf+95, glBdf+96, glBdf+97, glBdf+98, glBdf+99, glBdf+100, glBdf+101, glBdf+102, glBdf+103, glBdf+104, glBdf+105, glBdf+106, glBdf+107, glBdf+108, glBdf+109, glBdf+110, glBdf+111, glBdf+112, @@ -437,7 +440,23 @@ glBdf+161, glBdf+162, glBdf+163, glBdf+164, glBdf+165, glBdf+166, glBdf+167, glBdf+168, glBdf+169, glBdf+170, glBdf+171, glBdf+172, glBdf+173, glBdf+174, glBdf+175, glBdf+176, glBdf+177, glBdf+178, glBdf+179, glBdf+180, glBdf+181, glBdf+182, glBdf+183, glBdf+184, -glBdf+185, glBdf+186, glBdf+187, glBdf+188, glBdf+189 } +glBdf+185, glBdf+186, glBdf+187, glBdf+188, glBdf+189, glBdf+190 }, +NULL, 0, 0 +}; + +struct font2 const pbm_defaultBdffont2 = { + sizeof(pbm_defaultFixedfont2), /* len */ + PBM_FONT2_STRUCT_SIZE(charset_string), /* size */ + 14, 15, -1, -3, /* maxwidth, maxheight, x, y */ + pbm_defaultBdffont.glyph, /* glyph table */ + 255, NULL, 255, /* maxglyph, selector, maxmaxglyph */ + NULL, 0, 0, /* oldfont, fcols, frows */ + PBM_FORMAT, /* bit_format */ + 190, 190, /* total_chars, chars */ + FIXED_DATA, /* load_fn */ + 32, 1, /* default_char, default_char_defined */ + (char *) "builtin bdf", /* name */ + ISO_8859_1, (char *)"ISO8859-1" /* charset, charset_string */ }; diff --git a/test/Makefile b/test/Makefile index 4f0c063f..c640dfff 100644 --- a/test/Makefile +++ b/test/Makefile @@ -31,4 +31,4 @@ include $(SRCDIR)/common.mk distclean clean: cleanlocal .PHONY: cleanlocal cleanlocal: - rm -f $(PROGS) $(OKSTOGENERATE) + rm -f $(PROGS) $(patsubst %.rand-ok,%.ok,$(wildcard *.rand-ok)) diff --git a/test/all-in-place.ok b/test/all-in-place.ok index b9db6ee1..81eaa320 100644 --- a/test/all-in-place.ok +++ b/test/all-in-place.ok @@ -36,6 +36,7 @@ mtvtoppm: ok neotoppm: ok palmtopnm: ok pamaddnoise: ok +pamaltsat: ok pamarith: ok pambackground: ok pambayer: ok @@ -56,7 +57,9 @@ pamfix: ok pamflip: ok pamfunc: ok pamgauss: ok +pamgetcolor: ok pamgradient: ok +pamlevels: ok pamlookup: ok pammasksharpen: ok pammixinterlace: ok @@ -106,6 +109,7 @@ pamtotiff: ok pamtouil: ok pamtowinicon: ok pamtoxvmini: ok +pamtris: ok pamundice: ok pamunlookup: ok pamvalidate: ok diff --git a/test/all-in-place.test b/test/all-in-place.test index a52739ed..cf402b6f 100755 --- a/test/all-in-place.test +++ b/test/all-in-place.test @@ -78,6 +78,7 @@ ordinary_testprogs="\ neotoppm \ palmtopnm \ pamaddnoise \ + pamaltsat \ pamarith \ pambackground \ pambayer \ @@ -98,7 +99,9 @@ ordinary_testprogs="\ pamflip \ pamfunc \ pamgauss \ + pamgetcolor \ pamgradient \ + pamlevels \ pamlookup \ pammasksharpen \ pammixinterlace \ @@ -148,6 +151,7 @@ ordinary_testprogs="\ pamtouil \ pamtowinicon \ pamtoxvmini \ + pamtris \ pamundice \ pamunlookup \ pamvalidate \ diff --git a/test/pbmtext-bdf.ok b/test/pbmtext-bdf.ok index 5ab8b4af..b1486493 100644 --- a/test/pbmtext-bdf.ok +++ b/test/pbmtext-bdf.ok @@ -6,3 +6,16 @@ 1 1 1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 diff --git a/test/pbmtext-bdf.test b/test/pbmtext-bdf.test index 1bd7c52c..50df7b75 100755 --- a/test/pbmtext-bdf.test +++ b/test/pbmtext-bdf.test @@ -5,7 +5,7 @@ tmpdir=${tmpdir:-/tmp} font_bdf=${tmpdir}/font.bdf -font_corrupt_bdf=${tmpdir}/fontcorrupt.bdf +font_corrupt=${tmpdir}/fontcorrupt # Though this BDF font file defines only three letters, it is valid. @@ -69,31 +69,101 @@ pbmtext -font ${font_bdf} ABC | cksum # Test 2 -# The rest should all fail. Writes 1 seven times. +# These should all fail. Writes 1 eightteen times. echo "Test whether corrupted BDF font files are properly handled." 1>&2 -echo "Error messages will appear." 1>&2 -echo 1>&2 +echo "Error messages should appear below the line." 1>&2 +echo "-----------------------------------------------------------" 1>&2 pbmtext -font ${font_bdf} BCD echo $? +for token in "STARTPROPERTIES" "CHARS" "STARTCHAR" "ENCODING" "DWIDTH" +do + font_corrupt_bdf=${font_corrupt}.naked_${token}.bdf + sed 's/^'${token}' .*$/'${token}'/' \ + ${font_bdf} > ${font_corrupt_bdf} + pbmtext -font ${font_corrupt_bdf} ABC > /dev/null + echo $? + rm ${font_corrupt_bdf} +done + +font_corrupt_bdf=${font_corrupt}.fbbx_narrow.bdf sed 's/FONTBOUNDINGBOX 4 5 0 0/FONTBOUNDINGBOX 4 4 0 0/' \ + ${font_bdf} > ${font_corrupt_bdf} +pbmtext -font ${font_corrupt_bdf} ABC > /dev/null +echo $? +rm ${font_corrupt_bdf} + +font_corrupt_bdf=${font_corrupt}.fbbx_low.bdf +sed 's/FONTBOUNDINGBOX 4 5 0 0/FONTBOUNDINGBOX 3 5 0 0/' \ + ${font_bdf} > ${font_corrupt_bdf} +pbmtext -font ${font_corrupt_bdf} ABC > /dev/null +echo $? +rm ${font_corrupt_bdf} + +font_corrupt_bdf=${font_corrupt}.bbx_only3fields.bdf +sed 's/BBX 4 5 0 0/BBX 4 5 0/' \ + ${font_bdf} > ${font_corrupt_bdf} +pbmtext -font ${font_corrupt_bdf} ABC > /dev/null +echo $? +rm ${font_corrupt_bdf} + +font_corrupt_bdf=${font_corrupt}.bbx_wide.bdf +sed 's/BBX 4 5 0 0/BBX 9 5 0 0/' \ + ${font_bdf} > ${font_corrupt_bdf} +pbmtext -font ${font_corrupt_bdf} ABC > /dev/null +echo $? +rm ${font_corrupt_bdf} + +font_corrupt_bdf=${font_corrupt}.bbx_zerowidth.bdf +sed 's/BBX 4 5 0 0/BBX 0 5 0 0/' \ ${font_bdf} > ${font_corrupt_bdf} pbmtext -font ${font_corrupt_bdf} ABC > /dev/null echo $? rm ${font_corrupt_bdf} +font_corrupt_bdf=${font_corrupt}.bbx_tall.bdf sed 's/BBX 4 5 0 0/BBX 4 6 0 0/' \ ${font_bdf} > ${font_corrupt_bdf} pbmtext -font ${font_corrupt_bdf} ABC > /dev/null echo $? rm ${font_corrupt_bdf} +font_corrupt_bdf=${font_corrupt}.bbx_low.bdf +sed 's/BBX 4 5 0 0/BBX 4 1 0 0/' \ + ${font_bdf} > ${font_corrupt_bdf} +pbmtext -font ${font_corrupt_bdf} ABC > /dev/null +echo $? +rm ${font_corrupt_bdf} + +font_corrupt_bdf=${font_corrupt}.bbx_zeroheight.bdf +sed 's/BBX 4 5 0 0/BBX 4 0 0 0/' \ + ${font_bdf} > ${font_corrupt_bdf} +pbmtext -font ${font_corrupt_bdf} ABC > /dev/null +echo $? +rm ${font_corrupt_bdf} + for delete_line in 14 16 18 20 do + font_corrupt_bdf=${font_corrupt}.del${delete_line}.pdf sed "${delete_line}"d ${font_bdf} > ${font_corrupt_bdf} pbmtext -font ${font_corrupt_bdf} ABC > /dev/null echo $? rm ${font_corrupt_bdf} done + + +# Test 2 +# These should succeed. Warning messages will be displayed. +# Writes 1 two times. + +for token in "CHARSET_ENCODING" "CHARSET_REGISTRY" +do + font_corrupt_bdf=${font_corrupt}.naked_${token}.bdf + sed 's/^'${token}' .*$/'${token}'/' \ + ${font_bdf} > ${font_corrupt_bdf} + pbmtext -font ${font_corrupt_bdf} ABC > /dev/null + echo $? + rm ${font_corrupt_bdf} +done diff --git a/test/pbmtext-iso88591.ok b/test/pbmtext-iso88591.ok index d1516357..6cc1a856 100644 --- a/test/pbmtext-iso88591.ok +++ b/test/pbmtext-iso88591.ok @@ -1,4 +1,4 @@ -3491766365 5110 -3491766365 5110 -259944121 191 -259944121 191 +3806607098 5110 +3806607098 5110 +2858870527 192 +2858870527 192 diff --git a/test/pbmtext-iso88591.test b/test/pbmtext-iso88591.test index 34346f5c..bc5e83ab 100755 --- a/test/pbmtext-iso88591.test +++ b/test/pbmtext-iso88591.test @@ -21,26 +21,26 @@ if [ $? -ne 0 ] fi # Two rows -# Should print 3491766365 5110 twice +# Should print 3806607098 5110 twice LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=160;i<=255;++i) printf("%c",i); }' | \ pbmtext -builtin bdf | cksum LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=160;i<=255;++i) printf("%c",i); }' | \ LC_ALL=en_US.iso88591 pbmtext -builtin bdf -wchar | cksum # Two rows -# Should print 259944121 191 twice +# Should print 2858870527 192 twice LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=161;i<=255;++i) printf("%c",i); print "" }' | cksum LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=161;i<=255;++i) printf("%c",i); print ""}' | \ LC_ALL=en_US.iso88591 pbmtext -builtin bdf -wchar -text-dump | cksum \ No newline at end of file diff --git a/test/pbmtext-utf8.ok b/test/pbmtext-utf8.ok index 864c530a..9e65dec4 100644 --- a/test/pbmtext-utf8.ok +++ b/test/pbmtext-utf8.ok @@ -1,8 +1,8 @@ -1240895458 5110 -1240895458 5110 -898975479 2272 -898975479 2272 +2066913605 5110 +2066913605 5110 +2920616515 2301 +2920616515 2301 0 - !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} - !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 1 diff --git a/test/pbmtext-utf8.test b/test/pbmtext-utf8.test index ca1f45a2..010c02db 100755 --- a/test/pbmtext-utf8.test +++ b/test/pbmtext-utf8.test @@ -21,14 +21,14 @@ fi # Test 1. # Two rows -# Should print 1240895458 5110 twice +# Should print 2066913605 5110 twice LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=161;i<=255;++i) printf("%c",i); }' | \ pbmtext -builtin bdf | cksum LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=161;i<=255;++i) printf("%c",i); }' | \ iconv -f iso8859-1 -t utf-8 | \ LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar | cksum @@ -36,13 +36,13 @@ awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ # Test 2. # One row -# Should print 898975479 2272 twice +# Should print 2920616515 2301 twice LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print "" }' | \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print "" }' | \ pbmtext -builtin bdf | cksum LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""}' | \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""}' | \ LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar | cksum @@ -55,12 +55,12 @@ output=${tmpdir}/output # Output may be affected by locale. Compare with cmp. # Should print 0 LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=161;i<=255;++i) printf("%c",i); print "" }' | \ - iconv -f iso88591 -t utf8 > ${output} + iconv -f iso8859-1 -t utf-8 > ${output} LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""; \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""; \ for (i=161;i<=255;++i) printf("%c",i); print "" }' | \ iconv -f iso8859-1 -t utf-8 | \ LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar -text-dump | \ @@ -73,15 +73,19 @@ rm ${output} # Test 4. # One row # Should print the following twice: -# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} +# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print "" } ' +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print "" } ' LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); print ""}' | \ +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); print ""}' | \ LC_ALL=en_US.utf8 pbmtext -builtin bdf -wchar -text-dump +echo "Invalid utf-8 sequence as input." 1>&2 +echo "An error message should appear below the line." 1>&2 +echo "-----------------------------------------------------------" 1>&2 + # Test 5. # Invalid utf-8 sequence # Should print 1 diff --git a/test/pbmtext.ok b/test/pbmtext.ok index 000c0897..96e351f9 100644 --- a/test/pbmtext.ok +++ b/test/pbmtext.ok @@ -12,5 +12,5 @@ 1647614653 2027 1647614653 2027 1647614653 2027 -3233136020 4535 -1216262214 5711 +2547645687 4564 +1174281741 5741 diff --git a/test/pbmtext.test b/test/pbmtext.test index c92ed599..38578636 100755 --- a/test/pbmtext.test +++ b/test/pbmtext.test @@ -82,13 +82,13 @@ rm ${fontRectangle_txt} ${font_pbm} # One long row # Should print 3233136020 4535 LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c",i); +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c",i); for (i=160;i<=255;++i) printf("%c",i); }' | \ pbmtext -builtin bdf | cksum # One tall column # Should print 1216262214 5711 LC_ALL=C \ -awk 'BEGIN { for (i=32; i<=125;++i) printf("%c\n",i); +awk 'BEGIN { for (i=32; i<=126;++i) printf("%c\n",i); for (i=160;i<=255;++i) printf("%c\n",i); }' | \ pbmtext -nomargins -builtin bdf | cksum diff --git a/test/pgmnoise.ok b/test/pgmnoise.ok deleted file mode 100644 index 138218c2..00000000 --- a/test/pgmnoise.ok +++ /dev/null @@ -1 +0,0 @@ -2005134911 10015 diff --git a/test/ppmforge.ok b/test/ppmforge.ok deleted file mode 100644 index e4a4c9e2..00000000 --- a/test/ppmforge.ok +++ /dev/null @@ -1 +0,0 @@ -3634219838 196623 diff --git a/test/ppmpat-random.ok b/test/ppmpat-random.ok deleted file mode 100644 index 4d298cec..00000000 --- a/test/ppmpat-random.ok +++ /dev/null @@ -1,3 +0,0 @@ -2219119109 36015 -3436846137 16813 -908097729 16813 diff --git a/test/ppmrough.ok b/test/ppmrough.ok deleted file mode 100644 index 83643849..00000000 --- a/test/ppmrough.ok +++ /dev/null @@ -1 +0,0 @@ -378403602 30015 diff --git a/version.mk b/version.mk index abafa08b..c8aa2495 100644 --- a/version.mk +++ b/version.mk @@ -1,3 +1,3 @@ NETPBM_MAJOR_RELEASE = 10 -NETPBM_MINOR_RELEASE = 83 -NETPBM_POINT_RELEASE = 2 +NETPBM_MINOR_RELEASE = 84 +NETPBM_POINT_RELEASE = 0 |