diff options
Diffstat (limited to 'src/roff/groff/groff.cpp')
-rw-r--r-- | src/roff/groff/groff.cpp | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/src/roff/groff/groff.cpp b/src/roff/groff/groff.cpp new file mode 100644 index 0000000..4eb7329 --- /dev/null +++ b/src/roff/groff/groff.cpp @@ -0,0 +1,866 @@ +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +groff is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or +(at your option) any later version. + +groff is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +// A front end for groff. + +#include "lib.h" + +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> + +#include "errarg.h" +#include "error.h" +#include "stringclass.h" +#include "cset.h" +#include "font.h" +#include "device.h" +#include "pipeline.h" +#include "nonposix.h" +#include "relocate.h" +#include "defs.h" + +#define GXDITVIEW "gxditview" + +// troff will be passed an argument of -rXREG=1 if the -X option is +// specified +#define XREG ".X" + +#ifdef NEED_DECLARATION_PUTENV +extern "C" { + int putenv(const char *); +} +#endif /* NEED_DECLARATION_PUTENV */ + +// The number of commands must be in sync with MAX_COMMANDS in +// pipeline.h. + +// grap, chem, and ideal must come before pic; +// tbl must come before eqn +const int PRECONV_INDEX = 0; +const int SOELIM_INDEX = PRECONV_INDEX + 1; +const int REFER_INDEX = SOELIM_INDEX + 1; +const int GRAP_INDEX = REFER_INDEX + 1; +const int CHEM_INDEX = GRAP_INDEX + 1; +const int IDEAL_INDEX = CHEM_INDEX + 1; +const int PIC_INDEX = IDEAL_INDEX + 1; +const int TBL_INDEX = PIC_INDEX + 1; +const int GRN_INDEX = TBL_INDEX + 1; +const int EQN_INDEX = GRN_INDEX + 1; +const int TROFF_INDEX = EQN_INDEX + 1; +const int POST_INDEX = TROFF_INDEX + 1; +const int SPOOL_INDEX = POST_INDEX + 1; + +const int NCOMMANDS = SPOOL_INDEX + 1; + +class possible_command { + char *name; + string args; + char **argv; + + void build_argv(); +public: + possible_command(); + ~possible_command(); + void clear_name(); + void set_name(const char *); + void set_name(const char *, const char *); + const char *get_name(); + void append_arg(const char *, const char * = 0 /* nullptr */); + void insert_arg(const char *); + void insert_args(string s); + void clear_args(); + char **get_argv(); + void print(int is_last, FILE *fp); +}; + +extern "C" const char *Version_string; + +int lflag = 0; +char *spooler = 0 /* nullptr */; +char *postdriver = 0 /* nullptr */; +char *predriver = 0 /* nullptr */; +bool need_postdriver = true; +char *saved_path = 0 /* nullptr */; +char *groff_bin_path = 0 /* nullptr */; +char *groff_font_path = 0 /* nullptr */; + +possible_command commands[NCOMMANDS]; + +int run_commands(int no_pipe); +void print_commands(FILE *); +void append_arg_to_string(const char *arg, string &str); +void handle_unknown_desc_command(const char *command, const char *arg, + const char *filename, int lineno); +const char *xbasename(const char *); + +void usage(FILE *stream); + +static char *xstrdup(const char *s) { + if (0 /* nullptr */ == s) + return const_cast<char *>(s); + char *str = strdup(s); + if (0 /* nullptr */ == str) + fatal("unable to copy string: %1", strerror(errno)); + return str; +} + +static void xputenv(const char *s) { + if (putenv(const_cast<char *>(s)) != 0) + fatal("unable to write to environment: %1", strerror(errno)); + return; +} + +static void xexit(int status) { + free(spooler); + free(predriver); + free(postdriver); + free(saved_path); + free(groff_bin_path); + free(groff_font_path); + exit(status); +} + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + assert(NCOMMANDS <= MAX_COMMANDS); + string Pargs, Largs, Fargs; + int Kflag = 0; + int vflag = 0; + int Vflag = 0; + int zflag = 0; + int iflag = 0; + int Xflag = 0; + int oflag = 0; + int safer_flag = 1; + int is_xhtml = 0; + int eflag = 0; + int need_pic = 0; + int opt; + const char *command_prefix = getenv("GROFF_COMMAND_PREFIX"); + const char *encoding = getenv("GROFF_ENCODING"); + if (!command_prefix) + command_prefix = PROG_PREFIX; + commands[TROFF_INDEX].set_name(command_prefix, "troff"); + static const struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((opt = getopt_long( + argc, argv, + "abcCd:D:eEf:F:gGhiI:jJkK:lL:m:M:" + "n:No:pP:r:RsStT:UvVw:W:XzZ", + long_options, NULL)) + != EOF) { + char buf[3]; + buf[0] = '-'; + buf[1] = opt; + buf[2] = '\0'; + switch (opt) { + case 'i': + iflag = 1; + break; + case 'I': + commands[GRN_INDEX].set_name(command_prefix, "grn"); + commands[GRN_INDEX].append_arg("-M", optarg); + commands[SOELIM_INDEX].set_name(command_prefix, "soelim"); + commands[SOELIM_INDEX].append_arg(buf, optarg); + // .psbb may need to search for files + commands[TROFF_INDEX].append_arg(buf, optarg); + // \X'ps:import' may need to search for files + Pargs += buf; + Pargs += optarg; + Pargs += '\0'; + break; + case 'D': + commands[PRECONV_INDEX].set_name("preconv"); + commands[PRECONV_INDEX].append_arg("-D", optarg); + break; + case 'K': + commands[PRECONV_INDEX].append_arg("-e", optarg); + Kflag = 1; + // fall through + case 'k': + commands[PRECONV_INDEX].set_name("preconv"); + break; + case 't': + commands[TBL_INDEX].set_name(command_prefix, "tbl"); + break; + case 'J': + // commands[IDEAL_INDEX].set_name(command_prefix, "gideal"); + // need_pic = 1; + break; + case 'j': + commands[CHEM_INDEX].set_name(command_prefix, "chem"); + need_pic = 1; + break; + case 'p': + commands[PIC_INDEX].set_name(command_prefix, "pic"); + break; + case 'g': + commands[GRN_INDEX].set_name(command_prefix, "grn"); + break; + case 'G': + commands[GRAP_INDEX].set_name(command_prefix, "grap"); + need_pic = 1; + break; + case 'e': + eflag = 1; + commands[EQN_INDEX].set_name(command_prefix, "eqn"); + break; + case 's': + commands[SOELIM_INDEX].set_name(command_prefix, "soelim"); + break; + case 'R': + commands[REFER_INDEX].set_name(command_prefix, "refer"); + break; + case 'z': + case 'a': + commands[TROFF_INDEX].append_arg(buf); + // fall through + case 'Z': + zflag++; + need_postdriver = false; + break; + case 'l': + lflag++; + break; + case 'V': + Vflag++; + break; + case 'v': + vflag = 1; + printf("GNU groff version %s\n", Version_string); + printf( + "Copyright (C) 2022 Free Software Foundation, Inc.\n" + "GNU groff comes with ABSOLUTELY NO WARRANTY.\n" + "You may redistribute copies of groff and its subprograms\n" + "under the terms of the GNU General Public License.\n" + "For more information about these matters, see the file\n" + "named COPYING.\n"); + printf("\ncalled subprograms:\n\n"); + fflush(stdout); + // Pass -v to all possible subprograms + commands[PRECONV_INDEX].append_arg(buf); + commands[CHEM_INDEX].append_arg(buf); + commands[IDEAL_INDEX].append_arg(buf); + commands[POST_INDEX].append_arg(buf); + // fall through + case 'C': + commands[SOELIM_INDEX].append_arg(buf); + commands[REFER_INDEX].append_arg(buf); + commands[PIC_INDEX].append_arg(buf); + commands[GRAP_INDEX].append_arg(buf); + commands[TBL_INDEX].append_arg(buf); + commands[GRN_INDEX].append_arg(buf); + commands[EQN_INDEX].append_arg(buf); + commands[TROFF_INDEX].append_arg(buf); + break; + case 'N': + commands[EQN_INDEX].append_arg(buf); + break; + case 'h': + usage(stdout); + break; + case 'E': + case 'b': + commands[TROFF_INDEX].append_arg(buf); + break; + case 'c': + commands[TROFF_INDEX].append_arg(buf); + break; + case 'S': + safer_flag = 1; + break; + case 'U': + safer_flag = 0; + break; + case 'T': + if (strcmp(optarg, "xhtml") == 0) { + // force soelim to aid the html preprocessor + commands[SOELIM_INDEX].set_name(command_prefix, "soelim"); + Pargs += "-x"; + Pargs += '\0'; + Pargs += 'x'; + Pargs += '\0'; + is_xhtml = 1; + device = "html"; + break; + } + if (strcmp(optarg, "html") == 0) + // force soelim to aid the html preprocessor + commands[SOELIM_INDEX].set_name(command_prefix, "soelim"); + if (strcmp(optarg, "Xps") == 0) { + warning("-TXps option is obsolete: use -X -Tps instead"); + device = "ps"; + Xflag++; + } + else + device = optarg; + break; + case 'F': + font::command_line_font_dir(optarg); + if (Fargs.length() > 0) { + Fargs += PATH_SEP_CHAR; + Fargs += optarg; + } + else + Fargs = optarg; + break; + case 'o': + oflag = 1; + // fall through + case 'f': + case 'm': + case 'r': + case 'd': + case 'n': + case 'w': + case 'W': + commands[TROFF_INDEX].append_arg(buf, optarg); + break; + case 'M': + commands[EQN_INDEX].append_arg(buf, optarg); + commands[GRAP_INDEX].append_arg(buf, optarg); + commands[GRN_INDEX].append_arg(buf, optarg); + commands[TROFF_INDEX].append_arg(buf, optarg); + break; + case 'P': + Pargs += optarg; + Pargs += '\0'; + break; + case 'L': + append_arg_to_string(optarg, Largs); + break; + case 'X': + Xflag++; + need_postdriver = false; + break; + case '?': + usage(stderr); + xexit(EXIT_FAILURE); + break; + default: + assert(0 == "no case to handle option character"); + break; + } + } + if (need_pic) + commands[PIC_INDEX].set_name(command_prefix, "pic"); + if (encoding) { + commands[PRECONV_INDEX].set_name("preconv"); + if (!Kflag && *encoding) + commands[PRECONV_INDEX].append_arg("-e", encoding); + } + if (!safer_flag) { + commands[TROFF_INDEX].insert_arg("-U"); + commands[PIC_INDEX].append_arg("-U"); + } + font::set_unknown_desc_command_handler(handle_unknown_desc_command); + const char *desc = font::load_desc(); + if (0 /* nullptr */ == desc) + fatal("cannot load 'DESC' description file for device '%1'", + device); + if (need_postdriver && (0 /* nullptr */ == postdriver)) + fatal_with_file_and_line(desc, 0, "device description file missing" + " 'postpro' directive"); + if (predriver && !zflag) { + commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name()); + commands[TROFF_INDEX].set_name(predriver); + // pass the device arguments to the predrivers as well + commands[TROFF_INDEX].insert_args(Pargs); + if (eflag && is_xhtml) + commands[TROFF_INDEX].insert_arg("-e"); + if (vflag) + commands[TROFF_INDEX].insert_arg("-v"); + } + const char *real_driver = 0 /* nullptr */; + if (Xflag) { + real_driver = postdriver; + postdriver = xstrdup(GXDITVIEW); // so we can free() it in xexit() + commands[TROFF_INDEX].append_arg("-r" XREG "=", "1"); + } + if (postdriver) + commands[POST_INDEX].set_name(postdriver); + int gxditview_flag = postdriver + && strcmp(xbasename(postdriver), GXDITVIEW) == 0; + if (gxditview_flag && argc - optind == 1) { + commands[POST_INDEX].append_arg("-title"); + commands[POST_INDEX].append_arg(argv[optind]); + commands[POST_INDEX].append_arg("-xrm"); + commands[POST_INDEX].append_arg("*iconName:", argv[optind]); + string filename_string("|"); + append_arg_to_string(argv[0], filename_string); + append_arg_to_string("-Z", filename_string); + for (int i = 1; i < argc; i++) + append_arg_to_string(argv[i], filename_string); + filename_string += '\0'; + commands[POST_INDEX].append_arg("-filename"); + commands[POST_INDEX].append_arg(filename_string.contents()); + } + if (gxditview_flag && Xflag) { + string print_string(real_driver); + if (spooler) { + print_string += " | "; + print_string += spooler; + print_string += Largs; + } + print_string += '\0'; + commands[POST_INDEX].append_arg("-printCommand"); + commands[POST_INDEX].append_arg(print_string.contents()); + } + const char *p = Pargs.contents(); + const char *end = p + Pargs.length(); + while (p < end) { + commands[POST_INDEX].append_arg(p); + p = strchr(p, '\0') + 1; + } + if (gxditview_flag) + commands[POST_INDEX].append_arg("-"); + if (lflag && !vflag && !Xflag && spooler) { + commands[SPOOL_INDEX].set_name(BSHELL); + commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C); + Largs += '\0'; + Largs = spooler + Largs; + commands[SPOOL_INDEX].append_arg(Largs.contents()); + } + if (zflag) { + commands[POST_INDEX].set_name(0 /* nullptr */); + commands[SPOOL_INDEX].set_name(0 /* nullptr */); + } + commands[TROFF_INDEX].append_arg("-T", device); + if (strcmp(device, "html") == 0) { + if (is_xhtml) { + if (oflag) + fatal("'-o' option is invalid with device 'xhtml'"); + if (zflag) + commands[EQN_INDEX].append_arg("-Tmathml:xhtml"); + else if (eflag) + commands[EQN_INDEX].clear_name(); + } + else { + if (oflag) + fatal("'-o' option is invalid with device 'html'"); + // html renders equations as images via ps + commands[EQN_INDEX].append_arg("-Tps:html"); + } + } + else + commands[EQN_INDEX].append_arg("-T", device); + + commands[GRN_INDEX].append_arg("-T", device); + + int first_index; + for (first_index = 0; first_index < TROFF_INDEX; first_index++) + if (commands[first_index].get_name() != 0 /* nullptr */) + break; + if (optind < argc) { + if (argv[optind][0] == '-' && argv[optind][1] != '\0') + commands[first_index].append_arg("--"); + for (int i = optind; i < argc; i++) + commands[first_index].append_arg(argv[i]); + if (iflag) + commands[first_index].append_arg("-"); + } + if (Fargs.length() > 0) { + string e = "GROFF_FONT_PATH"; + e += '='; + e += Fargs; + char *fontpath = getenv("GROFF_FONT_PATH"); + if (fontpath && *fontpath) { + e += PATH_SEP_CHAR; + e += fontpath; + } + e += '\0'; + groff_font_path = xstrdup(e.contents()); + xputenv(groff_font_path); + } + { + // we save the original path in GROFF_PATH__ and put it into the + // environment -- troff will pick it up later. + char *path = getenv("PATH"); + string g = "GROFF_PATH__"; + g += '='; + if (path && *path) + g += path; + g += '\0'; + saved_path = xstrdup(g.contents()); + xputenv(saved_path); + char *binpath = getenv("GROFF_BIN_PATH"); + string f = "PATH"; + f += '='; + if (binpath && *binpath) + f += binpath; + else { + binpath = relocatep(BINPATH); + f += binpath; + } + if (path && *path) { + f += PATH_SEP_CHAR; + f += path; + } + f += '\0'; + groff_bin_path = xstrdup(f.contents()); + xputenv(groff_bin_path); + } + if (Vflag) + print_commands(Vflag == 1 ? stdout : stderr); + if (Vflag == 1) + xexit(EXIT_SUCCESS); + xexit(run_commands(vflag)); +} + +const char *xbasename(const char *s) +{ + if (!s) + return 0 /* nullptr */; + // DIR_SEPS[] are possible directory separator characters; see + // nonposix.h. We want the rightmost separator of all possible ones. + // Example: d:/foo\\bar. + const char *p = strrchr(s, DIR_SEPS[0]), *p1; + const char *sep = &DIR_SEPS[1]; + + while (*sep) + { + p1 = strrchr(s, *sep); + if (p1 && (!p || p1 > p)) + p = p1; + sep++; + } + return p ? p + 1 : s; +} + +void handle_unknown_desc_command(const char *command, const char *arg, + const char *filename, int lineno) +{ + if (strcmp(command, "print") == 0) { + if (arg == 0 /* nullptr */) + error_with_file_and_line(filename, lineno, "'print' directive" + " requires an argument"); + else + spooler = xstrdup(arg); + return; + } + if (strcmp(command, "prepro") == 0) { + if (arg == 0 /* nullptr */) + error("'prepro' directive requires an argument"); + else { + for (const char *p = arg; *p; p++) + if (csspace(*p)) { + error_with_file_and_line(filename, lineno, "invalid 'prepro'" + " directive argument '%1': program" + " name required", arg); + } + predriver = xstrdup(arg); + } + return; + } + if (strcmp(command, "postpro") == 0) { + if (arg == 0 /* nullptr */) + error_with_file_and_line(filename, lineno, "'postpro' directive" + " requires an argument"); + else { + for (const char *p = arg; *p; p++) + if (csspace(*p)) { + error_with_file_and_line(filename, lineno, "invalid 'postpro'" + " directive argument '%1': program" + " name required", arg); + return; + } + postdriver = xstrdup(arg); + } + return; + } +} + +void print_commands(FILE *fp) +{ + int last; + for (last = SPOOL_INDEX; last >= 0; last--) + if (commands[last].get_name() != 0 /* nullptr */) + break; + for (int i = 0; i <= last; i++) + if (commands[i].get_name() != 0 /* nullptr */) + commands[i].print(i == last, fp); +} + +// Run the commands. Return the code with which to exit. + +int run_commands(int no_pipe) +{ + char **v[NCOMMANDS]; // vector of argv arrays to pipe together + int ncommands = 0; + for (int i = 0; i < NCOMMANDS; i++) + if (commands[i].get_name() != 0 /* nullptr */) + v[ncommands++] = commands[i].get_argv(); + return run_pipeline(ncommands, v, no_pipe); +} + +possible_command::possible_command() +: name(0), argv(0) +{ +} + +possible_command::~possible_command() +{ + free(name); + delete[] argv; +} + +void possible_command::set_name(const char *s) +{ + free(name); + name = xstrdup(s); +} + +void possible_command::clear_name() +{ + delete[] name; + delete[] argv; + name = NULL; + argv = NULL; +} + +void possible_command::set_name(const char *s1, const char *s2) +{ + free(name); + name = (char*)malloc(strlen(s1) + strlen(s2) + 1); + strcpy(name, s1); + strcat(name, s2); +} + +const char *possible_command::get_name() +{ + return name; +} + +void possible_command::clear_args() +{ + args.clear(); +} + +void possible_command::append_arg(const char *s, const char *t) +{ + args += s; + if (t) + args += t; + args += '\0'; +} + +void possible_command::insert_arg(const char *s) +{ + string str(s); + str += '\0'; + str += args; + args = str; +} + +void possible_command::insert_args(string s) +{ + const char *p = s.contents(); + const char *end = p + s.length(); + int l = 0; + if (p >= end) + return; + // find the total number of arguments in our string + do { + l++; + p = strchr(p, '\0') + 1; + } while (p < end); + // now insert each argument preserving the order + for (int i = l - 1; i >= 0; i--) { + p = s.contents(); + for (int j = 0; j < i; j++) + p = strchr(p, '\0') + 1; + insert_arg(p); + } +} + +void possible_command::build_argv() +{ + if (argv) + return; + // Count the number of arguments. + int len = args.length(); + int argc = 1; + char *p = 0 /* nullptr */; + if (len > 0) { + p = &args[0]; + for (int i = 0; i < len; i++) + if (p[i] == '\0') + argc++; + } + // Build an argument vector. + argv = new char *[argc + 1]; + argv[0] = name; + for (int i = 1; i < argc; i++) { + argv[i] = p; + p = strchr(p, '\0') + 1; + } + argv[argc] = 0 /* nullptr */; +} + +void possible_command::print(int is_last, FILE *fp) +{ + build_argv(); + if (IS_BSHELL(argv[0]) + && argv[1] != 0 /* nullptr */ + && strcmp(argv[1], BSHELL_DASH_C) == 0 + && argv[2] != 0 /* nullptr */ && argv[3] == 0 /* nullptr */) + fputs(argv[2], fp); + else { + fputs(argv[0], fp); + string str; + for (int i = 1; argv[i] != 0 /* nullptr */; i++) { + str.clear(); + append_arg_to_string(argv[i], str); + put_string(str, fp); + } + } + if (is_last) + putc('\n', fp); + else + fputs(" | ", fp); +} + +void append_arg_to_string(const char *arg, string &str) +{ + str += ' '; + int needs_quoting = 0; + // Native Windows programs don't support '..' style of quoting, so + // always behave as if ARG included the single quote character. +#if defined(_WIN32) && !defined(__CYGWIN__) + int contains_single_quote = 1; +#else + int contains_single_quote = 0; +#endif + const char*p; + for (p = arg; *p != '\0'; p++) + switch (*p) { + case ';': + case '&': + case '(': + case ')': + case '|': + case '^': + case '<': + case '>': + case '\n': + case ' ': + case '\t': + case '\\': + case '"': + case '$': + case '?': + case '*': + needs_quoting = 1; + break; + case '\'': + contains_single_quote = 1; + break; + } + if (contains_single_quote || arg[0] == '\0') { + str += '"'; + for (p = arg; *p != '\0'; p++) + switch (*p) { +#if !(defined(_WIN32) && !defined(__CYGWIN__)) + case '"': + case '\\': + case '$': + str += '\\'; +#else + case '"': + case '\\': + if (*p == '"' || (*p == '\\' && p[1] == '"')) + str += '\\'; +#endif + // fall through + default: + str += *p; + break; + } + str += '"'; + } + else if (needs_quoting) { + str += '\''; + str += arg; + str += '\''; + } + else + str += arg; +} + +char **possible_command::get_argv() +{ + build_argv(); + return argv; +} + +void usage(FILE *stream) +{ + // Add `J` to the cluster if we ever get ideal(1) support. + fprintf(stream, +"usage: %s [-abcCeEgGijklNpRsStUVXzZ] [-d ctext] [-d string=text]" +" [-D fallback-encoding] [-f font-family] [-F font-directory]" +" [-I inclusion-directory] [-K input-encoding] [-L spooler-argument]" +" [-m macro-package] [-M macro-directory] [-n page-number]" +" [-o page-list] [-P postprocessor-argument] [-r cnumeric-expression]" +" [-r register=numeric-expression] [-T output-device]" +" [-w warning-category] [-W warning-category]" +" [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s {-h | --help}\n", + program_name, program_name, program_name); + if (stdout == stream) { + fputs( +"\n" +"groff (GNU roff) is a typesetting system that reads plain text input\n" +"files that include formatting commands to produce output in\n" +"PostScript, PDF, HTML, or DVI formats or for display to a terminal.\n" +"See the groff(1) manual page.\n", + stream); + exit(EXIT_SUCCESS); + } +} + +extern "C" { + +void c_error(const char *format, const char *arg1, const char *arg2, + const char *arg3) +{ + error(format, arg1, arg2, arg3); +} + +void c_fatal(const char *format, const char *arg1, const char *arg2, + const char *arg3) +{ + fatal(format, arg1, arg2, arg3); +} + +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: |