/* * pbmtextps.c - render text into a bitmap using a postscript interpreter * * Copyright (C) 2002 by James McCann. * * 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. * * PostScript is a registered trademark of Adobe Systems International. * * Additions by Bryan Henderson contributed to public domain by author. * */ #define _XOPEN_SOURCE /* Make sure popen() is in stdio.h */ #define _BSD_SOURCE /* Make sure stdrup() is in string.h */ #include #include #include #include #include #include "pm_c_util.h" #include "mallocvar.h" #include "nstring.h" #include "shhopt.h" #include "pbm.h" #define BUFFER_SIZE 2048 struct cmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ unsigned int res; /* resolution, DPI */ unsigned int fontsize; /* Size of font in points */ const char * font; /* Name of postscript font */ float stroke; /* Width of stroke in points (only for outline font) */ unsigned int verbose; const char * text; }; static void writeFileToStdout(const char * const fileName){ /* simple pbmtopbm */ FILE * ifP; int format; int cols, rows, row ; unsigned char * bitrow; ifP = pm_openr(fileName); pbm_readpbminit(ifP, &cols, &rows, &format); if (cols==0 || rows==0 || cols>INT_MAX-10 || rows>INT_MAX-10) pm_error("Abnormal output from gs program. " "width x height = %u x %u", cols, rows); pbm_writepbminit(stdout, cols, rows, 0); bitrow = pbm_allocrow_packed(cols); for (row = 0; row < rows; ++row) { pbm_readpbmrow_packed(ifP, bitrow, cols, format); pbm_writepbmrow_packed(stdout, bitrow, cols, 0); } pbm_freerow_packed(bitrow); } static void buildTextFromArgs(int const argc, char ** const argv, const char ** const textP) { char * text; unsigned int totalTextSize; unsigned int i; text = strdup(""); totalTextSize = 1; for (i = 1; i < argc; ++i) { if (i > 1) { totalTextSize += 1; text = realloc(text, totalTextSize); if (text == NULL) pm_error("out of memory"); strcat(text, " "); } totalTextSize += strlen(argv[i]); text = realloc(text, totalTextSize); if (text == NULL) pm_error("out of memory"); strcat(text, argv[i]); } *textP = text; } static void parseCommandLine(int argc, char ** argv, struct cmdlineInfo *cmdlineP) { /*--------------------------------------------------------------------------- Note that the file spec array we return is stored in the storage that was passed to us as the argv array. ---------------------------------------------------------------------------*/ optEntry * option_def; /* Instructions to OptParseOptions2 on how to parse our options. */ optStruct3 opt; unsigned int option_def_index; MALLOCARRAY(option_def, 100); option_def_index = 0; /* incremented by OPTENTRY */ OPTENT3(0, "resolution", OPT_UINT, &cmdlineP->res, NULL, 0); OPTENT3(0, "font", OPT_STRING, &cmdlineP->font, NULL, 0); OPTENT3(0, "fontsize", OPT_UINT, &cmdlineP->fontsize, NULL, 0); OPTENT3(0, "stroke", OPT_FLOAT, &cmdlineP->stroke, NULL, 0); OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); /* Set the defaults */ cmdlineP->res = 150; cmdlineP->fontsize = 24; cmdlineP->font = "Times-Roman"; cmdlineP->stroke = -1; opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; optParseOptions3(&argc, argv, opt, sizeof(opt), 0); buildTextFromArgs(argc, argv, &cmdlineP->text); } static const char * construct_postscript(struct cmdlineInfo const cmdline) { const char * retval; const char * template; if (cmdline.stroke < 0) template = "/%s findfont\n" "%d scalefont\n" "setfont\n" "12 36 moveto\n" "(%s) show\n" "showpage\n"; else template = "/%s findfont\n" "%d scalefont\n" "setfont\n" "12 36 moveto\n" "%f setlinewidth\n" "0 setgray\n" "(%s) true charpath\n" "stroke\n" "showpage\n"; if (cmdline.stroke < 0) asprintfN(&retval, template, cmdline.font, cmdline.fontsize, cmdline.text); else asprintfN(&retval, template, cmdline.font, cmdline.fontsize, cmdline.stroke, cmdline.text); return retval; } static const char * gsExecutableName() { const char * const which = "which gs"; static char buffer[BUFFER_SIZE]; FILE * f; memset(buffer, 0, BUFFER_SIZE); f = popen(which, "r"); if (!f) pm_error("Can't find ghostscript"); fread(buffer, 1, BUFFER_SIZE, f); if (buffer[strlen(buffer) - 1] == '\n') buffer[strlen(buffer) - 1] = '\0'; pclose(f); if (buffer[0] != '/' && buffer[0] != '.') pm_error("Can't find ghostscript"); return buffer; } static const char * cropExecutableName(void) { const char * const which = "which pnmcrop"; static char buffer[BUFFER_SIZE]; const char * retval; FILE * f; memset(buffer, 0, BUFFER_SIZE); f = popen(which, "r"); if (!f) retval = NULL; else { fread(buffer, 1, BUFFER_SIZE, f); if (buffer[strlen(buffer) - 1] == '\n') buffer[strlen(buffer) - 1] = 0; pclose(f); if (buffer[0] != '/' && buffer[0] != '.') { retval = NULL; pm_message("Can't find pnmcrop"); } else retval = buffer; } return retval; } static const char * gsCommand(const char * const psFname, const char * const outputFilename, struct cmdlineInfo const cmdline) { const char * retval; double const x = (double) cmdline.res * 11; double const y = (double) cmdline.res * ((double) cmdline.fontsize * 2 + 72) / 72; if (cmdline.res <= 0) pm_error("Resolution (dpi) must be positive."); if (cmdline.fontsize <= 0) pm_error("Font size must be positive."); /* The following checks are for guarding against overflows in this function. Huge x,y values that pass these checks may be rejected by the 'gs' program. */ if (x > (double) INT_MAX-10) pm_error("Absurdly fine resolution: %u. Output width too large.", cmdline.res ); if (y > (double) INT_MAX-10) pm_error("Absurdly fine resolution (%u) and/or huge font size (%u). " "Output height too large.", cmdline.res, cmdline.fontsize); asprintfN(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm " "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s /dev/null", gsExecutableName(), (int) x, (int) y, cmdline.res, outputFilename, psFname); return retval; } static const char * cropCommand(const char * const inputFileName) { const char * retval; const char * plainOpt = pm_plain_output ? "-plain" : "" ; if (cropExecutableName()) { asprintfN(&retval, "%s -top -right %s %s", cropExecutableName(), plainOpt, inputFileName); if (retval == strsol) pm_error("Unable to allocate memory"); } else retval = NULL; return retval; } static void writeProgram(const char * const psFname, struct cmdlineInfo const cmdline) { const char * ps; FILE * psfile; psfile = fopen(psFname, "w"); if (psfile == NULL) pm_error("Can't open temp file '%s'. Errno=%d (%s)", psFname, errno, strerror(errno)); ps = construct_postscript(cmdline); if (cmdline.verbose) pm_message("Postscript program = '%s'", ps); if (fwrite(ps, 1, strlen(ps), psfile) != strlen(ps)) pm_error("Can't write postscript to temp file"); fclose(psfile); strfree(ps); } static void executeProgram(const char * const psFname, const char * const outputFname, struct cmdlineInfo const cmdline) { const char * com; int rc; com = gsCommand(psFname, outputFname, cmdline); if (com == NULL) pm_error("Can't allocate memory for a 'ghostscript' command"); if (cmdline.verbose) pm_message("Running Postscript interpreter '%s'", com); rc = system(com); if (rc != 0) pm_error("Failed to run Ghostscript process. rc=%d", rc); strfree(com); } static void cropToStdout(const char * const inputFileName, bool const verbose) { const char * com; com = cropCommand(inputFileName); if (com == NULL) { /* No pnmcrop. So don't crop. */ pm_message("Can't find pnmcrop command, image will be large"); writeFileToStdout(inputFileName); } else { FILE * pnmcrop; if (verbose) pm_message("Running crop command '%s'", com); pnmcrop = popen(com, "r"); if (pnmcrop == NULL) pm_error("Can't run pnmcrop process"); else { char buf[2048]; bool eof; eof = FALSE; while (!eof) { int bytesRead; bytesRead = fread(buf, 1, sizeof(buf), pnmcrop); if (bytesRead > 0) { int rc; rc = fwrite(buf, 1, bytesRead, stdout); if (rc != bytesRead) pm_error("Can't write to stdout"); } else if (bytesRead == 0) eof = TRUE; else pm_error("Failed to read output of Pnmcrop process. " "Errno=%d (%s)", errno, strerror(errno)); } fclose(pnmcrop); } strfree(com); } } static void createOutputFile(struct cmdlineInfo const cmdline) { const char * const template = "./pstextpbm.%d.tmp.%s"; const char * psFname; const char * uncroppedPbmFname; asprintfN(&psFname, template, getpid(), "ps"); if (psFname == NULL) pm_error("Unable to allocate memory"); writeProgram(psFname, cmdline); asprintfN(&uncroppedPbmFname, template, getpid(), "pbm"); if (uncroppedPbmFname == NULL) pm_error("Unable to allocate memory"); executeProgram(psFname, uncroppedPbmFname, cmdline); unlink(psFname); strfree(psFname); cropToStdout(uncroppedPbmFname, cmdline.verbose); unlink(uncroppedPbmFname); strfree(uncroppedPbmFname); } int main(int argc, char *argv[]) { struct cmdlineInfo cmdline; pbm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); createOutputFile(cmdline); return 0; }