/* 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 #include #include #include #ifdef HAVE_CSTD_INCLUDE # include # include # include # include # include #else # include // for vsprintf # include # include # include # include #endif #include #include // 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(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 ? "" : 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(magmag*papersizes[i].h+0.5); bitmapW = static_cast(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(fileResolution * dvif->magnification()); initialisedInch = true; } if (DviFilePage *test = dynamic_cast(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(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(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(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(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(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(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(tside), dimen, absolute); for (int tside=0; tside<4; tside++) bitmap->crop (static_cast(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(STD::strtol (s->c_str(), 0, 0)); s++; if (s == l.end()) { stringOK = false; break; } rgb.green = static_cast(STD::strtol (s->c_str(), 0, 0)); s++; if (s == l.end()) { stringOK = false; break; } rgb.blue = static_cast(STD::strtol (s->c_str(), 0, 0)); if (stringOK) { if (verbosity > normal) cerr << "Set " << (setDefault ? "(default) " : "") << (isfg ? "foreground" : "background") << " to " << static_cast(rgb.red) << ',' << static_cast(rgb.green) << ',' << static_cast(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 (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++ definition does _not_ list snprintf as one of * the members, though it is listed in the definition of * 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; ioff)" << endl; #endif if (andExit) exit(1); }