diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/dvi2bitmap/dvi2bitmap.cc |
Initial commit
Diffstat (limited to 'dviware/dvi2bitmap/dvi2bitmap.cc')
-rw-r--r-- | dviware/dvi2bitmap/dvi2bitmap.cc | 1662 |
1 files changed, 1662 insertions, 0 deletions
diff --git a/dviware/dvi2bitmap/dvi2bitmap.cc b/dviware/dvi2bitmap/dvi2bitmap.cc new file mode 100644 index 0000000000..d694bb6c31 --- /dev/null +++ b/dviware/dvi2bitmap/dvi2bitmap.cc @@ -0,0 +1,1662 @@ +/* This file is part of dvi2bitmap; see README for copyrights and licence */ + +// FIXME: at several points in the option processing below, I've noted +// sensible changes to the behaviour. These, and the corresponding +// documentation, should be changed come the next minor version update +// (but not between bugfix releases). At the same time, check that +// the documentation (SGML and roff), and the usage message at the +// bottom of this file, are consistent with the new options. + +#include <config.h> + +#include <vector> +#include <iostream> +#include <string> + +#ifdef HAVE_CSTD_INCLUDE +# include <cstdio> +# include <cstdlib> +# include <cstdarg> +# include <cassert> +# include <cstring> +#else +# include <stdio.h> // for vsprintf +# include <stdlib.h> +# include <stdarg.h> +# include <assert.h> +# include <string.h> +#endif + +#include <bitset> +#include <unistd.h> // for getsubopt +#include "getopt_long.h" + +using STD::cout; // these are used multiple times +using STD::cerr; +using STD::endl; +using STD::strcmp; +using STD::exit; + +#include "DviFile.h" +#include "PkFont.h" +#include "Bitmap.h" +#include "BitmapImage.h" +#include "verbosity.h" +#include "PageRange.h" +#include "Util.h" +#include "stringstream.h" +#include "version.h" + +#ifdef ENABLE_KPATHSEA +#include "KarlPathSearcher.h" +#endif +#ifdef ENABLE_PNG +#include "PNGBitmap.h" // for PNGBitmap::libpng_version +#endif + +#define DVI2BITMAPURL "http://www.astro.gla.ac.uk/users/norman/star/dvi2bitmap/" + +// bitmap_info keeps together all the detailed information about the +// bitmap to be written. +struct bitmap_info { + bitmap_info() + : blur_bitmap(false), crop_bitmap(true), + make_transparent(true), bitmap_scale_factor(1), + ofile_pattern(""), ofile_name(""), ofile_type("") { } + bool blur_bitmap; + bool crop_bitmap; + bool make_transparent; + int bitmap_scale_factor; + string ofile_pattern; + string ofile_name; + string ofile_type; +}; + +static void process_dvi_file (DviFile *, bitmap_info&, int resolution, + PageRange&); +static bool process_special (DviFile *, string specialString, + Bitmap*, bitmap_info&); +static string substitute_ofn_pattern(string pattern, int pagenum); +static string get_ofn_pattern (string dviname); +static bool valid_ofile_pattern (string& patt); +static bool parse_boolean_string(char *s, bool *ok=0); +static void Usage (const char*); +//static void Usage (string msg); +static void Usage (bool); +static void show_help(); +static char *progname; + +/* Declare the getsubopt function. Do not attempt to check the + * HAVE_DECL_GETSUBOPT macro, as this will have been tested with a C + * compiler, and so we could incorrectly omit this declaration. + * [XXX is this bogus? It appears to work, but when was that ever a + * good argument.] + */ +extern "C" int getsubopt(char** options, char*const* tokens, char** value); + +verbosities verbosity = normal; +int bitmapH = -1; +int bitmapW = -1; + +// Make resolution global -- +// several functions in here might reasonably want to see this. +int resolution = PkFont::dpiBase();// in pixels-per-inch +int oneInch = resolution; // one inch, including magnification + + +int main (int argc, char **argv) +{ + string dviname; + bool dviname_is_seekable = true; + double magmag = 1.0; // magnification of file magnification factor + enum { font_show, font_incfound, font_cmds, font_long_display }; + STD::bitset<8> show_font_info; // all zero + bitmap_info bm; + // Which elements of the DVI file should we process. process_dvi + // indicates that we should process the body of the DVI file, and + // process_preamble and process_postamble indicate that we should + // process the corresponding element. + enum { process_dvi, process_preamble, process_postamble }; + STD::bitset<8> processing_; + processing_.set(process_dvi); + processing_.set(process_preamble); + processing_.set(process_postamble); + + PageRange PR; + + +#define MM oneInch * 0.03937 + struct { + char *name; double w; double h; + } papersizes[] = { + // Do these calculations in terms of the unit oneInch + // Defn of inch: 1m=39.37in => 1mm=0.03937in + { (char*)"a4", 210 * MM, 297 * MM }, // 210x297mm + { (char*)"a4l", 297 * MM, 210 * MM, }, // 297x210mm + { (char*)"a5", 148 * MM, 210 * MM, }, // 148x210mm + { (char*)"a5l", 210 * MM, 148 * MM, }, // 210x148mm + { (char*)"usletter", 8.5*oneInch, 8.5*oneInch, }, // 8.5x11in + }; + int npapersizes = sizeof(papersizes)/sizeof(papersizes[0]); + + // option parsing with getopt_long + int optch; + extern char *optarg; + struct option long_options[] = { + { (char*)"crop", 1, 0, 'c' }, + { (char*)"font-search", 1, 0, 'F' }, + { (char*)"debug", 1, 0, 'g' }, + { (char*)"font-gen", 1, 0, 'G' }, + { (char*)"height", 1, 0, 'h' }, + { (char*)"help", 0, 0, '?' }, + { (char*)"end-page", 1, 0, 'l' }, + { (char*)"magnification", 1, 0, 'm' }, + { (char*)"font-mode", 1, 0, 'M' }, + { (char*)"nodvi", 0, 0, 'n' }, + { (char*)"output", 1, 0, 'o' }, + { (char*)"start-page", 1, 0, 'p' }, + { (char*)"page-range", 1, 0, 'P' }, + { (char*)"query", 1, 0, 'Q' }, + { (char*)"resolution", 1, 0, 'r' }, + { (char*)"colours", 1, 0, 'R' }, + { (char*)"colors", 1, 0, 'R' }, // synonym + { (char*)"scaledown", 1, 0, 's' }, + { (char*)"paper-size", 1, 0, 't' }, +#define OPT_PIPE 1 + { (char*)"pipe", 0, 0, OPT_PIPE }, + { (char*)"output-type", 1, 0, 'T' }, + { (char*)"verbose", 1, 0, 'v' }, + { (char*)"version", 0, 0, 'V' }, + { (char*)"width", 1, 0, 'w' }, + { (char*)"process", 1, 0, 'X' }, + { 0, 0, 0, 0 } + }; + // should generate this from long_options + char *short_options = (char*)"c:F:g:G:h:l:M:no:p:P:Q:r:R:s:t:T:v:Vw:X:"; + + + progname = argv[0]; + +#ifdef ENABLE_KPATHSEA + KarlPathSearcher::setProgramName(progname); +#endif + + BitmapImage::setInfo (BitmapImage::SOFTWAREVERSION, + new string(version_string)); + BitmapImage::setInfo (BitmapImage::FURTHERINFO, + new string (DVI2BITMAPURL)); + + while ((optch = getopt_long(argc, argv, short_options, long_options, 0)) + != -1) { + + switch (optch) { + case OPT_PIPE: // --pipe + dviname_is_seekable = false; + break; + + case 'c': // --crop + { + // get dimension, and convert points to pixels. + // Note that the functionality here will vary + // depending on whether the magmag is set before + // or after this option, and it'll take no + // account of variations of the magnification + // within the DVI file. + char *options = optarg; + char *value; + char *tokens[] = { + (char*)"left", (char*)"right", + (char*)"top", (char*)"bottom", (char*)"all", + (char*)"absolute", (char*)"relative", + NULL + }; + bool absCrop = false; + bool haveValue; + int cropmargin; + while (*options) { + int suboptch = ::getsubopt(&options, tokens, &value); + if (!value) // no option value: should be absolute/relative + haveValue = false; + else { + cropmargin + = static_cast<int>(magmag*STD::atof(value)/72.0 + *resolution); + haveValue = true; + } + + switch(suboptch) { + case 0: // left + if (!haveValue) Usage("no value for --crop=left"); + Bitmap::cropDefault + (Bitmap::Left, cropmargin, absCrop); + break; + case 1: // right + if (!haveValue) Usage("no value for --crop=right"); + Bitmap::cropDefault + (Bitmap::Right, cropmargin, absCrop); + break; + case 2: // top + if (!haveValue) Usage("no value for --crop=top"); + Bitmap::cropDefault + (Bitmap::Top, cropmargin, absCrop); + break; + case 3: // bottom + if (!haveValue) Usage("no value for --crop=bottom"); + Bitmap::cropDefault + (Bitmap::Bottom, cropmargin, absCrop); + break; + case 4: // all + if (!haveValue) Usage("no value for --crop=all"); + if (absCrop) // don't want this! + Usage("can't have absolute crop for --crop=all"); + Bitmap::cropDefault + (Bitmap::All, cropmargin, false); + break; + case 5: // absolute + if (haveValue) Usage("--crop=absolute takes no value"); + absCrop = true; + break; + case 6: // relative + if (haveValue) Usage("--crop=relative takes no value"); + absCrop = false; + break; + case -1: + Usage("unrecognised keyword for --crop"); + break; + default: + throw DviBug("Impossible crop suboption %c", suboptch); + } + } + } + break; + + case 'F': // --font-search + { + char *options = optarg; + char *value; + char *tokens[] = { + (char*)"path", (char*)"nopath", + (char*)"command", (char*)"nocommand", + (char*)"kpathsea", (char*)"nokpathsea", + (char*)"none", + NULL, + }; + while (*options) { + int suboptch = getsubopt(&options, tokens, &value); + switch (suboptch) { + case 0: // path + PkFont::setFontSearchPath(value); // value may be 0 + break; + case 1: // nopath + PkFont::setFontSearchPath(false); + break; + + case 2: // command + PkFont::setFontSearchCommand(value); // value may be 0 + break; + case 3: // nocommand + PkFont::setFontSearchCommand(false); + break; + + case 4: // kpathsea + // any argument is permitted but ignored + PkFont::setFontSearchKpse(true); + break; + case 5: // nokpathsea + PkFont::setFontSearchKpse(false); + break; + + case 6: // none + PkFont::setFontSearchPath(false); + PkFont::setFontSearchKpse(false); + PkFont::setFontSearchCommand(false); + break; + + default: + Usage("bad keyword for --font-search"); + } + } + } + break; + + case 'G': // --font-gen + { + // Parse option: + // --font-gen=command[=blah] + // --font-gen=[boolean] + // Can't use getsubopt() since blah will contain spaces + bool boolok; + if (parse_boolean_string(optarg, &boolok)) { + PkFont::setFontgen(true); + } else if (boolok) { + // parse_boolean_string returned false, + // because it did find a valid boolean + PkFont::setFontgen(false); + } else if (STD::strncmp(optarg, "command", 7) == 0) { + char* cmd; + for (cmd=optarg; *cmd!='=' && *cmd!='\0'; cmd++) + ; // find equals sign or eos + // cerr << "G: optarg=" << optarg << ", cmd=" + // << (cmd==0 ? "<NULL>" : cmd) << endl; + if (*cmd == '\0') { // no cmd + // simply enable it, with default cmd + PkFont::setFontgen(true); + } else { + cmd++; // was pointing at '=' + PkFont::setFontgenCommand(cmd); + } + } else { + Usage("Unrecognised argument to --font-gen"); + } + } + break; + + case 'g': // --debug + { + // !!! Additions/changes here should be mirrored + // in show_help() below + char *o = optarg; + verbosities debuglevel = debug; + for (; *o != '\0'; ++o) { + switch (*o) + { + case 'b': // debug bitmap + Bitmap::verbosity(debuglevel); + BitmapImage::verbosity(debuglevel); + // following is used only within Bitmap.cc + DviFilePosition::verbosity(debuglevel); + break; + case 'd': // debug DVI file + DviFile::verbosity(debuglevel); + break; + case 'g': + if (debuglevel == debug) + debuglevel = everything; + break; + case 'i': // debug input + InputByteStream::verbosity(debuglevel); + break; + case 'm': // debug main program + verbosity = debuglevel; + break; + case 'p': // debug PK file (and kpathsea) + PkFont::verbosity(debuglevel); + break; + case 'r': // debug rasterdata parsing + PkRasterdata::verbosity(debuglevel); + break; + case 'u': // debug utility functions + Util::verbosity(debuglevel); + break; + default: + { + char fmt[] = "bad flag (%c) for --debug"; + char buf[sizeof(fmt)]; + STD::sprintf(buf, fmt, *o); + Usage(buf); + break; + } + } + } + } + break; + + case 'h': // --height + bitmapH = STD::atoi(optarg); + break; + + case 'l': // --end-page + case 'p': // --start-page + case 'P': // --page-range + if (! PR.addSpec(optch, optarg)) + Usage("improper page spec"); + break; + + case 'M': // --font-mode + PkFont::setMissingFontMode (optarg); + break; + + case 'm': // --magnification + magmag = STD::atof (optarg); + break; + + case 'n': // --nodvi + // don't actually process the DVI file + processing_.reset(process_dvi); + PkFont::setFontgen(false); + break; + + case 'o': // --output + { + string a = optarg; + if (a.find('%') == string::npos) + bm.ofile_name = a; + else + bm.ofile_pattern = a; + break; + } + + // case 'p', 'P': see 'l' + + case 'Q': // --query + { + char *options = optarg; + char *value; + char *tokens[] = { + (char*)"missing-fonts", (char*)"f", + (char*)"all-fonts", (char*)"F", + (char*)"missing-fontgen", (char*)"g", + (char*)"all-fontgen", (char*)"G", + (char*)"bitmaps", + (char*)"paper", + (char*)"types", + NULL + }; + while (*options) { + int suboptch = getsubopt(&options, tokens, &value); + if (value) // no values + Usage("--query keywords do not take values"); + switch (suboptch) + { + case 0: // missing-fonts + show_font_info.set(font_show); + show_font_info.set(font_long_display); + PkFont::setFontgen(false); + break; + case 1: // f + show_font_info.set(font_show); + PkFont::setFontgen(false); + break; + case 2: // all-fonts + show_font_info.set(font_show); + show_font_info.set(font_incfound); + show_font_info.set(font_long_display); + PkFont::setFontgen(false); + break; + case 3: // F + show_font_info.set(font_show); + show_font_info.set(font_incfound); + PkFont::setFontgen(false); + break; + case 4: // missing-fontgen + show_font_info.set(font_show); + show_font_info.set(font_cmds); + show_font_info.set(font_long_display); + PkFont::setFontgen(false); + break; + case 5: // g + show_font_info.set(font_show); + show_font_info.set(font_cmds); + PkFont::setFontgen(false); + break; + case 6: // all-fontgen + show_font_info.set(font_show); + show_font_info.set(font_incfound); + show_font_info.set(font_cmds); + show_font_info.set(font_long_display); + PkFont::setFontgen(false); + break; + case 7: // G + show_font_info.set(font_show); + show_font_info.set(font_incfound); + show_font_info.set(font_cmds); + PkFont::setFontgen(false); + break; + + case 8: // bitmaps + Bitmap::logBitmapInfo ("Qbitmaps "); + break; + + case 9: // paper + cout << "Qpaper"; + for (int i=0; i<npapersizes; i++) + cout << ' ' << papersizes[i].name; + cout << endl; + break; + + case 10: // types + { + cout << "Qtypes "; + const char *ft + = BitmapImage::firstBitmapImageFormat(); + cout << ft; + ft = BitmapImage::nextBitmapImageFormat(); + while (ft != 0) + { + cout << ' ' << ft; + ft = BitmapImage::nextBitmapImageFormat(); + } + cout << endl; + } + break; + + case -1: + Usage("unknown query type"); + break; + default: + throw DviBug("Impossible query suboption %c", suboptch); + } + } + } + break; + + case 'r': // --resolution + PkFont::setResolution (STD::atoi(optarg)); + resolution = PkFont::dpiBase(); + break; + + case 'R': // --colours, --colors + { + char *options = optarg; + char *value; + char *tokens[] = { + (char*)"foreground", (char*)"background", NULL + }; + while (*options) { + int fb = getsubopt(&options, tokens, &value); + Bitmap::BitmapColour rgb; + if (!value) + Usage("no value given for --colours keyword"); + if (Util::parseRGB(rgb, value)) + Bitmap::setDefaultRGB (fb==0, &rgb); + else + Usage("bad colour spec"); + } + } + break; + + case 's': // --scaledown + bm.bitmap_scale_factor = STD::atoi (optarg); + break; + + case 't': // --paper-size + // Note that the functionality here will vary + // depending on whether the magmag is set before + // or after this option, and it'll take no + // account of variations of the magnification + // within the DVI file. + int i; + for (i = 0; i<npapersizes; i++) + if (strcmp (optarg, papersizes[i].name) == 0) + { + bitmapH + = static_cast<int>(magmag*papersizes[i].h+0.5); + bitmapW + = static_cast<int>(magmag*papersizes[i].w+0.5); + break; + } + if (i == npapersizes) + cerr << "--paper-size=" << optarg + << " not recognised. See --query=paper" << endl; + else + if (verbosity > normal) + cerr << "Papersize " << optarg + << ": H=" << bitmapH + << " W=" << bitmapW + << endl; + break; + + case 'T': // --output-type + bm.ofile_type = optarg; + if (! BitmapImage::supportedBitmapImage (bm.ofile_type)) + { + bm.ofile_type + = BitmapImage::firstBitmapImageFormat(); + cerr << "Unsupported image type " + << optarg + << ": using " + << bm.ofile_type + << " instead" << endl; + } + break; + + case 'V': // --version + cout << version_string << endl << "Options:" << endl; + + cout << "ENABLE_GIF " << +#ifdef ENABLE_GIF + "yes" +#else + "no" +#endif + << endl; + + cout << "ENABLE_PNG " << +#ifdef ENABLE_PNG + "yes" << endl << " libpng: " << PNGBitmap::version_string() +#else + "no" +#endif + << endl; + + cout << "ENABLE_KPATHSEA " << +#ifdef ENABLE_KPATHSEA + "yes" << endl << " libkpathsea: " << KarlPathSearcher::version_string() +#else + "no" +#endif + << endl; + +#ifdef DEFAULT_TEXMFCNF + cout << " DEFAULT_TEXMFCNF=" << DEFAULT_TEXMFCNF << endl; +#endif + +#ifdef FAKE_PROGNAME + cout << " FAKE_PROGNAME=" << FAKE_PROGNAME << endl; +#endif + +#ifdef FONT_SEARCH_SCRIPT + cout << "FONT_SEARCH_SCRIPT " << FONT_SEARCH_SCRIPT << endl; +#endif + +#ifdef FONT_GEN_TEMPLATE + cout << "FONT_GEN_TEMPLATE " << FONT_GEN_TEMPLATE << endl; +#else + cout << "Font generation disabled" << endl; +#endif +#ifdef DEFAULT_MFMODE + cout << " DEFAULT_MFMODE=" << DEFAULT_MFMODE << endl; +#endif +#ifdef DEFAULT_RESOLUTION + cout << " DEFAULT_RESOLUTION=" << DEFAULT_RESOLUTION << endl; +#endif + + cout << version_string << endl; + processing_.reset(); // do no further processing + break; + + case 'v': // --verbose + if (strcmp(optarg, "quiet") == 0) + verbosity = quiet; + else if (strcmp(optarg, "silent") == 0) + verbosity = silent; + else if (strcmp(optarg, "normal") == 0) + verbosity = normal; + else + Usage("bad verbosity keyword"); + + DviFile::verbosity(verbosity); + PkFont::verbosity(verbosity); + PkRasterdata::verbosity(verbosity); + InputByteStream::verbosity(verbosity); + Bitmap::verbosity(verbosity); + BitmapImage::verbosity(verbosity); + Util::verbosity(verbosity); + break; + + case 'w': // --width + bitmapW = STD::atoi(optarg); + break; + + case 'X': // --process + { + char *options = optarg; + char *value; + // There is no reason why the user should switch off + // process_preamble, so it is not (documented as) + // possible to do this below. + char *tokens[] = { + (char*)"dvi", // 0 + (char*)"nodvi", // 1 + (char*)"XXXpreamble", // 2 + (char*)"XXXnopreamble", // 3 + (char*)"postamble", // 4 + (char*)"nopostamble", // 5 + (char*)"blur", // 6 + (char*)"noblur", // 7 + (char*)"transparent", // 8 + (char*)"notransparent", // 9 + (char*)"crop", // 10 + (char*)"nocrop", // 11 + (char*)"options", // 12 + NULL + }; + while (*options) { + int suboptch = getsubopt(&options, tokens, &value); + if (value) // no values + Usage("--process keywords do not take values"); + switch (suboptch) + { + case 0: // dvi + processing_.set(process_dvi); + break; + case 1: // nodvi + processing_.reset(process_dvi); + break; + case 2: // preamble + processing_.set(process_preamble); + break; + case 3: // nopreamble + processing_.reset(process_preamble); + break; + case 4: // postamble + processing_.set(process_postamble); + break; + case 5: // nopostamble + processing_.reset(process_postamble); + break; + case 12: // options-only + processing_.reset(); // reset everything + break; + + case 6: // blur + bm.blur_bitmap = true; + break; + case 7: // noblur + bm.blur_bitmap = false; + break; + + case 8: // transparent + bm.make_transparent = true; + break; + case 9: // notransparent + bm.make_transparent = false; + break; + + case 10: // crop + bm.crop_bitmap = true; + break; + case 11: // nocrop + bm.crop_bitmap = false; + break; + + case -1: + Usage("bad process keyword"); + break; + default: // eh? + throw DviBug("Impossible process suboption %c", suboptch); + } + } + } + break; + + case '?': // --help + show_help(); + processing_.reset(); // clear all + break; + + default: + Usage("unrecognised option"); + } + } + + if (processing_.none()) + exit(0); + + argc -= optind; + argv += optind; + if (argc != 1) + Usage("no DVI file specified"); + dviname = *argv; + + // Insist we have a DVI file specified. + if (dviname.length() == 0) + Usage("DVI filename has zero length!"); + + if (verbosity >= normal) + // Banner + cout << "This is " << version_string << endl; + + if (bm.ofile_pattern.length() == 0) + bm.ofile_pattern = get_ofn_pattern(dviname == "-" ? "stdin" : dviname); + if (bm.ofile_pattern.length() == 0) + { + if (verbosity > silent) + cerr << "Error: Can't make output filename pattern from " + << dviname << endl; + exit(1); + } + + bool fonts_ok = true; // are there accumulated font errors? + try + { + DviFile *dvif = new DviFile(dviname, resolution, magmag, + processing_.test(process_postamble), + dviname_is_seekable); + if (dvif->eof()) + { + if (verbosity > silent) + cerr << "Error: Can't open file " << dviname + << " to read" << endl; + exit(1); + } + + bool all_fonts_present = true; // are all expected fonts found? + + if (dvif->haveReadPostamble()) { + all_fonts_present = true; + bool no_font_present = true; // are no expected fonts found? + + const DviFile::FontSet* fontset = dvif->getFontSet(); + for (DviFile::FontSet::const_iterator ci = fontset->begin(); + ci != fontset->end(); + ++ci) + { + const PkFont *f = *ci; + if (f->loaded()) { + no_font_present = false; + if (verbosity > normal) + cerr << "dvi2bitmap: loaded font " << f->name() + << endl; + } + else // flag at least one missing + all_fonts_present = false; + + if (show_font_info.test(font_show)) { + bool fld = show_font_info.test(font_long_display); + if (show_font_info.test(font_cmds)) { + if (show_font_info.test(font_incfound) + || !f->loaded()) { + // If f->loaded() is true, then we're here + // because FONT_INCFOUND was set, so indicate + // this in the output. + string cmd = f->fontgenCommand(); + if (cmd.length() == 0) + throw DviError + ("configuration problem: " + "I can't create a font-generation command"); + cout << (f->loaded() + ? (fld ? "Qall-fontgen " : "Qg ") + : (fld ? "Qmissing-fontgen " : "Qg ")) + << cmd + << endl; + } + } else { + if (show_font_info.test(font_incfound) + || !f->loaded()) { + // If f->loaded() is true, then we're here + // because FONT_INCFOUND was set, so indicate + // this in the output. + cout << (f->loaded() + ? (fld ? "Qall-fonts " : "QF ") + : (fld ? "Qmissing-fonts " : "Qf ")); + + // write out font name, dpi, base-dpi, mag, MF mode + cout << f->name() << ' ' + << f->dpiBase() << ' ' + << f->dpiScaled() << ' ' + << f->magnification() + << " localfont"; + if (f->loaded()) + { + string fn = f->fontFilename(); + string unk = "unknown"; + cout << ' ' << (fn.length() > 0 ? fn : unk); + } + cout << endl; + } + } + } + } + + // Font loading is deemed `successful' if either all the + // fonts are loaded, or failing that, if it's not the case + // that _no_ fonts were loaded. Note that if there were + // _no_ fonts listed in the postamble (an odd DVI file, + // but not impossible), then both all_fonts_present and + // no_font_present are true, and this expression evaluates + // to true. + fonts_ok = all_fonts_present || !no_font_present; + } else { + // haven't read postamble + if (show_font_info.any()) { + cerr << "Font information requested, " + << "but DVI postamble suppressed" << endl; + fonts_ok = false; // deem this an error + } else { + fonts_ok = true; // don't know any better + } + } + + if (processing_.test(process_dvi)) { + if (fonts_ok) { + process_dvi_file(dvif, bm, resolution, PR); + } else { + // something's gone wrong -- we'll exit with an error below + if (verbosity > silent) + cerr << progname << ": no fonts found! Giving up" << endl; + } + } else { + // We were not processing the DVI file, just (presumably) + // the preamble and postamble. In this case, require that + // _all_ the fonts are present, but if fonts_ok was false + // for some other reason, keep it false. + fonts_ok = fonts_ok && all_fonts_present; + } + } + catch (DviBug& e) + { + if (verbosity > silent) + e.print(); + } + catch (DviError& e) + { + if (verbosity > silent) + e.print(); + } + + // Exit status depends on the final value of fonts_ok. The + // result of the above logic is (should be) that we + // exit zero/success if (a) we were processing the DVI + // file normally and we found at least one font, or (b) we were + // just checking the preamble and we found _all_ the fonts. + exit(fonts_ok ? 0 : 1); +} + +void process_dvi_file (DviFile *dvif, bitmap_info& b, int fileResolution, + PageRange& PR) +{ + DviFileEvent *ev; + const PkFont *curr_font = 0; + int pagenum = 0; + Bitmap *bitmap = 0; + bool end_of_file = false; + size_t outcount = 0; // characters written to output current line + bool initialisedInch = false; + bool skipPage = false; + + while (! end_of_file) { + if (skipPage) + ev = dvif->getEndOfPage(); + else + ev = dvif->getEvent(); + + if (verbosity > debug) + ev->debug(); + + if (! initialisedInch) + { + // can't do this any earlier, as it's set in the preamble + oneInch = static_cast<int>(fileResolution * dvif->magnification()); + initialisedInch = true; + } + + if (DviFilePage *test = dynamic_cast<DviFilePage*>(ev)) + { + DviFilePage &page = *test; + if (page.isStart) + { + pagenum++; + + // Are we to print this page? + if (! PR.isSelected(pagenum, page.count)) + skipPage = true; + else + { + // Request a big-enough bitmap; this bitmap is the `page' + // on which we `print' below. hSize and vSize are the + // width and height of the widest and tallest pages, + // as reported by the DVI file; however, the file doesn't + // report the offsets of these pages. Add a + // couple of inches to both and hope for the best. + // If we haven't read the postamble, then hSize() + // and vSize() return negative, so make a rough guess. + if (bitmap == 0) { + int bmw, bmh; + if (bitmapW > 0) + bmw = bitmapW; + else if (dvif->hSize() < 0) + bmw = 4*oneInch; + else + bmw = dvif->hSize() + 2*oneInch; + if (bitmapH > 0) + bmh = bitmapH; + else if (dvif->vSize() < 0) + bmh = 2*oneInch; + else + bmh = dvif->vSize() + oneInch; + + bitmap = new Bitmap(bmw, bmh); + } else { + bitmap->clear(); + } + + if (verbosity > quiet) + { + int last, i; + // find last non-zero count + for (last=9; last>=0; last--) + if (page.count[last] != 0) + break; + + SSTREAM pageind; + pageind << '[' << page.count[0]; + for (i=1; i<=last; i++) + pageind << '.' << page.count[i]; + pageind << '\0'; + string ostr = SS_STRING(pageind); + if (outcount + ostr.length() > 78) + { + cout << endl; + outcount = 0; + } + cout << ostr; + outcount += ostr.length(); + } + } + } else { + // end of page + if (skipPage) { + // nothing to do in this case except reset it + skipPage = false; + if (bitmap != 0 && !bitmap->empty()) + // shouldn't happen, but just in case + bitmap->clear(); + } else { + if (bitmap == 0) + throw DviBug ("bitmap uninitialised at page end"); + else if (bitmap->empty()) { + if (verbosity > quiet) + cerr << "Warning: page " << pagenum + << " empty: nothing written" << endl; + } else { + if (bitmap->overlaps() && verbosity > quiet) { + int *bb = bitmap->boundingBox(); + cerr << "Warning: p." << pagenum + << ": bitmap too big: occupies (" << bb[0] << ',' + << bb[1] << ")...(" << bb[2] << ',' + << bb[3] << "). Requested " + << (bitmapW > 0 ? bitmapW : dvif->hSize()+oneInch) + << 'x' + << (bitmapH > 0 ? bitmapH : dvif->vSize()+oneInch) + << endl; + } + if (b.crop_bitmap) + bitmap->crop(); + if (b.blur_bitmap) + bitmap->blur(); + if (b.make_transparent) + bitmap->setTransparent(true); + if (b.bitmap_scale_factor != 1) + bitmap->scaleDown (b.bitmap_scale_factor); + const string *fn = dvif->filename(); + if (fn->length() != 0) + BitmapImage::setInfo (BitmapImage::INPUTFILENAME, + fn); + if (b.ofile_type.length() == 0) + { + b.ofile_type + = BitmapImage::firstBitmapImageFormat(); + /* Keep this warning? If not, why not? + cerr << "Warning: unspecified image format. Selecting default (" + << b.ofile_type << ")" << endl; + */ + } + if (b.ofile_name.length() == 0) + { + string output_filename + = substitute_ofn_pattern(b.ofile_pattern, + pagenum); + bitmap->write (output_filename, b.ofile_type); + } + else + bitmap->write (b.ofile_name, b.ofile_type); + } + b.ofile_name = ""; + + bitmap->clear(); + + if (verbosity > quiet) + { + cout << "] "; + outcount += 2; + } + } + } + } + else if (DviFileSetChar *test = dynamic_cast<DviFileSetChar*>(ev)) + { + if (curr_font == 0 || bitmap == 0) + throw DviBug ("font or bitmap not initialised setting char"); + DviFileSetChar& sc = *test; + PkGlyph& glyph = *curr_font->glyph(sc.charno()); + if (verbosity > normal) + { + cerr << "glyph `" << glyph.characterChar() + << "\' (" << glyph.characterCode() << ')'; + if (verbosity > debug) + cerr << " size " << glyph.w() << 'x' << glyph.h() + << " at position (" + << dvif->currH() << ',' << dvif->currV() + << ") plus oneInch=" << oneInch; + cerr << endl; + } + // calculate glyph positions, taking into account the + // offsets for the bitmaps, and the (1in,1in)=(72pt,72pt) + // = (resolution px,resolution px) offset of the TeX origin. + int x = dvif->currH(DviFile::unit_pixels) + glyph.hoff() + oneInch; + int y = dvif->currV(DviFile::unit_pixels) + glyph.voff() + oneInch; + bitmap->paint (x, y, + glyph.w(), glyph.h(), + glyph.bitmap()); + } + else if (DviFileSetRule *test = dynamic_cast<DviFileSetRule*>(ev)) + { + DviFileSetRule& sr = *test; + int x = dvif->currH() + oneInch; + int y = dvif->currV() + oneInch; + bitmap->rule (x,y,sr.w, sr.h); + } + else if (DviFileFontChange *test = + dynamic_cast<DviFileFontChange*>(ev)) + { + DviFileFontChange& fc = *test; + const PkFont *f = fc.font; + if (f->loaded()) + curr_font = f; + else { + f = dvif->getFallbackFont(f); + if (f != 0) + curr_font = f; + } + // curr_font unchanged if font-change was unsuccessful + } else if (DviFileSpecial* test = + dynamic_cast<DviFileSpecial*>(ev)) { + DviFileSpecial& special = *test; + if (!process_special (dvif, + special.specialString, + bitmap, b)) + if (verbosity > quiet) + cerr << "Warning: unrecognised special: " + << special.specialString + << endl; + } else if (DviFilePostamble *post + = dynamic_cast<DviFilePostamble*>(ev)) { + if (verbosity >= debug) + post->debug(); + end_of_file = true; + } + + ev->release(); + } + + if (verbosity > quiet) + cout << endl; +} + +// Process the special string, returning true on success. +bool process_special (DviFile *dvif, string specialString, + Bitmap* bitmap, bitmap_info& b) +{ + string_list l = Util::tokenise_string (specialString); + string_list::const_iterator s = l.begin(); + bool stringOK = false; + bool setDefault = false; + bool absolute = false; + + // Define units used within specials. special_unit is used in the + // file, but if it's set with `default', then + // default_special_unit is set, too. + static DviFile::DviUnits default_special_unit = DviFile::unit_pt; + DviFile::DviUnits special_unit = default_special_unit; + + if (verbosity > normal) + cerr << "dvi2bitmap: special default unit=" + << DviFile::unitString(special_unit) << endl; + + if (*s == "dvi2bitmap") { // OK + stringOK = true; + s++; + + while (s != l.end() && stringOK) { + if (*s == "default") + setDefault = true; + + else if (*s == "absolute") + absolute = true; + + else if (*s == "outputfile") { + s++; + if (s == l.end()) + stringOK = false; + else { + if (setDefault) { + // Build up the ofile_pattern, making sure that + // we end up with precisely one %d in the result. + bool seenPageCount = false; // seen %d or # + b.ofile_pattern = ""; + int imax = s->length()-1; + for (int i=0; i<=imax; i++) { + char c = (*s)[i]; + if (seenPageCount) { + if (c == '%') + b.ofile_pattern += '%'; + b.ofile_pattern += c; + } else { + // Support "#" for the page number, + // rather than just "%d", since it's + // tricky to get an unadorned percent + // into a TeX special, unless you play + // catcode tricks. Since it's also + // tricky to get just one "#" into the + // string, allow any number of them. + if (c == '#') { + b.ofile_pattern += '%'; + b.ofile_pattern += 'd'; + seenPageCount = true; + // absorb any number of # characters + while (i+1 <= imax && (*s)[i+1] == '#') + i++; + } else if (c == '%') { + i++; + if (i > imax && verbosity >= normal) { + cerr << "Warning: found descriptor %" + " at end of output string -- " + "replaced by %d" << endl; + } else if ((*s)[i] != 'd' + && verbosity >= normal) { + cerr << "Warning: found descriptor %" + << (*s)[i] + << " in output string: " + "replaced by %d" + << endl; + } + b.ofile_pattern += '%'; + b.ofile_pattern += 'd'; + seenPageCount = true; + } else { + b.ofile_pattern += c; + } + } + } + + if (!seenPageCount) { + b.ofile_pattern += '%'; + b.ofile_pattern += 'd'; + } + assert(valid_ofile_pattern(b.ofile_pattern)); + if (verbosity > normal) + cerr << "special: ofile_pattern=" + << b.ofile_pattern << endl; + } else { + b.ofile_name = *s; + } + } + + } else if (*s == "crop") { + s++; + if (s == l.end()) { stringOK = false; break; } + string side_s = *s; + Bitmap::Margin side = Bitmap::All; + s++; + if (s == l.end()) { stringOK = false; break; } + int dimen = STD::atoi (s->c_str()); + // scale from points to pixels + double npixels = DviFile::convertUnits(dimen, + special_unit, + DviFile::unit_pixels, + dvif); +// double npixels = dimen/72.0 // to inches +// * oneInch; + if (absolute) + { + // these dimensions are given w.r.t. an origin one inch + // from the left and top of the `paper'. Add this inch: + npixels += oneInch; + } + dimen = static_cast<int>(npixels); + + if (side_s == "left") + side = Bitmap::Left; + else if (side_s == "right") + side = Bitmap::Right; + else if (side_s == "top") + side = Bitmap::Top; + else if (side_s == "bottom") + side = Bitmap::Bottom; + else if (side_s == "all") + side = Bitmap::All; + else + stringOK = false; + + if (verbosity > normal) + cerr << "Crop " << side_s << '=' << dimen + << (setDefault ? " default" : "") + << (absolute ? " absolute" : "") + << endl; + + if (stringOK) { + if (side == Bitmap::All) { + if (setDefault) + for (int tside=0; tside<4; tside++) + Bitmap::cropDefault + (static_cast<Bitmap::Margin>(tside), + dimen, absolute); + for (int tside=0; tside<4; tside++) + bitmap->crop + (static_cast<Bitmap::Margin>(tside), + dimen, absolute); + } else { + if (setDefault) + Bitmap::cropDefault (side, dimen, absolute); + bitmap->crop (side, dimen, absolute); + } + } + } else if (*s == "imageformat") { + s++; + if (s == l.end()) + stringOK = false; + else + { + if (BitmapImage::supportedBitmapImage (*s)) + b.ofile_type = *s; + else + { + b.ofile_type = BitmapImage::firstBitmapImageFormat(); + cerr << "Warning: imageformat " << *s + << " not supported. Using " << b.ofile_type + << " instead." << endl; + } + if (!setDefault && verbosity > quiet) + cerr << "Warning: imageformat special " + << "should be prefixed with `default'" << endl; + } + } else if (*s == "foreground" || *s == "background") { + bool isfg = (*s == "foreground"); + //Byte r, g, b; + Bitmap::BitmapColour rgb; + s++; + if (s == l.end()) { stringOK = false; break; } + rgb.red = static_cast<Byte>(STD::strtol (s->c_str(), 0, 0)); + s++; + if (s == l.end()) { stringOK = false; break; } + rgb.green = static_cast<Byte>(STD::strtol (s->c_str(), 0, 0)); + s++; + if (s == l.end()) { stringOK = false; break; } + rgb.blue = static_cast<Byte>(STD::strtol (s->c_str(), 0, 0)); + + if (stringOK) + { + if (verbosity > normal) + cerr << "Set " + << (setDefault ? "(default) " : "") + << (isfg ? "foreground" : "background") + << " to " + << static_cast<int>(rgb.red) << ',' + << static_cast<int>(rgb.green) << ',' + << static_cast<int>(rgb.blue) << endl; + if (setDefault) + Bitmap::setDefaultRGB (isfg, &rgb); + else + bitmap->setRGB (isfg, &rgb); + } + } else if (*s == "strut") { + int x = dvif->currH() + oneInch; + int y = dvif->currV() + oneInch; + int strut_lrtb[4]; + for (int i=0; i<4; i++) { + s++; + if (s == l.end()) { + stringOK = false; + break; + } + strut_lrtb[i] = static_cast<int> + (DviFile::convertUnits(STD::strtod(s->c_str(),0), + special_unit, + DviFile::unit_pixels, + dvif) + + 0.5); // round to nearest pixel + if (verbosity > normal) + cerr << "dvi2bitmap: strut " + << *s << DviFile::unitString(special_unit) + << " = " << strut_lrtb[i] << "px" + << endl; + if (strut_lrtb[i] < 0) { + if (verbosity > silent) + cerr << "Strut must have positive dimensions" + << endl; + stringOK = false; + } + } + + if (stringOK) { + if (verbosity > normal) + cerr << "Strut: (" << x << ',' << y + << ") (lrtb)=(" + << strut_lrtb[0] << ',' << strut_lrtb[1] << ',' + << strut_lrtb[2] << ',' << strut_lrtb[3] + << ")px" << endl; + bitmap->strut (x, y, + strut_lrtb[0], + strut_lrtb[1], + strut_lrtb[2], + strut_lrtb[3]); + } + } else if (*s == "unit") { + s++; + if (s == l.end()) { + stringOK = false; + break; // JUMP OUT of special-processing loop + } + special_unit = DviFile::unitType(*s); + if (setDefault) + default_special_unit = special_unit; + } else if (*s == "mark") { + // Set the mark at the current position. This must + // match the offset calculations we make when setting + // characters while handling DviFileSetChar event above. + bitmap->mark + (new DviFilePosition + (dvif, + dvif->currH(DviFile::unit_pixels) + oneInch, + dvif->currV(DviFile::unit_pixels) + oneInch, + DviFile::unit_pixels)); + + } else + stringOK = false; + + s++; + } + } + + if (!stringOK && verbosity > quiet) + cerr << "Warning: unrecognised special: " << specialString << endl; + + return stringOK; +} + +/* The C++ <cstdio> definition does _not_ list snprintf as one of + * the members, though it is listed in the definition of <stdio.h> + * in the C standard. So different compilers seem to have different + * ideas of where it should be. + */ +#if HAVE_SNPRINTF +# if SNPRINTF_NAMESPACE == 3 +# define SNPRINTF std::snprintf +# elif SNPRINTF_NAMESPACE == 2 +# define SNPRINTF ::snprintf +# elif SNPRINTF_NAMESPACE == 1 +# define SNPRINTF snprintf +# else +/* this case shouldn't really happen! */ +# define SNPRINTF snprintf + extern "C" int snprintf(char *, int, const char*, ...); +# endif +#endif + +string substitute_ofn_pattern(string pattern, int pagenum) +{ + static char *buf = 0; + static int buflen = 50; + + assert (valid_ofile_pattern(pattern)); + + if (buf == 0) + buf = new char[buflen]; + +#if HAVE_SNPRINTF + int wanted = SNPRINTF(buf, buflen, pattern.c_str(), pagenum); + if (wanted >= buflen) { + delete[] buf; + buflen = wanted+1; // include space for trailing null + buf = new char[buflen]; + SNPRINTF(buf, buflen, pattern.c_str(), pagenum); + } +# undef SNPRINTF +#else + if (pattern.length() + 12 > buflen) { + // 12 is longer than the longest integer + delete[] buf; + buflen = pattern.length() + 20; // be generous + buf = new char[buflen]; + } + STD::sprintf(buf, pattern.c_str(), pagenum); +#endif + return buf; +} + +string get_ofn_pattern (string dviname) +{ + // strip path and extension from filename + size_t string_index = dviname.rfind(FSPATH_SEP); + string dvirootname; + if (string_index == string::npos) + dvirootname = dviname; + else + dvirootname = dviname.substr(string_index+1); + string_index = dvirootname.rfind('.'); + if (string_index != string::npos) // there is an extension -- skip it + dvirootname = dvirootname.substr(0,string_index); + + return dvirootname + "-page%d"; +} + +// Validate an ofile_pattern, returning true if there is precisely one +// %-format in the string, and it is "%d" (format %% is harmless, +// so allow that, too). We're paranoid here, because this is input +// obtained from the user. +static bool valid_ofile_pattern (string& patt) +{ + int nd = 0; + if (verbosity > normal) + cerr << "valid_ofile_pattern: examining :" << patt << endl; + for (string::const_iterator ci = patt.begin(); + ci != patt.end(); + ++ci) { + char c = *ci; + if (c == '%') { + ++ci; + if (ci == patt.end()) // unexpected + return false; + c = *ci; + switch (c) { + case '%': // OK, allow that + break; + case 'd': // the one we're after + nd++; + break; + default: // oops + return false; + } + } + } + return (nd == 1); +} + + +// Return true if the string is "yes", "true", "on", +// false if "no", "false", "off". +// Return false if it doesn't match anything. +// If ok is non-zero, then set it to true if one of the values did match, +// and false if none die +bool parse_boolean_string(char *s, bool *ok) +{ + if (ok != 0) *ok = true; + if (strcmp(s, "yes") == 0) return true; + if (strcmp(s, "no") == 0) return false; + if (strcmp(s, "true") == 0) return true; + if (strcmp(s, "false") == 0) return false; + if (strcmp(s, "on") == 0) return true; + if (strcmp(s, "off") == 0) return false; + if (ok != 0) *ok = false; + return false; +} + +void show_help() +{ + Usage(false); + cerr << "Options:" << endl; + string helpstrings[] = { +" --colours, --colors=[foreground|background]=spec", +" RGB spec, as red/blue/green or #rrggbb", +" --crop=[left|right|top|bottom|all]=n Specify margin round output bitmap", +" --crop=[absolute|relative] Specify cropping of bitmap", +" --debug=[flags] Trace execution (see below)", +" --font-search=[path|command|kpathsea]=value", +" --font-search=[nopath|nocommand|nokpathsea|none]", +" Control font-searching", +" --font-gen=[boolean|command[=cmd]] Generate missing fonts?", +" --font-mode=mode MetaFont mode used when generating fonts", +" --height=size, --width=size Size of output bitmap", +" --help Show this help", +" --magnification=n Magnification parameter for DVI file", +" -n, --preamble-only Read only the DVI pre- and postamble", +" --output=filename-pattern Specify pattern for output bitmaps", +" --output-type=type Type of output bitmap, cf --query=types", +" --paper-size=string Preset bitmap size, cf --query=paper", +" --process=[no][dvi,postamble,blur,transparent,crop]|options", +" Processing to do", +" --query=[missing-fonts|missing-fontgen|all-fonts|all-fontgen|f|F|g|G]", +" Display missing/present fonts", +" --query=bitmaps Log bitmaps generated", +" --query=paper Show predefiend `paper' sizes", +" --query=types Show available bitmap formats", +" --resolution=n Output resolution, pixels-per-inch", +" --start-page=n, --end-page=n, --page-range=spec", +" Control which pages are processed", +" --scaledown=n Scale output bitmap down by n", +" --verbose=[normal|quiet|silent] Suppress chatter", +" -V, --version Show version and configuration info", +" (debug flags: b=bitmap, d=DVI file, g=everything, i=input, m=main", +" p=fonts and kpse, r=rasterdata, u=utilities)", + }; + int nstrings = sizeof(helpstrings)/sizeof(helpstrings[0]); + for (int i=0; i<nstrings; i++) + cerr << helpstrings[i] << endl; +} + +// void Usage (string msg) +// { +// cerr << "Error: " << msg << endl; +// Usage(true); +// } +void Usage (const char* msg) +{ + cerr << "Error: " << msg << endl; + Usage(true); +} +void Usage (bool andExit) +{ + cerr << "Usage: " << progname << " [flags...] filename" << endl; +#if 0 + cerr << "Usage: " << progname << " [-b(h|w) size] [-bp a4|a4l|usletter...]" << endl << +" [-[Cc][lrtb] size] [-fp PKpath ] [-fm mfmode] [-fg] [-fG]" << endl << +" [-g[dpribmg]] [-l num] [-m magmag ] [-n[n]] [-o outfile-pattern]" << endl << +" [-p num] [-pp ranges] [-P[bBtTcC]] [-q[q]] [-Q[FfGgtbp]]" << endl << +" [-r resolution] [-R[fb] int,int,int] [-s scale-factor]" << endl << +" [-t xbm" +#ifdef ENABLE_GIF + << "|gif" +#endif +#ifdef ENABLE_PNG + << "|png" +#endif + << "] [-V]" << endl << +" dvifile" << endl << endl << +" -bh, -bw page height and width in pixels -bp bitmap <-- papersize" << endl << +" -c crop margins left, right, top, bottom -C crop absolute" << endl << +" -fp set font-path (DVI2BITMAP_PK_PATH) -fg switch off (-fG=on) fontgen" << endl << +" -fm Metafont mode (must match -r) -r Metafont resolution" << endl << +" -m DVI file magnification -s scale-down of bitmap" << endl << +" -o set output file pattern, including %d -g debugging of sections" << endl << +" -n only process DVI file preamble -nn only process-options" << endl << +" -q switch off chatter and warnings -qq switch off errors, too" << endl << +" -R set foreground/background colour (RGB) -V version+features then exit" << endl << +" -l num, -p num, -pp ranges select pages to process (l--p, or ranges pp)" << endl << +" -Q query: f=missing fonts, g=missing font commands (F, G=all fonts)" << endl << +" t=supported output types, b=output bitmap names, p=paper sizes in -bp" << endl << +" -P Processing: b=blur bitmap, t=set transparent, c=do cropping (BTC->off)" << endl; +#endif + if (andExit) + exit(1); +} |