/
niceSOFT
/
groff
Обзор
Документация
Войти
/
niceSOFT
/
groff
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/devices/grohtml/post-html.cpp
6 255 строк
159 KB
G. Branden Robinson
[grohtml]: Keep images work-withable.
14 июл 2026, 00:49
14 июл 2026, 00:49
48ff393
Код
Авторство
О чём код?
/* Copyright 2000-2007 Free Software Foundation, Inc. * 2021-2025 G. Branden Robinson * 2023 TANAKA Takuji * * Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp * but it owes a huge amount of ideas and raw code from * James Clark (jjc@jclark.com) grops/ps.cpp. */ /* This file is part of groff, the GNU roff typesetting system. 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/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <assert.h> #include <errno.h> #include <stdio.h> // EOF, FILE, fclose(), fflush(), fopen(), freopen(), // fseek(), SEEK_SET, setbuf(), stderr, stdout #include <stdlib.h> // abs(), atoi(), EXIT_SUCCESS, exit(), free() #include <string.h> // strcmp(), strdup(), strerror(), strlen(), // strncmp() #include <strings.h> // strcasecmp() #include <time.h> // asctime(), tm #include <getopt.h> // getopt_long() #include <new> // std::bad_alloc #include <stack> #include <vector> // libgroff #include "symbol.h" // prerequisite of color.h #include "color.h" #include "cset.h" // csspace() #include "curtime.h" #include "lib.h" // strsave(), xtmpfile() #include "stringclass.h" #include "unicode.h" // libdriver #include "driver.h" // interpret_troff_output_file() #include "printer.h" // environment, printer // grohtml #include "html.h" #include "html-table.h" #include "html-text.h" extern "C" const char *Version_string; #define MAX_LINE_LENGTH 60 /* maximum characters we want in a line */ #define SIZE_INCREMENT 2 /* font size increment <big> = +2 */ #define CENTER_TOLERANCE 2 /* how many pixels off center do we allow */ #define ANCHOR_TEMPLATE "heading" /* if simple anchor is set we use this */ #define UNICODE_DESC_START 0x80 /* all character entities above this are */ /* either encoded by their glyph names or if */ /* there is no name then we use &#nnn; */ typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT; typedef enum {col_tag, tab_tag, tab0_tag, none_tag} colType; #undef DEBUG_TABLES // #define DEBUG_TABLES /* * prototypes */ const char *get_html_translation (font *f, const string &name); static const char *get_html_entity(unsigned int code); int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single); static int auto_links = true; /* by default we enable automatic links at */ /* top of the document. */ static int auto_rule = true; /* by default we enable an automatic rule */ /* at the top and bottom of the document */ static int simple_anchors = false; /* default to anchors with heading text */ static int manufacture_headings = false; /* default is to use the Hn html headings, */ /* rather than manufacture our own. */ static int do_write_creator_comment = true; /* write Creator HTML comment */ static int do_write_date_comment = true; /* write CreationDate HTML comment */ /* has user requested initial bg color? */ static color *default_background = 0 /* nullptr */; static string job_name; /* if set then the output is split into */ /* multiple files with 'job_name'-%d.html */ // TODO: boolify static int multiple_files = false; /* must we the output be divided into */ /* multiple html files, one for each */ /* heading? */ static int base_point_size = 0; /* which troff font size maps onto html */ /* size 3? */ static int split_level = 2; /* what heading level to split at? */ static string head_info; /* user supplied information to be placed */ /* into <head> </head> */ static int valid_flag = false; /* has user requested a valid flag at the */ /* end of each page? */ static int groff_sig = false; /* "This document was produced using" */ html_dialect dialect = html4; /* which html dialect should grohtml output */ // TODO: Make this an enum. static const int CHARSET_ASCII = 0; static const int CHARSET_MIXED = 1; static const int CHARSET_UTF8 = 2; static int charset_encoding = CHARSET_MIXED;/* The character set may be plain ASCII, */ /* pure UTF-8, or a mixture of character */ /* entity references. */ /* * TODO: CHARSET_MIXED doesn't seem to govern the output in any way. * Everywhere `charset_encoding` is tested, it is cast to a `bool`, so * all we're really choosing between is ASCII and UTF-8. Can we get rid * of CHARSET_MIXED? */ /* * start with a few favorites */ static void stop () {} static int min (int a, int b) { if (a < b) return a; else return b; } static int max (int a, int b) { if (a > b) return a; else return b; } /* * is_intersection - returns true if range a1..a2 intersects with * b1..b2 */ // TODO: boolify static int is_intersection (int a1, int a2, int b1, int b2) { // easier to prove NOT outside limits return ! ((a1 > b2) || (a2 < b1)); } /* * is_digit - returns true if character, ch, is a digit. */ // TODO: boolify static int is_digit (char ch) { return (ch >= '0') && (ch <= '9'); } /* * the classes and methods for maintaining a list of files. */ struct file { FILE *fp; file *next; int new_output_file; int require_links; string output_file_name; file (FILE * /* f */); }; /* * file - initialize all fields to null pointers */ file::file (FILE * f) : fp(f), next(0 /* nullptr */), new_output_file(false), require_links(false), output_file_name("") { } // TODO: Explicit `void` parameter lists are unnecessary and // inconsistent with other groff code style. class files { public: files (); FILE *get_file (void); void start_of_list (void); void move_next (void); void add_new_file (FILE * /* f */); void set_file_name (string /* name */); void set_links_required (void); int are_links_required (void); int is_new_output_file (void); string file_name (void); string next_file_name (void); private: file *head; file *tail; file *ptr; }; /* * files - create an empty list of files. */ files::files () : head(0 /* nullptr */), tail(0 /* nullptr */), ptr(0 /* nullptr */) { } /* * get_file - returns the FILE associated with ptr. */ FILE *files::get_file (void) { if (ptr != 0 /* nullptr */) return ptr->fp; else return 0 /* nullptr */; } /* * start_of_list - reset the ptr to the start of the list. */ void files::start_of_list (void) { ptr = head; } /* * move_next - moves the ptr to the next element on the list. */ void files::move_next (void) { if (ptr != 0 /* nullptr */) ptr = ptr->next; } /* * add_new_file - adds a new file, f, to the list. */ void files::add_new_file (FILE *f) { assert(f != 0 /* nullptr */); if (0 /* nullptr */ == f) return; file *new_file = 0 /* nullptr */; try { new_file = new file(f); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for file object"); } if (0 /* nullptr */ == head) { head = new_file; tail = head; } else { tail->next = new_file; tail = tail->next; } ptr = tail; } /* * set_file_name - sets the final file name to contain the html * data to name. */ void files::set_file_name (string name) { if (ptr != 0 /* nullptr */) { ptr->output_file_name = name; ptr->new_output_file = true; } } /* * set_links_required - issue links when processing this component * of the file. */ void files::set_links_required (void) { if (ptr != 0 /* nullptr */) ptr->require_links = true; } /* * are_links_required - returns true if this section of the file * requires that links should be issued. */ // TODO: boolify int files::are_links_required (void) { if (ptr != 0 /* nullptr */) return ptr->require_links; return false; } /* * is_new_output_file - returns true if this component of the file * is the start of a new output file. */ // TODO: boolify int files::is_new_output_file (void) { if (ptr != 0 /* nullptr */) return ptr->new_output_file; return false; } /* * file_name - returns the name of the file. */ string files::file_name (void) { if (ptr != 0 /* nullptr */) return ptr->output_file_name; return string(""); } /* * next_file_name - returns the name of the next file. */ string files::next_file_name (void) { if ((ptr != 0 /* nullptr */) && (ptr->next != 0 /* nullptr */)) return ptr->next->output_file_name; return string(""); } /* * the class and methods for styles */ struct style { font *f; int point_size; int font_no; int height; int slant; color col; style (); style (font * /* p */, int /* sz */, int /* h */, int /* sl */, int /* no */, color /* c */); int operator == (const style & /* s */) const; int operator != (const style & /* s */) const; }; style::style() : f(0 /* nullptr */), point_size(-1) { } style::style(font *p, int sz, int h, int sl, int no, color c) : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c) { } int style::operator==(const style &s) const { return ((f == s.f) && (point_size == s.point_size) && (height == s.height) && (slant == s.slant) && (col == s.col)); } int style::operator!=(const style &s) const { return !(*this == s); } class char_buffer { public: char_buffer(); ~char_buffer(); char *add_string(const char * /* s */, unsigned int /* length */); char *add_string(const string & /* s */); private: // Simulate two views into the `char` buffer; one for the whole // buffer, and one for the list of individual C strings populating it. // It's a simulation because we support the latter by making _copies_ // of the string, which is inefficient. A better approach would be to // store indices from the start of `vec`, but doing that requires // retyping in this class's users. // TODO: Do the retyping and store the indices, not copies of strings. std::vector<char> vec; std::stack<char *> strings; }; // A block size of 4096 seems to run faster and with less variance than // the default, or 128, 256, 512, 1024, 2048, or 8192. // TODO: Use sysconf(PAGE_SIZE)? static const size_t char_buffer_block_size = 4096; char_buffer::char_buffer() { vec = std::vector<char>(char_buffer_block_size, '\0'); strings = std::stack<char *>(); } char_buffer::~char_buffer() { while (!strings.empty()) { free(strings.top()); strings.pop(); } } char *char_buffer::add_string (const char *s, unsigned int length) { if (length <= 0) return 0 /* nullptr */; if ((vec.capacity() - vec.size()) < length) vec.resize(vec.capacity() + char_buffer_block_size); for (size_t i = 0; i < length; i++) vec.push_back(s[i]); vec.push_back('\0'); char *p = strdup(s); strings.push(p); return p; } char *char_buffer::add_string (const string &s) { return add_string(s.contents(), s.length()); } /* * the classes and methods for maintaining glyph positions. */ class text_glob { public: void text_glob_html (style * /* s */, char * /* str */, int /* length */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); void text_glob_special (style * /* s */, char * /* str */, int /* length */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); void text_glob_line (style * /* s */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */, int /* thickness */); void text_glob_auto_image(style * /* s */, char * /* str */, int /* length */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); void text_glob_tag (style * /* s */, char * /* str */, int /* length */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); text_glob (void); ~text_glob (void); int is_a_line (void); int is_a_tag (void); int is_eol (void); int is_auto_img (void); int is_br (void); int is_in (void); int is_po (void); int is_ti (void); int is_ll (void); int is_ce (void); int is_tl (void); int is_eo_tl (void); int is_eol_ce (void); int is_col (void); int is_tab (void); int is_tab0 (void); int is_ta (void); int is_tab_ts (void); int is_tab_te (void); int is_nf (void); int is_fi (void); int is_eo_h (void); int get_arg (void); int get_tab_args (char * /* align */); void remember_table (html_table * /* t */); html_table *get_table (void); style text_style; const char *text_string; unsigned int text_length; int minv, minh, maxv, maxh; int is_tag; // is this a .br, .sp, .tl etc int is_img_auto; // image created by eqn delim int is_special; // text has come via 'x X html:' int is_line; // is the command a <line>? int thickness; // the thickness of a line html_table *tab; // table description private: text_glob (style * /* s */, const char * /* str */, int /* length */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */, bool /* is_troff_command */, bool /* is_auto_image */, bool /* is_special_command */, bool /* is_a_line */, int /* thickness */); }; text_glob::text_glob (style *s , const char *str, int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal, bool is_troff_command, bool is_auto_image, bool is_special_command, bool is_a_line_flag, int line_thickness) : text_style(*s), text_string(str), text_length(length), minv(min_vertical), minh(min_horizontal), maxv(max_vertical), maxh(max_horizontal), is_tag(is_troff_command), is_img_auto(is_auto_image), is_special(is_special_command), is_line(is_a_line_flag), thickness(line_thickness), tab(0 /* nullptr */) { } text_glob::text_glob () : text_string(0 /* nullptr */), text_length(0), minv(-1), minh(-1), maxv(-1), maxh(-1), is_tag(false), is_special(false), is_line(false), thickness(0), tab(0 /* nullptr */) { } text_glob::~text_glob () { delete tab; } /* * text_glob_html - used to place html text into the glob buffer. */ void text_glob::text_glob_html (style *s, char *str, int length, int min_vertical , int min_horizontal, int max_vertical , int max_horizontal) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(s, str, length, min_vertical, min_horizontal, max_vertical, max_horizontal, false, false, false, false, 0); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text_glob object" " of length %1", length); } *this = *g; delete g; } /* * text_glob_html - used to place html specials into the glob buffer. * This text is essentially html commands coming * through from the macro sets, with special * designated sequences of characters translated into * html. See add_and_encode. */ void text_glob::text_glob_special (style *s, char *str, int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(s, str, length, min_vertical, min_horizontal, max_vertical, max_horizontal, false, false, true, false, 0); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for special text_glob object" " of length %1", length); } *this = *g; delete g; } /* * text_glob_line - record horizontal draw line commands. */ void text_glob::text_glob_line (style *s, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal, int thickness_value) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(s, "", 0, min_vertical, min_horizontal, max_vertical, max_horizontal, false, false, false, true, thickness_value); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for line text_glob object"); } *this = *g; delete g; } /* * text_glob_auto_image - record the presence of a .auto-image tag * command. Used to mark that an image has been * created automatically by a preprocessor and * (pre-grohtml/troff) combination. Under some * circumstances images may not be created. * (consider .EQ * delim $$ * .EN * .TS * tab(!), center; * l!l. * $1 over x$!recripical of x * .TE * the first auto-image marker is created via * .EQ/.EN pair and no image is created. The * second auto-image marker occurs at $1 over * x$ Currently this image will not be created * as the whole of the table is created as an * image. (Once html tables are handled by * grohtml this will change. Shortly this will * be the case). */ void text_glob::text_glob_auto_image(style *s, char *str, int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(s, str, length, min_vertical, min_horizontal, max_vertical, max_horizontal, true, true, false, false, 0); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for auto_image text_glob object" " of length %1", length); } *this = *g; delete g; } /* * text_glob_tag - records a troff tag. */ void text_glob::text_glob_tag (style *s, char *str, int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(s, str, length, min_vertical, min_horizontal, max_vertical, max_horizontal, true, false, false, false, 0); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for tag text_glob object" " of length %1", length); } *this = *g; delete g; } /* * is_a_line - returns true if glob should be converted into an <hr> */ // TODO: boolify int text_glob::is_a_line (void) { return is_line; } /* * is_a_tag - returns true if glob contains a troff directive. */ // TODO: boolify int text_glob::is_a_tag (void) { return is_tag; } /* * is_eol - returns true if glob contains the tag eol */ // TODO: boolify int text_glob::is_eol (void) { return is_tag && (strcmp(text_string, "devtag:.eol") == 0); } /* * is_eol_ce - returns true if glob contains the tag eol.ce */ // TODO: boolify int text_glob::is_eol_ce (void) { return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0); } /* * is_tl - returns true if glob contains the tag .tl */ // TODO: boolify int text_glob::is_tl (void) { return is_tag && (strcmp(text_string, "devtag:.tl") == 0); } /* * is_eo_tl - returns true if glob contains the tag eo.tl */ // TODO: boolify int text_glob::is_eo_tl (void) { return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0); } /* * is_nf - returns true if glob contains the tag .fi 0 */ // TODO: boolify int text_glob::is_nf (void) { return is_tag && (strncmp(text_string, "devtag:.fi", strlen("devtag:.fi")) == 0) && (get_arg() == 0); } /* * is_fi - returns true if glob contains the tag .fi 1 */ // TODO: boolify int text_glob::is_fi (void) { return (is_tag && (strncmp(text_string, "devtag:.fi", strlen("devtag:.fi")) == 0) && (get_arg() == 1)); } /* * is_eo_h - returns true if glob contains the tag .eo.h */ // TODO: boolify int text_glob::is_eo_h (void) { return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0); } /* * is_ce - returns true if glob contains the tag .ce */ // TODO: boolify int text_glob::is_ce (void) { return is_tag && (strncmp(text_string, "devtag:.ce", strlen("devtag:.ce")) == 0); } /* * is_in - returns true if glob contains the tag .in */ // TODO: boolify int text_glob::is_in (void) { return is_tag && (strncmp(text_string, "devtag:.in ", strlen("devtag:.in ")) == 0); } /* * is_po - returns true if glob contains the tag .po */ // TODO: boolify int text_glob::is_po (void) { return is_tag && (strncmp(text_string, "devtag:.po ", strlen("devtag:.po ")) == 0); } /* * is_ti - returns true if glob contains the tag .ti */ // TODO: boolify int text_glob::is_ti (void) { return is_tag && (strncmp(text_string, "devtag:.ti ", strlen("devtag:.ti ")) == 0); } /* * is_ll - returns true if glob contains the tag .ll */ // TODO: boolify int text_glob::is_ll (void) { return is_tag && (strncmp(text_string, "devtag:.ll ", strlen("devtag:.ll ")) == 0); } /* * is_col - returns true if glob contains the tag .col */ // TODO: boolify int text_glob::is_col (void) { return is_tag && (strncmp(text_string, "devtag:.col", strlen("devtag:.col")) == 0); } /* * is_tab_ts - returns true if glob contains the tag .tab_ts */ // TODO: boolify int text_glob::is_tab_ts (void) { return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0); } /* * is_tab_te - returns true if glob contains the tag .tab_te */ // TODO: boolify int text_glob::is_tab_te (void) { return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0); } /* * is_ta - returns true if glob contains the tag .ta */ // TODO: boolify int text_glob::is_ta (void) { return is_tag && (strncmp(text_string, "devtag:.ta ", strlen("devtag:.ta ")) == 0); } /* * is_tab - returns true if glob contains the tag tab */ // TODO: boolify int text_glob::is_tab (void) { return is_tag && (strncmp(text_string, "devtag:tab ", strlen("devtag:tab ")) == 0); } /* * is_tab0 - returns true if glob contains the tag tab0 */ // TODO: boolify int text_glob::is_tab0 (void) { return is_tag && (strncmp(text_string, "devtag:tab0", strlen("devtag:tab0")) == 0); } /* * is_auto_img - returns true if the glob contains an automatically * generated image. */ // TODO: boolify int text_glob::is_auto_img (void) { return is_img_auto; } /* * is_br - returns true if the glob is a tag containing a .br * or an implied .br. Note that we do not include .nf or .fi * as grohtml will place a .br after these commands if they * should break the line. */ // TODO: boolify int text_glob::is_br (void) { return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) || (strncmp("devtag:.sp", text_string, strlen("devtag:.sp")) == 0)); } int text_glob::get_arg (void) { if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) { const char *p = text_string; while ((*p != '\0') && (!csspace(*p))) p++; while ((*p != '\0') && (csspace(*p))) p++; if ('\0' == *p) return -1; return atoi(p); } return -1; } /* * get_tab_args - returns the tab position and alignment of the tab tag */ int text_glob::get_tab_args (char *align) { assert(align != 0 /* nullptr */); if (0 /* nullptr */ == align) return 0; if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) { const char *p = text_string; // firstly the alignment C|R|L while ((*p != '\0') && (!csspace(*p))) p++; while ((*p != '\0') && (csspace(*p))) p++; *align = *p; // now the int value while ((*p != '\0') && (!csspace(*p))) p++; while ((*p != '\0') && (csspace(*p))) p++; if ('\0' == *p) return -1; return atoi(p); } return -1; } /* * remember_table - saves table, t, in the text_glob. */ void text_glob::remember_table (html_table *t) { delete tab; tab = t; } /* * get_table - returns the stored table description. */ html_table *text_glob::get_table (void) { return tab; } /* * the class and methods used to construct ordered double linked * lists. In a previous implementation we used templates via * #include "ordered-list.h", but this does assume that all C++ * compilers can handle this feature. Pragmatically it is safer to * assume this is not the case. */ struct element_list { element_list *right; element_list *left; text_glob *datum; int lineno; int minv, minh, maxv, maxh; element_list (text_glob * /* d */, int /* line_number */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); element_list (); ~element_list (); }; element_list::element_list () : right(0 /* nullptr */), left(0 /* nullptr */), datum(0 /* nullptr */), lineno(0), minv(-1), minh(-1), maxv(-1), maxh(-1) { } /* * element_list - create a list element assigning the datum and region * parameters. */ element_list::element_list (text_glob *in, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) : right(0 /* nullptr */), left( /* nullptr */0), datum(in), lineno(line_number), minv(min_vertical), minh(min_horizontal), maxv(max_vertical), maxh(max_horizontal) { } element_list::~element_list () { delete datum; } class list { public: list (); ~list (); int is_less (element_list * /* a */, element_list * /* b */); void add (text_glob * /* in */, int /* line_number */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); void sub_move_right (void); void move_right (void); void move_left (void); int is_empty (void); int is_equal_to_tail (void); int is_equal_to_head (void); void start_from_head (void); void start_from_tail (void); void insert (text_glob * /* in */); void move_to (text_glob * /* in */); text_glob *move_right_get_data (void); text_glob *move_left_get_data (void); text_glob *get_data (void); private: element_list *head; element_list *tail; element_list *ptr; }; /* * list - construct an empty list. */ list::list () : head(0 /* nullptr */), tail(0 /* nullptr */), ptr(0 /* nullptr */) { } /* * ~list - destroy a complete list. */ list::~list() { element_list *temp=head; do { temp = head; if (temp != 0 /* nullptr */) { head = head->right; delete temp; } } while ((head != 0 /* nullptr */) && (head != tail)); } /* * is_less - returns true if a is left of b if on the same line or * if a is higher up the page than b. */ // TODO: boolify int list::is_less (element_list *a, element_list *b) { assert(a != 0 /* nullptr */); assert(b != 0 /* nullptr */); if ((0 /* nullptr */ == a) || (0 /* nullptr */ == b)) return 0; // was: // if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) { if (a->lineno < b->lineno) { return true; } else if (a->lineno > b->lineno) { return false; } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) { return (a->minh < b->minh); } else { return (a->maxv < b->maxv); } } /* * add - adds a datum to the list in the order specified by the * region position. */ void list::add (text_glob *in, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { // create a new list element with datum and position fields // initialized element_list *t = 0 /* nullptr */; try { t = new element_list(in, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for element_list object"); } element_list *last; #if 0 fprintf(stderr, "[%s %d,%d,%d,%d] ", in->text_string, min_vertical, min_horizontal, max_vertical, max_horizontal); fflush(stderr); #endif if (0 /* nullptr */ == head) { head = t; tail = t; ptr = t; t->left = t; t->right = t; } else { last = tail; while ((last != head) && (is_less(t, last))) last = last->left; if (is_less(t, last)) { t->right = last; last->left->right = t; t->left = last->left; last->left = t; // now check for a new head if (last == head) head = t; } else { // add t beyond last t->right = last->right; t->left = last; last->right->left = t; last->right = t; // now check for a new tail if (last == tail) tail = t; } } } /* * sub_move_right - removes the element which is currently pointed to * by ptr from the list and moves ptr to the right. */ void list::sub_move_right (void) { element_list *t=ptr->right; if (head == tail) { head = 0; delete tail; tail = 0 /* nullptr */; ptr = 0 /* nullptr */; } else { if (head == ptr) head = head->right; if (tail == ptr) tail = tail->left; ptr->left->right = ptr->right; ptr->right->left = ptr->left; ptr = t; } } /* * start_from_head - assigns ptr to the head. */ void list::start_from_head (void) { ptr = head; } /* * start_from_tail - assigns ptr to the tail. */ void list::start_from_tail (void) { ptr = tail; } /* * is_empty - returns true if the list has no elements. */ // TODO: boolify int list::is_empty (void) { return 0 /* nullptr */ == head; } /* * is_equal_to_tail - returns true if the ptr equals the tail. */ // TODO: boolify int list::is_equal_to_tail (void) { return ptr == tail; } /* * is_equal_to_head - returns true if the ptr equals the head. */ // TODO: boolify int list::is_equal_to_head (void) { return ptr == head; } /* * move_left - moves the ptr left. */ void list::move_left (void) { ptr = ptr->left; } /* * move_right - moves the ptr right. */ void list::move_right (void) { ptr = ptr->right; } /* * get_datum - returns the datum referenced via ptr. */ text_glob* list::get_data (void) { return ptr->datum; } /* * move_right_get_data - returns the datum referenced via ptr and moves * ptr right. */ text_glob* list::move_right_get_data (void) { ptr = ptr->right; if (ptr == head) return 0; else return ptr->datum; } /* * move_left_get_data - returns the datum referenced via ptr and moves * ptr right. */ text_glob* list::move_left_get_data (void) { ptr = ptr->left; if (ptr == tail) return 0; else return ptr->datum; } /* * insert - inserts data after the current position. */ void list::insert (text_glob *in) { if (is_empty()) fatal("list must not be empty if we are inserting data"); else { if (0 /* nullptr */ == ptr) ptr = head; element_list *t = 0 /* nullptr */; try { t = new element_list(in, ptr->lineno, ptr->minv, ptr->minh, ptr->maxv, ptr->maxh); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for element_list object"); } if (ptr == tail) tail = t; ptr->right->left = t; t->right = ptr->right; ptr->right = t; t->left = ptr; } } /* * move_to - moves the current position to the point where data, in, * exists. This is an expensive method and should be used * sparingly. */ void list::move_to (text_glob *in) { ptr = head; while ((ptr != tail) && (ptr->datum != in)) ptr = ptr->right; } /* * page class and methods */ class page { public: page (void); void add (style * /* s */, const string & /* str */, int /* line_number */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); void add_tag (style * /* s */, const string & /* str */, int /* line_number */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */); void add_and_encode (style * /* s */, const string & /* str */, int /* line_number */, int /* min_vertical */, int /* min_horizontal */, int /* max_vertical */, int /* max_horizontal */, int /* is_tag */); // TODO: boolify? void add_line (style * /* s */, int /* line_number */, int /* x1 */, int /* y1 */, int /* x2 */, int /* y2 */, int /* thickness */); void insert_tag (const string & /* str */); void dump_page (void); // debugging method // and the data list glyphs; // position of glyphs and specials on page char_buffer buffer; // all characters for this page }; page::page() { } /* * insert_tag - inserts a tag after the current position. */ void page::insert_tag (const string &str) { if (str.length() > 0) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text_glob object"); } text_glob *f=glyphs.get_data(); g->text_glob_tag(&f->text_style, buffer.add_string(str), str.length(), f->minv, f->minh, f->maxv, f->maxh); glyphs.insert(g); } } /* * add - add html text to the list of glyphs. */ void page::add (style *s, const string &str, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { if (str.length() > 0) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text_glob object"); } g->text_glob_html(s, buffer.add_string(str), str.length(), min_vertical, min_horizontal, max_vertical, max_horizontal); glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal); } } /* * add_tag - adds a troff tag, for example: .tl .sp .br */ void page::add_tag (style *s, const string &str, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { if (str.length() > 0) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text_glob object"); } if (strncmp((str+'\0').contents(), "devtag:.auto-image", strlen("devtag:.auto-image")) == 0) g->text_glob_auto_image(s, buffer.add_string(str), str.length(), min_vertical, min_horizontal, max_vertical, max_horizontal); else g->text_glob_tag(s, buffer.add_string(str), str.length(), min_vertical, min_horizontal, max_vertical, max_horizontal); glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal); } } /* * add_line - adds the <line> primitive providing that y1==y2 */ void page::add_line (style *s, int line_number, int x_1, int y_1, int x_2, int y_2, int thickness) { if (y_1 == y_2) { text_glob *g = 0 /* nullptr */; try { g = new text_glob(); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text_glob object"); } g->text_glob_line(s, min(y_1, y_2), min(x_1, x_2), max(y_1, y_2), max(x_1, x_2), thickness); glyphs.add(g, line_number, min(y_1, y_2), min(x_1, x_2), max(y_1, y_2), max(x_1, x_2)); } } /* * to_numerical_char_ref - returns a numerical character reference of * unicode character code `ch`. */ static char *to_numerical_char_ref (unsigned int ch) { // Make static buffer large enough for a 64-bit `int` type in // hexadecimal (8 bytes) plus '&#x;' plus null terminator. static char buf[8 + 4 + 1]; sprintf(buf, "&#x%X;", ch); return buf; } /* * add_and_encode - adds a special string to the page, it translates * the string into html glyphs. The special string * will have come from x X html: and can contain troff * character encodings which appear as \[char]. A * sequence of \\ represents \. * So for example we can write: * "cost = \[Po]3.00 file = \\foo\\bar" * which is translated into: * "cost = £3.00 file = \foo\bar" */ void page::add_and_encode (style *s, const string &str, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal, int is_tag) { string html_string; const char *html_glyph; int i = 0; const int len = str.length(); assert(s != 0 /* nullptr */); if ((0 /* nullptr */ == s) || (0 /* nullptr */ == s->f)) return; while (i < len) { if ((i + 1 < len) && (str.substring(i, 2) == string("\\["))) { // start of escape i += 2; // move over \[ int a = i; while ((i < len) && (str[i] != ']')) i++; if (i > 0) { string troff_charname = str.substring(a, i - a); html_glyph = get_html_translation(s->f, troff_charname); if (html_glyph != 0 /* nullptr */) html_string += html_glyph; else { glyph *g = name_to_glyph((troff_charname + '\0').contents()); if (s->f->contains(g)) html_string += s->f->get_code(g); } } } else html_string += str[i]; i++; } html_string += '\0'; if (html_string.length() > 1 /* '\0' */) { text_glob *g=new text_glob(); if (is_tag) g->text_glob_tag(s, buffer.add_string(html_string), html_string.length(), min_vertical, min_horizontal, max_vertical, max_horizontal); else g->text_glob_special(s, buffer.add_string(html_string), html_string.length(), min_vertical, min_horizontal, max_vertical, max_horizontal); glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal); } } /* * dump_page - dump the page contents for debugging purposes. */ void page::dump_page(void) { #if defined(DEBUG_TABLES) text_glob *old_pos = glyphs.get_data(); text_glob *g; printf("\n<!--\n"); printf("\n\ndebugging start\n"); glyphs.start_from_head(); do { g = glyphs.get_data(); if (g->is_tab_ts()) { printf("\n\n"); if (g->get_table() != 0 /* nullptr */) g->get_table()->dump_table(); } printf("%s ", g->text_string); if (g->is_tab_te()) printf("\n\n"); glyphs.move_right(); } while (! glyphs.is_equal_to_head()); glyphs.move_to(old_pos); printf("\ndebugging end\n\n"); printf("\n-->\n"); fflush(stdout); #endif } /* * font classes and methods */ class html_font : public font { html_font(const char * /* nm */); public: int encoding_index; char *encoding; char *reencoded_name; ~html_font(); static html_font *load_html_font(const char * /* s */); }; html_font *html_font::load_html_font(const char *s) { html_font *f = 0 /* nullptr */; try { f = new html_font(s); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_font object"); } if (!f->load()) { delete f; return 0 /* nullptr */; } return f; } html_font::html_font(const char *nm) : font(nm) { } html_font::~html_font() { } /* * a simple class to contain the header to this document */ class title_desc { public: title_desc (); ~title_desc (); int has_been_written; int has_been_found; int with_h1; string text; }; title_desc::title_desc () : has_been_written(false), has_been_found(false), with_h1(false) { } title_desc::~title_desc () { } class header_desc { public: header_desc (); ~header_desc (); int no_of_level_one_headings; // how many .SH or .NH 1 have we found? int no_of_headings; // how many headings have we found? char_buffer headings; // all the headings used in the document list headers; // list of headers built from .NH and .SH list header_filename; // in which file is this header? int header_level; // current header level int written_header; // have we written the header yet? // TODO: boolify? string header_buffer; // current header text void write_headings (FILE * /* f */, int /* force */); // TODO: boolify }; header_desc::header_desc () : no_of_level_one_headings(0), no_of_headings(0), header_level(2), written_header(0) { } header_desc::~header_desc () { } /* * write_headings - emits a list of links for the headings in this * document */ void header_desc::write_headings (FILE *f, int force) { text_glob *g; assert(f != 0 /* nullptr */); if (0 /* nullptr */ == f) return; if (auto_links || force) { if (! headers.is_empty()) { int h=1; headers.start_from_head(); header_filename.start_from_head(); if (xhtml == dialect) fputs("<p>", f); do { g = headers.get_data(); fputs("<a href=\"", f); if (multiple_files && (! header_filename.is_empty())) { text_glob *fn = header_filename.get_data(); fputs(fn->text_string, f); } fputs("#", f); if (simple_anchors) { string buffer(ANCHOR_TEMPLATE); buffer += as_string(h); buffer += '\0'; fprintf(f, "%s", buffer.contents()); } else fputs(g->text_string, f); h++; fputs("\">", f); fputs(g->text_string, f); fputs("</a>", f); if (xhtml == dialect) fputs("<br/>\n", f); else fputs("<br>\n", f); headers.move_right(); if (multiple_files && (! header_filename.is_empty())) header_filename.move_right(); } while (! headers.is_equal_to_head()); fputs("\n", f); if (xhtml == dialect) fputs("</p>\n", f); } } } struct assert_pos { assert_pos *next; const char *val; const char *id; }; class assert_state { public: assert_state (); ~assert_state (); void addx (const char * /* c */, const char * /* i */, const char * /* v */, const char * /* f */, const char * /* l */); void addy (const char * /* c */, const char * /* i */, const char * /* v */, const char * /* f */, const char * /* l */); void build(const char * /* c */, const char * /* v */, const char * /* f */, const char * /* l */); void check_br (int /* br */); void check_ce (int /* ce */); void check_fi (int /* fi */); void check_sp (int /* sp */); void reset (void); private: int check_br_flag; int check_ce_flag; int check_fi_flag; int check_sp_flag; const char *val_br; const char *val_ce; const char *val_fi; const char *val_sp; const char *file_br; const char *file_ce; const char *file_fi; const char *file_sp; const char *line_br; const char *line_ce; const char *line_fi; const char *line_sp; assert_pos *xhead; assert_pos *yhead; void add (assert_pos ** /* h */, const char * /* c */, const char * /* i */, const char * /* v */, const char * /* f */, const char * /* l */); void compare (assert_pos * /* t */, const char * /* v */, const char * /* f */, const char * /* l */); void close (const char * /* c */); void set (const char * /* c */, const char * /* v */, const char * /* f */, const char * /* l */); void check_value (const char * /* s */, int /* v */, const char *nam /* e */, const char * /* f */, const char * /* l */, int * /* flag */); // TODO: boolify int check_value_error(int /* c */, int /* v */, const char * /* s */, const char *nam /* e */, const char * /* f */, const char * /* l */, int /* flag */); // TODO: boolify }; assert_state::assert_state () { reset(); val_br = 0; val_ce = 0; val_fi = 0; val_sp = 0; file_br = 0; file_ce = 0; file_fi = 0; file_sp = 0; line_br = 0; line_ce = 0; line_fi = 0; line_sp = 0; xhead = 0; yhead = 0; } assert_state::~assert_state () { assert_pos *t; while (xhead != 0 /* nullptr */) { t = xhead; xhead = xhead->next; delete[] t->val; delete[] t->id; delete t; } while (yhead != 0 /* nullptr */) { t = yhead; yhead = yhead->next; delete[] t->val; delete[] t->id; delete t; } } void assert_state::reset (void) { check_br_flag = 0; check_ce_flag = 0; check_fi_flag = 0; check_sp_flag = 0; } void assert_state::add (assert_pos **h, const char *c, const char *i, const char *v, const char *f, const char *l) { assert(h != 0 /* nullptr */); assert(*h != 0 /* nullptr */); if ((0 /* nullptr */ == h) || (0 /* nullptr */ == *h)) return; assert_pos *t = *h; while (t != 0 /* nullptr */) { if (strcmp(t->id, i) == 0) break; t = t->next; } if ((t != 0 /* nullptr */) && (v != 0 /* nullptr */) && (v[0] != '=')) compare(t, v, f, l); else { if (0 /* nullptr */ == t) { try { t = new assert_pos; } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for assert_pos object"); } t->next = *h; (*h) = t; } if ((0 == v) || (v[0] != '=')) { if (0 /* nullptr */ == f) f = strsave("stdin"); if (0 /* nullptr */ == l) l = strsave("<none>"); if (0 /* nullptr */ == v) v = "no value at all"; fprintf(stderr, "%s:%s:%s: error in assertion format of id=%s;" " expected value prefixed with an '=', got %s\n", program_name, f, l, i, v); } t->id = i; t->val = v; delete[] c; delete[] f; delete[] l; } } void assert_state::addx (const char *c, const char *i, const char *v, const char *f, const char *l) { add(&xhead, c, i, v, f, l); } void assert_state::addy (const char *c, const char *i, const char *v, const char *f, const char *l) { add(&yhead, c, i, v, f, l); } void assert_state::compare(assert_pos *t, const char *v, const char *f, const char *l) { assert(t != 0 /* nullptr */); assert(v != 0 /* nullptr */); if ((0 /* nullptr */ == t) || (0 /* nullptr */ == v)) return; const char *s=t->val; while ((*v) == '=') v++; while ((*s) == '=') s++; if (strcmp(v, s) != 0) { if (0 /* nullptr */ == f) f = "stdin"; if (0 /* nullptr */ == l) l = "<none>"; fprintf(stderr, "%s:%s:%s: grohtml assertion failed at id%s: " "expected %s, got %s\n", program_name, f, l, t->id, s, v); } } void assert_state::close (const char *c) { assert(c != 0 /* nullptr */); if (0 /* nullptr */ == c) return; if (strcmp(c, "sp") == 0) check_sp_flag = 0; else if (strcmp(c, "br") == 0) check_br_flag = 0; else if (strcmp(c, "fi") == 0) check_fi_flag = 0; else if (strcmp(c, "nf") == 0) check_fi_flag = 0; else if (strcmp(c, "ce") == 0) check_ce_flag = 0; else fprintf(stderr, "%s: ignoring unrecognized tag '%s'\n", program_name, c); } static const char *replace_negate_str (const char *before, char *after) { delete[] before; assert(after != 0 /* nullptr */); if (0 /* nullptr */ == after) return 0 /* nullptr */; if (strlen(after) > 0) { int d = atoi(after); if ((d < 0) || (d > 1)) { fprintf(stderr, "%s: expected nf/fi value of 0 or 1, got '%s';" " ignoring\n", program_name, after); d = 0; } if (0 == d) after[0] = '1'; else after[0] = '0'; after[1] = '\0'; } return after; } static const char *replace_str (const char *before, const char *after) { delete[] before; return after; } void assert_state::set (const char *c, const char *v, const char *f, const char *l) { assert(c != 0 /* nullptr */); assert(v != 0 /* nullptr */); if ((0 /* nullptr */ == c) || (0 /* nullptr */ == v)) return; if (0 /* nullptr */ == l) l = "<none>"; if (0 /* nullptr */ == f) f = "stdin"; // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v); if (strcmp(c, "sp") == 0) { check_sp_flag = 1; val_sp = replace_str(val_sp, strsave(v)); file_sp = replace_str(file_sp, strsave(f)); line_sp = replace_str(line_sp, strsave(l)); } else if (strcmp(c, "br") == 0) { check_br_flag = 1; val_br = replace_str(val_br, strsave(v)); file_br = replace_str(file_br, strsave(f)); line_br = replace_str(line_br, strsave(l)); } else if (strcmp(c, "fi") == 0) { check_fi_flag = 1; val_fi = replace_str(val_fi, strsave(v)); file_fi = replace_str(file_fi, strsave(f)); line_fi = replace_str(line_fi, strsave(l)); } else if (strcmp(c, "nf") == 0) { check_fi_flag = 1; val_fi = replace_negate_str(val_fi, strsave(v)); file_fi = replace_str(file_fi, strsave(f)); line_fi = replace_str(line_fi, strsave(l)); } else if (strcmp(c, "ce") == 0) { check_ce_flag = 1; val_ce = replace_str(val_ce, strsave(v)); file_ce = replace_str(file_ce, strsave(f)); line_ce = replace_str(line_ce, strsave(l)); } } /* * build - builds the troff state assertion. * see tmac/www.tmac for cmd examples. */ void assert_state::build (const char *c, const char *v, const char *f, const char *l) { assert(c != 0 /* nullptr */); if (0 /* nullptr */ == c) return; if (c[0] == '{') set(&c[1], v, f, l); if (c[0] == '}') close(&c[1]); } // TODO: boolify int assert_state::check_value_error (int c, int v, const char *s, const char *name, const char *f, const char *l, int flag) { if (0 /* nullptr */ == c) { if (0 /* nullptr */ == f) f = "stdin"; if (0 /* nullptr */ == l) l = "<none>"; fprintf(stderr, "%s:%s:%s: troff state assertion failed; " "expected %s to be %s, got %d\n", program_name, f, l, name, s, v); return 0; } return flag; } void assert_state::check_value (const char *s, int v, const char *name, const char *f, const char *l, int *flag) { assert(s != 0 /* nullptr */); assert(flag != 0 /* nullptr */); if ((0 /* nullptr */ == v) || (0 /* nullptr */ == flag)) return; if (strncmp(s, "<=", 2) == 0) *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag); else if (strncmp(s, ">=", 2) == 0) *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag); else if (strncmp(s, "==", 2) == 0) *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag); else if (strncmp(s, "!=", 2) == 0) *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag); else if (strncmp(s, "<", 1) == 0) *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag); else if (strncmp(s, ">", 1) == 0) *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag); else if (strncmp(s, "=", 1) == 0) *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag); else *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag); } void assert_state::check_sp (int sp) { if (check_sp_flag) check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag); } void assert_state::check_fi (int fi) { if (check_fi_flag) check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag); } void assert_state::check_br (int br) { if (check_br_flag) check_value(val_br, br, "br", file_br, line_br, &check_br_flag); } void assert_state::check_ce (int ce) { if (check_ce_flag) check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag); } class html_printer : public printer { files file_list; simple_output html; int res; glyph *space_glyph; int space_width; int no_of_printed_pages; int paper_length; string sbuf; int sbuf_start_hpos; int sbuf_vpos; int sbuf_end_hpos; int sbuf_prev_hpos; int sbuf_kern; style sbuf_style; int last_sbuf_length; int overstrike_detected; style output_style; int output_hpos; int output_vpos; int output_vpos_max; int output_draw_point_size; int line_thickness; int output_line_thickness; unsigned char output_space_code; char *inside_font_style; int page_number; title_desc title; header_desc header; int header_indent; int suppress_sub_sup; int cutoff_heading; page *page_contents; html_text *current_paragraph; html_indent *indent; html_table *table; int end_center; int end_tempindent; TAG_ALIGNMENT next_tag; int fill_on; int max_linelength; int linelength; int pageoffset; int troff_indent; int device_indent; int temp_indent; int pointsize; int vertical_spacing; int line_number; color *background; int seen_indent; int next_indent; int seen_pageoffset; int next_pageoffset; int seen_linelength; int next_linelength; int seen_center; int next_center; int seen_space; int seen_break; int current_column; int row_space; assert_state as; void flush_sbuf (); void set_style (const style & /* sty */); void set_space_code (unsigned char /* c */); void set_line_thickness (const environment * /* env */); void terminate_current_font(void); void flush_font (void); void add_to_sbuf (glyph * /* g */, const string & /* s */); void write_title (int /* in_head */); // TODO: boolify int sbuf_continuation (glyph * /* g */, const char * /* name */, const environment * /* env */, int /* w */); void flush_page (void); void troff_tag (text_glob * /* g */); void flush_globs (void); void emit_line (text_glob * /* g */); void emit_raw (text_glob * /* g */); void emit_html (text_glob * /* g */); void determine_space (text_glob * /* g */); void start_font (const char * /* name */); void end_font (const char * /* name */); int is_font_courier (font * /* f */); int is_line_start (int /* nf */); // TODO: boolify int is_courier_until_eol (void); void start_size (int /* from */, int /* to */); void do_font (text_glob * /* g */); void do_center (char * /* arg */); void do_check_center (void); void do_break (void); void do_space (char * /* arg */); void do_eol (void); void do_eol_ce (void); void do_title (void); void do_fill (char * /* arg */); void do_heading (char * /* arg */); void write_header (void); void determine_header_level(int /* level */); void do_linelength (char * /* arg */); void do_pageoffset (char * /* arg */); void do_indentation (char * /* arg */); void do_tempindent (char * /* arg */); void do_indentedparagraph (void); void do_verticalspacing (char * /* arg */); void do_pointsize (char * /* arg */); void do_centered_image (void); void do_left_image (void); void do_right_image (void); void do_auto_image (text_glob * /* g */, const char * /* filename */); void do_links (void); void do_flush (void); void do_job_name (char * /* name */); void do_head (char * /* name */); void insert_split_file (void); int is_in_middle (int /* left */, int /* right */); void do_sup_or_sub (text_glob * /* g */); int start_subscript (text_glob * /* g */); int end_subscript (text_glob * /* g */); int start_superscript (text_glob * /* g */); int end_superscript (text_glob * /* g */); void outstanding_eol (int /* n */); int is_bold (font * /* f */); font *make_bold (font * /* f */); int overstrike (glyph * /* g */, const char * /* name */, const environment * /* env */, int /* w */); void do_body (void); int next_horiz_pos (text_glob * /* g */, int /* nf */); // TODO: boolify void lookahead_for_tables (void); void insert_tab_te (void); text_glob *insert_tab_ts (text_glob * /* where */); void insert_tab0_foreach_tab(void); void insert_tab_0 (text_glob * /* where */); void do_indent (int /* in */, int /* pageoff */, int /* linelen */); void shutdown_table (void); void do_tab_ts (text_glob * /* g */); void do_tab_te (void); void do_col (char * /* s */); void do_tab (char * /* s */); void do_tab0 (void); int calc_nf (text_glob * /* g */, int nf); // TODO: boolify void calc_po_in (text_glob * /* g */, int nf); // TODO: boolify void remove_tabs (void); void remove_courier_tabs (void); void update_min_max (colType /* type_of_col */, int * /* minimum */, int * /* maximum */, text_glob * /* g */); void add_table_end (const char * /* debug_string */); void do_file_components (void); void write_navigation (const string & /* top */, const string & /* prev */, const string & /* next */, const string & /* current */); void emit_link (const string & /* to */, const char * /* name */); int get_troff_indent (void); void restore_troff_indent (void); void handle_assertion (int /* minv */, int /* minh */, int /* maxv */, int /* maxh */, const char * /* s */); void handle_state_assertion (text_glob * /* g */); void do_end_para (text_glob * /* g */); int round_width (int /* x */); void handle_tag_within_title(text_glob * /* g */); void writeHeadMetaStyle (void); void handle_valid_flag (int /* needs_para */); // TODO: boolify void do_math (text_glob * /* g */); void write_html_anchor (text_glob * /* h */); void write_xhtml_anchor (text_glob * /* h */); // ADD HERE public: html_printer (); ~html_printer (); void set_char (glyph * /*g */, font * /*f */, const environment * /* env */, int /* w */, const char * /* name */); void set_numbered_char (int /* num */, const environment * /* env */, int * /* widthp */); glyph *set_char_and_width(const char * /* nm */, const environment * /* env */, int * /* widthp */, font ** /* f */); void draw (int /* code */, int * /* p */, int np, // TODO: -> npoints const environment * /* env */); void begin_page (int /* n */); void end_page (int); // XXX: unused argument void special (char * /* arg */, const environment * /* env */, char /* type */); void devtag (char * /* arg */, const environment * /* env */, char /* type */); font *make_font (const char * /* nm */); void end_of_line (); }; printer *make_printer() { return new html_printer; } static void usage(FILE *stream); void html_printer::set_style(const style &sty) { const char *fontname = sty.f->get_filename(); if (0 /* nullptr */ == fontname) // XXX: Is this the only circumstance that can cause a null font // description file name? ps.cpp uses sty.f->get_internal_name()... fatal("cannot set style; font description file '%1' lacks an" " 'internalname' directive", fontname); #if 0 change_font(fontname, (font::res / (72 * font::sizescale)) * sty.point_size); #endif } /* * is_bold - returns true if font, f, is bold. */ int html_printer::is_bold (font *f) { assert(f != 0 /* nullptr */); if (0 /* nullptr */ == f) return 0; // XXX: This property should be inferred from font description data, // not the file name. const char *fontname = f->get_filename(); return (strcmp(fontname, "B") == 0) || (strcmp(fontname, "BI") == 0); } /* * make_bold - if a bold style for f exists, return it. */ font *html_printer::make_bold (font *f) { assert(f != 0 /* nullptr */); if (0 /* nullptr */ == f) return 0 /* nullptr */; // XXX: This logic locks us into a font description file naming // scheme. const char *fontname = f->get_filename(); if (strcmp(fontname, "B") == 0) return f; if (strcmp(fontname, "I") == 0) return font::load_font("BI"); if (strcmp(fontname, "BI") == 0) return f; return 0 /* nullptr */; } void html_printer::end_of_line() { flush_sbuf(); line_number++; } /* * emit_line - writes out a horizontal rule. */ void html_printer::emit_line (text_glob *) { // --fixme-- needs to know the length in percentage if (xhtml == dialect) html.put_string("<hr/>"); else html.put_string("<hr>"); } /* * restore_troff_indent - is called when we have temporarily shutdown * indentation (typically done when we have * centered an image). */ void html_printer::restore_troff_indent (void) { troff_indent = next_indent; if (troff_indent > 0) { /* * force device indentation */ device_indent = 0; do_indent(get_troff_indent(), pageoffset, linelength); } } /* * emit_raw - writes the raw html information directly to the device. */ void html_printer::emit_raw (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; do_font(g); if (next_tag == INLINE) { determine_space(g); current_paragraph->do_emittext(g->text_string, g->text_length); } else { // TODO: boolify int space = current_paragraph->retrieve_para_space() || seen_space; current_paragraph->done_para(); shutdown_table(); switch (next_tag) { case CENTERED: if (html4 == dialect) current_paragraph->do_para("align=\"center\"", space); else current_paragraph->do_para("class=\"center\"", space); break; case LEFT: if (html4 == dialect) current_paragraph->do_para(&html, "align=\"left\"", get_troff_indent(), pageoffset, linelength, space); else current_paragraph->do_para(&html, "class=\"left\"", get_troff_indent(), pageoffset, linelength, space); break; case RIGHT: if (html4 == dialect) current_paragraph->do_para(&html, "align=\"right\"", get_troff_indent(), pageoffset, linelength, space); else current_paragraph->do_para(&html, "class=\"right\"", get_troff_indent(), pageoffset, linelength, space); break; default: fatal("unknown enumeration"); } current_paragraph->do_emittext(g->text_string, g->text_length); current_paragraph->done_para(); next_tag = INLINE; suppress_sub_sup = true; seen_space = false; restore_troff_indent(); } } /* * handle_tag_within_title - handle a limited number of tags within * the context of a table. Those tags which * set values rather than generate spaces * and paragraphs. */ void html_printer::handle_tag_within_title (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll() || g->is_fi() || g->is_nf()) troff_tag(g); } /* * do_center - handle the .ce commands from troff. */ void html_printer::do_center (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; next_center = atoi(arg); seen_center = true; } /* * do_centered_image - set a flag such that the next devtag is * placed inside a centered paragraph. */ void html_printer::do_centered_image (void) { next_tag = CENTERED; } /* * do_right_image - set a flag such that the next devtag is * placed inside a right aligned paragraph. */ void html_printer::do_right_image (void) { next_tag = RIGHT; } /* * do_left_image - set a flag such that the next devtag is * placed inside a left aligned paragraph. */ void html_printer::do_left_image (void) { next_tag = LEFT; } /* * exists - returns true if filename exists. */ // TODO: boolify static int exists (const char *filename) { assert(filename != 0 /* nullptr */); if (0 /* nullptr */ == filename) return false; FILE *fp = fopen(filename, "r"); if (0 /* nullptr */ == fp) { return false; } else { fclose(fp); return true; } } /* * generate_img_src - returns a html image tag for the filename * providing that the image exists. */ static string &generate_img_src (const char *filename) { assert(filename != 0 /* nullptr */); string *s = 0 /* nullptr */; try { s = new string(""); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for string object"); } while ((filename != 0 /* nullptr */) && (' ' == filename[0])) filename++; if ((0 /* nullptr */ == filename) || exists(filename)) { *s += string("<img src=\"") + filename + "\" " + "alt=\"Image " + filename + "\">"; if (xhtml == dialect) *s += "</img>"; } return *s; } /* * do_auto_image - tests whether the image, indicated by filename, * is present, if so then it emits an html image tag. * An image tag may be passed through from pic, eqn * but the corresponding image might not be created. * Consider .EQ delim $$ .EN or an empty .PS .PE. */ void html_printer::do_auto_image (text_glob *g, const char *filename) { assert(g != 0 /* nullptr */); assert(filename != 0 /* nullptr */); if ((0 /* nullptr */ == g) || (0 /* nullptr */ == filename)) return; string buffer = generate_img_src(filename); if (! buffer.empty()) { /* * utilize emit_raw by creating a new text_glob. */ text_glob h = *g; h.text_string = buffer.contents(); h.text_length = buffer.length(); emit_raw(&h); } else next_tag = INLINE; } /* * outstanding_eol - call do_eol, n, times. */ void html_printer::outstanding_eol (int n) { while (n > 0) { do_eol(); n--; } } /* * do_title - handle the .tl commands from troff. */ void html_printer::do_title (void) { text_glob *t; int removed_from_head; if (1 == page_number) { int found_title_start = false; if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.sub_move_right(); // move onto next word do { t = page_contents->glyphs.get_data(); removed_from_head = false; if (t->is_auto_img()) { string img = generate_img_src((t->text_string + 20)); if (! img.empty()) { if (found_title_start) title.text += " "; found_title_start = true; title.has_been_found = true; title.text += img; } page_contents->glyphs.sub_move_right(); // move onto next word removed_from_head = ((!page_contents->glyphs.is_empty()) && (page_contents->glyphs .is_equal_to_head())); } else if (t->is_eo_tl()) { // end of title found title.has_been_found = true; return; } else if (t->is_a_tag()) { handle_tag_within_title(t); page_contents->glyphs.sub_move_right(); // move onto next word removed_from_head = ((!page_contents->glyphs.is_empty()) && (page_contents->glyphs .is_equal_to_head())); } else if (found_title_start) { title.text += " " + string(t->text_string, t->text_length); page_contents->glyphs.sub_move_right(); // move onto next word removed_from_head = ((!page_contents->glyphs.is_empty()) && (page_contents->glyphs .is_equal_to_head())); } else { title.text += string(t->text_string, t->text_length); found_title_start = true; title.has_been_found = true; page_contents->glyphs.sub_move_right(); // move onto next word removed_from_head = ((!page_contents->glyphs.is_empty()) && (page_contents->glyphs .is_equal_to_head())); } } while ((! page_contents->glyphs.is_equal_to_head()) || removed_from_head); } } } /* * write_html_anchor - writes out an anchor. The style of the anchor * dependent upon simple_anchor. */ void html_printer::write_html_anchor (text_glob *h) { if (html4 == dialect) { if (h != 0 /* nullptr */) { html.put_string("<a name=\""); if (simple_anchors) { string buffer(ANCHOR_TEMPLATE); buffer += as_string(header.no_of_headings); buffer += '\0'; html.put_string(buffer.contents()); } else html.put_string(header.header_buffer.contents()); html.put_string("\"></a>").nl(); } } } /* * write_xhtml_anchor - writes out an anchor. The style of the anchor * dependent upon simple_anchor. */ void html_printer::write_xhtml_anchor (text_glob *h) { if (xhtml == dialect) { if (h != 0 /* nullptr */) { html.put_string(" id=\""); if (simple_anchors) { string buffer(ANCHOR_TEMPLATE); buffer += as_string(header.no_of_headings); buffer += '\0'; html.put_string(buffer.contents()); } else html.put_string(header.header_buffer.contents()); html.put_string("\""); } } } void html_printer::write_header (void) { if (! header.header_buffer.empty()) { text_glob *a = 0 /* nullptr */; // TODO: boolify int space = current_paragraph->retrieve_para_space() || seen_space; if (header.header_level > 7) header.header_level = 7; // firstly we must terminate any font and type faces current_paragraph->done_para(); suppress_sub_sup = true; if (cutoff_heading+2 > header.header_level) { // now we save the header so we can issue a list of links header.no_of_headings++; style st; try { a = new text_glob(); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text_glob object"); } a->text_glob_html(&st, header.headings .add_string(header.header_buffer.contents()), header.header_buffer.length(), header.no_of_headings, header.header_level, header.no_of_headings, header.header_level); // and add this header to the header list header.headers.add(a, header.no_of_headings, header.no_of_headings, header.no_of_headings, header.no_of_headings, header.no_of_headings); } html.nl().nl(); if (manufacture_headings) { // line break before a header if (!current_paragraph->emitted_text()) current_paragraph->do_space(); // user wants manufactured headings which look better than // <Hn></Hn> if (header.header_level<4) { html.put_string("<b><font size=\"+1\">"); html.put_string(header.header_buffer.contents()); html.put_string("</font>").nl(); write_html_anchor(a); html.put_string("</b>").nl(); } else { html.put_string("<b>"); html.put_string(header.header_buffer.contents()).nl(); write_html_anchor(a); html.put_string("</b>").nl(); } } else { // and now we issue the real header html.put_string("<h"); html.put_number(header.header_level); write_xhtml_anchor(a); html.put_string(">"); html.put_string(header.header_buffer.contents()).nl(); write_html_anchor(a); html.put_string("</h"); html.put_number(header.header_level); html.put_string(">").nl(); } /* and now we save the file name in which this header will occur */ style st; // fake style to enable us to use the list data structure text_glob *h=new text_glob(); h->text_glob_html(&st, header.headings.add_string(file_list.file_name()), file_list.file_name().length(), header.no_of_headings, header.header_level, header.no_of_headings, header.header_level); header.header_filename.add(h, header.no_of_headings, header.no_of_headings, header.no_of_headings, header.no_of_headings, header.no_of_headings); current_paragraph->do_para(&html, "", get_troff_indent(), pageoffset, linelength, space); } } void html_printer::determine_header_level (int level) { if (0 == level) { for (size_t i = 0; ((i < header.header_buffer.length()) && (('.' == header.header_buffer[i]) || is_digit(header.header_buffer[i]))); i++) { if ('.' == header.header_buffer[i]) level++; } } header.header_level = level+1; if ((header.header_level >= 2) && (header.header_level <= split_level)) { header.no_of_level_one_headings++; insert_split_file(); } } /* * do_heading - handle the .SH and .NH and equivalent commands from * troff. */ void html_printer::do_heading (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; text_glob *g; int level=atoi(arg); int horiz; header.header_buffer.clear(); page_contents->glyphs.move_right(); if (! page_contents->glyphs.is_equal_to_head()) { g = page_contents->glyphs.get_data(); horiz = g->minh; do { if (g->is_auto_img()) { string img=generate_img_src((g->text_string + 20)); if (! img.empty()) { // we cannot use full heading anchors with images simple_anchors = true; if (horiz < g->minh) header.header_buffer += " "; header.header_buffer += img; } } else if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll()) troff_tag(g); else if (g->is_fi()) fill_on = 1; else if (g->is_nf()) fill_on = 0; else if (! (g->is_a_line() || g->is_a_tag())) { /* * we ignore the other tag commands when constructing a heading */ if (horiz < g->minh) header.header_buffer += " "; horiz = g->maxh; header.header_buffer += g->text_string; } page_contents->glyphs.move_right(); g = page_contents->glyphs.get_data(); } while ((! page_contents->glyphs.is_equal_to_head()) && (! g->is_eo_h())); } determine_header_level(level); write_header(); /* * finally set the output font to uninitialized, thus forcing * the new paragraph to start a new font block. */ output_style.f = 0; g = page_contents->glyphs.get_data(); page_contents->glyphs.move_left(); // so that next time we use old g } /* * is_courier_until_eol - returns true if we can see a whole line which * is courier */ int html_printer::is_courier_until_eol (void) { text_glob *orig = page_contents->glyphs.get_data(); int result = true; text_glob *g; if (! page_contents->glyphs.is_equal_to_tail()) { page_contents->glyphs.move_right(); do { g = page_contents->glyphs.get_data(); if (! g->is_a_tag() && (! is_font_courier(g->text_style.f))) result = false; page_contents->glyphs.move_right(); } while (result && (! page_contents->glyphs.is_equal_to_head()) && (! g->is_fi()) && (! g->is_eol())); /* * now restore our previous position. */ while (page_contents->glyphs.get_data() != orig) page_contents->glyphs.move_left(); } return result; } /* * do_linelength - handle the .ll command from troff. */ void html_printer::do_linelength (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; if (-1 == max_linelength) max_linelength = atoi(arg); next_linelength = atoi(arg); seen_linelength = true; } /* * do_pageoffset - handle the .po command from troff. */ void html_printer::do_pageoffset (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; next_pageoffset = atoi(arg); seen_pageoffset = true; } /* * get_troff_indent - returns the indent value. */ int html_printer::get_troff_indent (void) { if (end_tempindent > 0) return temp_indent; else return troff_indent; } /* * do_indentation - handle the .in command from troff. */ void html_printer::do_indentation (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; next_indent = atoi(arg); seen_indent = true; } /* * do_tempindent - handle the .ti command from troff. */ void html_printer::do_tempindent (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; if (fill_on) { /* * we set the end_tempindent to 2 as the first .br * activates the .ti and the second terminates it. */ end_tempindent = 2; temp_indent = atoi(arg); } } /* * shutdown_table - shuts down the current table. */ void html_printer::shutdown_table (void) { if (table != 0 /* nullptr */) { current_paragraph->done_para(); table->emit_finish_table(); // don't delete this table as it will be deleted when we destroy the // text_glob table = 0 /* nullptr */; } } /* * do_indent - remember the indent parameters and if * indent is > pageoff and indent has changed * then we start a html table to implement the indentation. */ void html_printer::do_indent (int in, int pageoff, int linelen) { if ((device_indent != -1) && ((pageoffset+device_indent) != (in+pageoff))) { // TODO: boolify int space = current_paragraph->retrieve_para_space() || seen_space; current_paragraph->done_para(); device_indent = in; pageoffset = pageoff; if (linelen <= max_linelength) linelength = linelen; current_paragraph->do_para(&html, "", device_indent, pageoffset, max_linelength, space); } } /* * do_verticalspacing - handle the .vs command from troff. */ void html_printer::do_verticalspacing (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; vertical_spacing = atoi(arg); } /* * do_pointsize - handle the .ps command from troff. */ void html_printer::do_pointsize (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; /* * firstly check to see whether this point size is really associated * with a .tl tag */ if (! page_contents->glyphs.is_empty()) { text_glob *g = page_contents->glyphs.get_data(); text_glob *t = page_contents->glyphs.get_data(); while (t->is_a_tag() && (!page_contents->glyphs.is_equal_to_head())) { if (t->is_tl()) { /* * found title therefore ignore this .ps tag */ while (t != g) { page_contents->glyphs.move_left(); t = page_contents->glyphs.get_data(); } return; } page_contents->glyphs.move_right(); t = page_contents->glyphs.get_data(); } /* * move back to original position */ while (t != g) { page_contents->glyphs.move_left(); t = page_contents->glyphs.get_data(); } /* * collect valid pointsize */ pointsize = atoi(arg); } } /* * do_fill - records whether troff has requested that text be filled. */ void html_printer::do_fill (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; int on = atoi(arg); output_hpos = get_troff_indent()+pageoffset; suppress_sub_sup = true; if (fill_on != on) { if (on) current_paragraph->do_para("", seen_space); fill_on = on; } } /* * do_eol - handle the end of line */ void html_printer::do_eol (void) { if (! fill_on) { if (current_paragraph->ever_emitted_text()) { current_paragraph->do_newline(); current_paragraph->do_break(); } } output_hpos = get_troff_indent()+pageoffset; } /* * do_check_center - checks to see whether we have seen a '.ce' tag * during the previous line. */ void html_printer::do_check_center(void) { if (seen_center) { seen_center = false; if (next_center > 0) { if (0 == end_center) { // TODO: boolify int space = current_paragraph->retrieve_para_space() || seen_space; current_paragraph->done_para(); suppress_sub_sup = true; if (html4 == dialect) current_paragraph->do_para("align=\"center\"", space); else current_paragraph->do_para("class=\"center\"", space); } else if ((strcmp("align=\"center\"", current_paragraph->get_alignment()) != 0) && (strcmp("class=\"center\"", current_paragraph->get_alignment()) != 0)) { /* * different alignment, so shutdown paragraph and open * a new one. */ // TODO: boolify int space = current_paragraph->retrieve_para_space() || seen_space; current_paragraph->done_para(); suppress_sub_sup = true; if (html4 == dialect) current_paragraph->do_para("align=\"center\"", space); else current_paragraph->do_para("class=\"center\"", space); } else // same alignment; if we have emitted text, issue a break. if (current_paragraph->emitted_text()) current_paragraph->do_break(); } else /* * next_center == 0 */ if (end_center > 0) { // TODO: boolify seen_space = seen_space || current_paragraph->retrieve_para_space(); current_paragraph->done_para(); suppress_sub_sup = true; current_paragraph->do_para("", seen_space); } end_center = next_center; } } /* * do_eol_ce - handle end of line specifically for a .ce */ void html_printer::do_eol_ce (void) { if (end_center > 0) { if (end_center > 1) if (current_paragraph->emitted_text()) current_paragraph->do_break(); end_center--; if (0 == end_center) { current_paragraph->done_para(); suppress_sub_sup = true; } } } /* * do_flush - flushes all output and tags. */ void html_printer::do_flush (void) { current_paragraph->done_para(); } /* * do_links - moves onto a new temporary file and sets auto_links to * false. */ void html_printer::do_links (void) { html.end_line(); // flush line auto_links = false; // from now on only emit under user request file_list.add_new_file(xtmpfile()); file_list.set_links_required(); html.set_file(file_list.get_file()); } /* * insert_split_file - */ void html_printer::insert_split_file (void) { if (multiple_files) { current_paragraph->done_para(); // flush paragraph html.end_line(); // flush line html.set_file(file_list.get_file()); // flush current file file_list.add_new_file(xtmpfile()); string split_file = job_name; split_file += string("-"); split_file += as_string(header.no_of_level_one_headings); if (xhtml == dialect) split_file += string(".xhtml"); else split_file += string(".html"); split_file += '\0'; file_list.set_file_name(split_file); html.set_file(file_list.get_file()); } } /* * do_job_name - assigns the job_name to name. */ void html_printer::do_job_name (char *name) { assert(name != 0 /* nullptr */); if (0 /* nullptr */ == name) return; if (! multiple_files) { multiple_files = true; while (name != 0 /* nullptr */ && (*name != '\0') && (' ' == *name)) name++; job_name = name; } } /* * do_head - adds a string to head_info which is to be included into * the <head> </head> section of the html document. */ void html_printer::do_head (char *name) { assert(name != 0 /* nullptr */); if (0 /* nullptr */ == name) return; head_info += string(name); head_info += '\n'; } /* * do_break - handles the ".br" request and also undoes an outstanding * ".ti" command and calls indent if the indentation related * registers have changed. */ void html_printer::do_break (void) { int seen_temp_indent = false; current_paragraph->do_break(); if (end_tempindent > 0) { end_tempindent--; if (end_tempindent > 0) seen_temp_indent = true; } if (seen_indent || seen_pageoffset || seen_linelength || seen_temp_indent) { if (seen_indent && (! seen_temp_indent)) troff_indent = next_indent; if (! seen_pageoffset) next_pageoffset = pageoffset; if (! seen_linelength) next_linelength = linelength; do_indent(get_troff_indent(), next_pageoffset, next_linelength); } seen_indent = seen_temp_indent; seen_linelength = false; seen_pageoffset = false; do_check_center(); output_hpos = get_troff_indent()+pageoffset; suppress_sub_sup = true; } void html_printer::do_space (char *arg) { assert(arg != 0 /* nullptr */); if (0 /* nullptr */ == arg) return; int n = atoi(arg); seen_space = atoi(arg); as.check_sp(seen_space); #if 0 if ((n>0) && table) table->set_space(true); #endif while (n>0) { current_paragraph->do_space(); n--; } suppress_sub_sup = true; } /* * do_tab_ts - start a table, which will have already been defined. */ void html_printer::do_tab_ts (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; html_table *t = g->get_table(); if (t != 0 /* nullptr */) { current_column = 0; current_paragraph->done_pre(); current_paragraph->done_para(); current_paragraph->remove_para_space(); #if defined(DEBUG_TABLES) html.simple_comment("TABS"); #endif t->set_linelength(max_linelength); t->add_indent(pageoffset); #if 0 t->emit_table_header(seen_space); #else t->emit_table_header(false); // TODO: boolify row_space = current_paragraph->retrieve_para_space() || seen_space; seen_space = false; #endif } table = t; } /* * do_tab_te - finish a table. */ void html_printer::do_tab_te (void) { if (table != 0 /* nullptr */) { current_paragraph->done_para(); current_paragraph->remove_para_space(); table->emit_finish_table(); } table = 0 /* nullptr */; restore_troff_indent(); } /* * do_tab - handle the "devtag:tab" tag */ void html_printer::do_tab (char *s) { assert(s != 0 /* nullptr */); if (0 /* nullptr */ == s) return; if (table != 0 /* nullptr */) { while (csspace(*s)) s++; s++; int col = table->find_column(atoi(s) + pageoffset + get_troff_indent()); if (col > 0) { current_paragraph->done_para(); table->emit_col(col); } } } /* * do_tab0 - handle the "devtag:tab0" tag */ void html_printer::do_tab0 (void) { if (table != 0 /* nullptr */) { int col = table->find_column(pageoffset+get_troff_indent()); if (col > 0) { current_paragraph->done_para(); table->emit_col(col); } } } /* * do_col - start column, s. */ void html_printer::do_col (char *s) { assert(s != 0 /* nullptr */); if (0 /* nullptr */ == s) return; if (table != 0 /* nullptr */) { if (atoi(s) < current_column) row_space = seen_space; current_column = atoi(s); current_paragraph->done_para(); table->emit_col(current_column); current_paragraph->do_para("", row_space); } } /* * troff_tag - processes the troff tag and manipulates the troff * state machine. */ void html_printer::troff_tag (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; /* * firstly skip over devtag: */ char *t=const_cast<char *>(g->text_string)+strlen("devtag:"); if (strncmp(g->text_string, "html</p>:", strlen("html</p>:")) == 0) { do_end_para(g); } else if (strncmp(g->text_string, "html<?p>:", strlen("html<?p>:")) == 0) { if (current_paragraph->emitted_text()) html.put_string(g->text_string+9); else do_end_para(g); } else if (strncmp(g->text_string, "math<?p>:", strlen("math<?p>:")) == 0) { do_math(g); } else if (g->is_eol()) { do_eol(); } else if (g->is_eol_ce()) { do_eol_ce(); } else if (strncmp(t, ".sp", 3) == 0) { char *a = t+3; do_space(a); } else if (strncmp(t, ".br", 3) == 0) { seen_break = 1; as.check_br(1); do_break(); } else if (strcmp(t, ".centered-image") == 0) { do_centered_image(); } else if (strcmp(t, ".right-image") == 0) { do_right_image(); } else if (strcmp(t, ".left-image") == 0) { do_left_image(); } else if (strncmp(t, ".auto-image", 11) == 0) { char *a = t+11; do_auto_image(g, a); } else if (strncmp(t, ".ce", 3) == 0) { char *a = t+3; suppress_sub_sup = true; do_center(a); } else if (g->is_tl()) { suppress_sub_sup = true; title.with_h1 = true; do_title(); } else if (strncmp(t, ".html-tl", 8) == 0) { suppress_sub_sup = true; title.with_h1 = false; do_title(); } else if (strncmp(t, ".fi", 3) == 0) { char *a = t+3; do_fill(a); } else if ((strncmp(t, ".SH", 3) == 0) || (strncmp(t, ".NH", 3) == 0)) { char *a = t+3; do_heading(a); } else if (strncmp(t, ".ll", 3) == 0) { char *a = t+3; do_linelength(a); } else if (strncmp(t, ".po", 3) == 0) { char *a = t+3; do_pageoffset(a); } else if (strncmp(t, ".in", 3) == 0) { char *a = t+3; do_indentation(a); } else if (strncmp(t, ".ti", 3) == 0) { char *a = t+3; do_tempindent(a); } else if (strncmp(t, ".vs", 3) == 0) { char *a = t+3; do_verticalspacing(a); } else if (strncmp(t, ".ps", 3) == 0) { char *a = t+3; do_pointsize(a); } else if (strcmp(t, ".links") == 0) { do_links(); } else if (strncmp(t, ".job-name", 9) == 0) { char *a = t+9; do_job_name(a); } else if (strncmp(t, ".head", 5) == 0) { char *a = t+5; do_head(a); } else if (strcmp(t, ".no-auto-rule") == 0) { auto_rule = false; } else if (strcmp(t, ".tab-ts") == 0) { do_tab_ts(g); } else if (strcmp(t, ".tab-te") == 0) { do_tab_te(); } else if (strncmp(t, ".col ", 5) == 0) { char *a = t+4; do_col(a); } else if (strncmp(t, "tab ", 4) == 0) { char *a = t+3; do_tab(a); } else if (strncmp(t, "tab0", 4) == 0) { do_tab0(); } } /* * do_math - prints out the equation */ void html_printer::do_math (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; do_font(g); if (current_paragraph->emitted_text()) html.put_string(g->text_string+9); else do_end_para(g); } /* * is_in_middle - returns true if the positions left..right are in the * center of the page. */ // TODO: boolify int html_printer::is_in_middle (int left, int right) { return( abs(abs(left-pageoffset) - abs(pageoffset+linelength-right)) <= CENTER_TOLERANCE ); } /* * flush_globs - runs through the text glob list and emits html. */ void html_printer::flush_globs (void) { text_glob *g; if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.start_from_head(); do { g = page_contents->glyphs.get_data(); #if 0 fprintf(stderr, "[%s:%d:%d:%d:%d]", g->text_string, g->minv, g->minh, g->maxv, g->maxh) ; fflush(stderr); #endif handle_state_assertion(g); if (strcmp(g->text_string, "XXXXXXX") == 0) stop(); if (g->is_a_tag()) troff_tag(g); else if (g->is_a_line()) emit_line(g); else { as.check_sp(seen_space); as.check_br(seen_break); seen_break = 0; seen_space = 0; emit_html(g); } as.check_fi(fill_on); as.check_ce(end_center); /* * after processing the title (and removing it) the glyph list * might be empty */ if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.move_right(); } } while (! page_contents->glyphs.is_equal_to_head()); } } /* * calc_nf - calculates the _no_ format flag, given the * text glob, g. */ // TODO: boolify int html_printer::calc_nf (text_glob *g, int nf) { if (g != 0 /* nullptr */) { if (g->is_fi()) { as.check_fi(true); return false; } if (g->is_nf()) { as.check_fi(false); return true; } } as.check_fi(! nf); return nf; } /* * calc_po_in - calculates the, in, po, registers */ void html_printer::calc_po_in (text_glob *g, int nf) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; if (g->is_in()) troff_indent = g->get_arg(); else if (g->is_po()) pageoffset = g->get_arg(); else if (g->is_ti()) { temp_indent = g->get_arg(); end_tempindent = 2; } else if (g->is_br() || (nf && g->is_eol())) { if (end_tempindent > 0) end_tempindent--; } } /* * next_horiz_pos - returns the next horiz position. * -1 is returned if it doesn't exist. */ int html_printer::next_horiz_pos (text_glob *g, int nf) { int next = -1; if ((g != 0 /* nullptr */) && (g->is_br() || (nf && g->is_eol()))) if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.move_right_get_data(); if (0 /* nullptr */ == g) { page_contents->glyphs.start_from_head(); as.reset(); } else { next = g->minh; page_contents->glyphs.move_left(); } } return next; } /* * insert_tab_ts - inserts a tab-ts before, where. */ text_glob *html_printer::insert_tab_ts (text_glob *where) { text_glob *start_of_table; text_glob *old_pos = page_contents->glyphs.get_data(); page_contents->glyphs.move_to(where); page_contents->glyphs.move_left(); // tab table start page_contents->insert_tag(string("devtag:.tab-ts")); page_contents->glyphs.move_right(); start_of_table = page_contents->glyphs.get_data(); page_contents->glyphs.move_to(old_pos); return start_of_table; } /* * insert_tab_te - inserts a tab-te before the current position * (it skips backward over .sp/.br) */ void html_printer::insert_tab_te (void) { text_glob *g = page_contents->glyphs.get_data(); page_contents->dump_page(); while (page_contents->glyphs.get_data()->is_a_tag()) page_contents->glyphs.move_left(); // tab table end page_contents->insert_tag(string("devtag:.tab-te")); while (g != page_contents->glyphs.get_data()) page_contents->glyphs.move_right(); page_contents->dump_page(); } /* * insert_tab_0 - inserts a tab0 before, where. */ void html_printer::insert_tab_0 (text_glob *where) { text_glob *old_pos = page_contents->glyphs.get_data(); page_contents->glyphs.move_to(where); page_contents->glyphs.move_left(); // tab0 start of line page_contents->insert_tag(string("devtag:tab0")); page_contents->glyphs.move_right(); page_contents->glyphs.move_to(old_pos); } /* * remove_tabs - removes the tabs tags on this line. */ void html_printer::remove_tabs (void) { text_glob *orig = page_contents->glyphs.get_data(); text_glob *g; if (! page_contents->glyphs.is_equal_to_tail()) { do { g = page_contents->glyphs.get_data(); if (g->is_tab()) { page_contents->glyphs.sub_move_right(); if (g == orig) orig = page_contents->glyphs.get_data(); } else page_contents->glyphs.move_right(); } while ((! page_contents->glyphs.is_equal_to_head()) && (! g->is_eol())); /* * now restore our previous position. */ while (page_contents->glyphs.get_data() != orig) page_contents->glyphs.move_left(); } } void html_printer::remove_courier_tabs (void) { text_glob *g; int line_start = true; int nf = false; if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.start_from_head(); as.reset(); line_start = true; do { g = page_contents->glyphs.get_data(); handle_state_assertion(g); nf = calc_nf(g, nf); if (line_start) { if (line_start && nf && is_courier_until_eol()) { remove_tabs(); g = page_contents->glyphs.get_data(); } } // line_start = g->is_br() || g->is_nf() || g->is_fi() // || (nf && g->is_eol()); line_start = g->is_br() || (nf && g->is_eol()); page_contents->glyphs.move_right(); } while (! page_contents->glyphs.is_equal_to_head()); } } void html_printer::insert_tab0_foreach_tab (void) { text_glob *start_of_line = 0 /* nullptr */; text_glob *g = 0 /* nullptr */; int seen_tab = false; int seen_col = false; int nf = false; if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.start_from_head(); as.reset(); start_of_line = page_contents->glyphs.get_data(); do { g = page_contents->glyphs.get_data(); handle_state_assertion(g); nf = calc_nf(g, nf); if (g->is_tab()) seen_tab = true; if (g->is_col()) seen_col = true; if (g->is_br() || (nf && g->is_eol())) { do { page_contents->glyphs.move_right(); g = page_contents->glyphs.get_data(); handle_state_assertion(g); nf = calc_nf(g, nf); if (page_contents->glyphs.is_equal_to_head()) { if (seen_tab && !seen_col) insert_tab_0(start_of_line); return; } } while (g->is_br() || (nf && g->is_eol()) || g->is_ta()); // printf("\nstart_of_line is: %s\n", g->text_string); if (seen_tab && !seen_col) { insert_tab_0(start_of_line); page_contents->glyphs.move_to(g); } seen_tab = false; seen_col = false; start_of_line = g; } page_contents->glyphs.move_right(); } while (! page_contents->glyphs.is_equal_to_head()); if (seen_tab && !seen_col) insert_tab_0(start_of_line); } } /* * update_min_max - updates the extent of a column, given the left and * right extents of a glyph, g. */ void html_printer::update_min_max (colType type_of_col, int *minimum, int *maximum, text_glob *g) { switch (type_of_col) { case tab_tag: break; case tab0_tag: *minimum = g->minh; break; case col_tag: *minimum = g->minh; *maximum = g->maxh; break; default: break; } } /* * add_table_end - moves left one glyph, adds a table end tag and adds * a debugging string. */ void html_printer::add_table_end (const char * #if defined(DEBUG_TABLES) debug_string #endif ) { page_contents->glyphs.move_left(); insert_tab_te(); #if defined(DEBUG_TABLES) if (0 /* nullptr */ == debug_string) fatal("internal error: debug_string is a null pointer"); page_contents->insert_tag(string(debug_string)); #endif } /* * lookahead_for_tables - checks for .col tags and inserts table * start/end tags */ void html_printer::lookahead_for_tables (void) { text_glob *g; text_glob *start_of_line = 0 /* nullptr */; text_glob *start_of_table = 0 /* nullptr */; text_glob *last = 0 /* nullptr */; colType type_of_col = none_tag; int found_col = false; int ncol = 0; int colmin = 0; // pacify compiler int colmax = 0; // pacify compiler html_table *tbl = 0 /* nullptr */; const char *tab_defs = 0 /* nullptr */; char align = 'L'; int nf = false; int old_pageoffset = pageoffset; try { tbl = new html_table(&html, -1); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_table object"); } remove_courier_tabs(); page_contents->dump_page(); insert_tab0_foreach_tab(); page_contents->dump_page(); if (! page_contents->glyphs.is_empty()) { page_contents->glyphs.start_from_head(); as.reset(); g = page_contents->glyphs.get_data(); if (g->is_br()) { g = page_contents->glyphs.move_right_get_data(); handle_state_assertion(g); if (page_contents->glyphs.is_equal_to_head()) { delete tbl; tbl = 0 /* nullptr */; return; } start_of_line = g; ncol = 0; if (found_col) last = g; found_col = false; } do { #if defined(DEBUG_TABLES) fprintf(stderr, " [") ; fprintf(stderr, g->text_string) ; fprintf(stderr, "] ") ; fflush(stderr); if (strcmp(g->text_string, "XXXXXXX") == 0) stop(); #endif nf = calc_nf(g, nf); calc_po_in(g, nf); if (g->is_col()) { if ((tab_tag == type_of_col) && (start_of_table != 0 /* nullptr */)) { page_contents->glyphs.move_left(); insert_tab_te(); start_of_table->remember_table(tbl); try { tbl = new html_table(&html, -1); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_table object"); } page_contents->insert_tag(string("*** TAB -> COL ***")); if (tab_defs != 0 /* nullptr */) tbl->tab_stops->init(tab_defs); start_of_table = 0 /* nullptr */; last = 0 /* nullptr */; } type_of_col = col_tag; found_col = true; ncol = g->get_arg(); align = 'L'; colmin = 0; colmax = 0; } else if (g->is_tab()) { type_of_col = tab_tag; colmin = g->get_tab_args(&align); align = 'L'; // for now as 'C' and 'R' are broken ncol = tbl->find_tab_column(colmin); colmin += pageoffset + get_troff_indent(); colmax = tbl->get_tab_pos(ncol+1); if (colmax > 0) colmax += pageoffset + get_troff_indent(); } else if (g->is_tab0()) { if ((col_tag == type_of_col) && (start_of_table != 0 /* nullptr */)) { page_contents->glyphs.move_left(); insert_tab_te(); start_of_table->remember_table(tbl); try { tbl = new html_table(&html, -1); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_table object"); } page_contents->insert_tag(string("*** COL -> TAB ***")); start_of_table = 0 /* nullptr */; last = 0 /* nullptr */; } if (tab_defs != 0 /* nullptr */) tbl->tab_stops->init(tab_defs); type_of_col = tab0_tag; ncol = 1; colmin = 0; colmax = tbl->get_tab_pos(2) + pageoffset + get_troff_indent(); } else if (! g->is_a_tag()) update_min_max(type_of_col, &colmin, &colmax, g); if ((g->is_col() || g->is_tab() || g->is_tab0()) && (start_of_line != 0 /* nullptr */) && (0 /* nullptr */ == start_of_table)) { start_of_table = insert_tab_ts(start_of_line); start_of_line = 0 /* nullptr */; } else if (g->is_ce() && (start_of_table != 0 /* nullptr */)) { add_table_end("*** CE ***"); start_of_table->remember_table(tbl); try { tbl = new html_table(&html, -1); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_table object"); } start_of_table = 0 /* nullptr */; last = 0 /* nullptr */; } else if (g->is_ta()) { tab_defs = g->text_string; if (col_tag == type_of_col) tbl->tab_stops->check_init(tab_defs); if (!tbl->tab_stops->compatible(tab_defs)) { if (start_of_table != 0 /* nullptr */) { add_table_end("*** TABS ***"); start_of_table->remember_table(tbl); try { tbl = new html_table(&html, -1); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_table object"); } start_of_table = 0 /* nullptr */; type_of_col = none_tag; last = 0 /* nullptr */; } tbl->tab_stops->init(tab_defs); } } if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != 0 /* nullptr */)) { // we are in a table and have a glyph if ((0 == ncol) || (! tbl->add_column(ncol, colmin, colmax, align))) { if (0 == ncol) add_table_end("*** NCOL == 0 ***"); else add_table_end("*** CROSSED COLS ***"); start_of_table->remember_table(tbl); try { tbl = new html_table(&html, -1); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for html_table object"); } start_of_table = 0 /* nullptr */; type_of_col = none_tag; last = 0 /* nullptr */; } } /* * move onto next glob, check whether we are starting a new line */ g = page_contents->glyphs.move_right_get_data(); handle_state_assertion(g); if (0 /* nullptr */ == g) { if (found_col) { page_contents->glyphs.start_from_head(); as.reset(); last = g; found_col = false; } } else if (g->is_br() || (nf && g->is_eol())) { do { g = page_contents->glyphs.move_right_get_data(); handle_state_assertion(g); nf = calc_nf(g, nf); } while ((g != 0 /* nullptr */) && (g->is_br() || (nf && g->is_eol()))); start_of_line = g; ncol = 0; if (found_col) last = g; found_col = false; } } while ((g != 0 /* nullptr */) && (! page_contents->glyphs.is_equal_to_head())); #if defined(DEBUG_TABLES) fprintf(stderr, "finished scanning for tables\n"); #endif page_contents->glyphs.start_from_head(); if (start_of_table != 0 /* nullptr */) { if (last != 0 /* nullptr */) while (last != page_contents->glyphs.get_data()) page_contents->glyphs.move_left(); insert_tab_te(); start_of_table->remember_table(tbl); tbl = 0 /* nullptr */; page_contents->insert_tag(string("*** LAST ***")); } } delete tbl; tbl = 0 /* nullptr */; // and reset the registers pageoffset = old_pageoffset; troff_indent = 0; temp_indent = 0; end_tempindent = 0; } void html_printer::flush_page (void) { suppress_sub_sup = true; flush_sbuf(); page_contents->dump_page(); lookahead_for_tables(); page_contents->dump_page(); flush_globs(); current_paragraph->done_para(); current_paragraph->flush_text(); // move onto a new page delete page_contents; #if defined(DEBUG_TABLES) fprintf(stderr, "\n\n*** flushed page ***\n\n"); html.simple_comment("new page called"); #endif try { page_contents = new page; } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for page object"); } } /* * determine_space - works out whether we need to write a space. * If last glyph is adjoining, then emit no space. */ void html_printer::determine_space (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; if (current_paragraph->is_in_pre()) { /* * .nf has been specified */ while (output_hpos < g->minh) { output_hpos += space_width; current_paragraph->emit_space(); } } else { if ((output_vpos != g->minv) || (output_hpos < g->minh)) { current_paragraph->emit_space(); } } } /* * is_line_start - returns true if we are at the start of a line. */ int html_printer::is_line_start (int nf) { int line_start = false; // TODO: boolify int result = true; text_glob *orig = page_contents->glyphs.get_data(); text_glob *g; if (! page_contents->glyphs.is_equal_to_head()) { do { page_contents->glyphs.move_left(); g = page_contents->glyphs.get_data(); result = g->is_a_tag(); if (g->is_fi()) nf = false; else if (g->is_nf()) nf = true; line_start = g->is_col() || g->is_br() || (nf && g->is_eol()); } while ((!line_start) && (result)); /* * now restore our previous position. */ while (page_contents->glyphs.get_data() != orig) page_contents->glyphs.move_right(); } return result; } /* * is_font_courier - returns true if the font, f, is courier. */ // TODO: boolify int html_printer::is_font_courier (font *f) { // XXX: This logic locks us into a font description file naming // scheme. if (f != 0 /* nullptr */) { const char *fontname = f->get_filename(); return((fontname != 0 /* nullptr */) && ('C' == fontname[0])); } return false; } /* * end_font - shuts down the font corresponding to fontname. */ void html_printer::end_font (const char *fontname) { assert(fontname != 0 /* nullptr */); if (0 /* nullptr */ == fontname) return; if (strcmp(fontname, "B") == 0) { current_paragraph->done_bold(); } else if (strcmp(fontname, "I") == 0) { current_paragraph->done_italic(); } else if (strcmp(fontname, "BI") == 0) { current_paragraph->done_bold(); current_paragraph->done_italic(); } else if (strcmp(fontname, "CR") == 0) { current_paragraph->done_tt(); } else if (strcmp(fontname, "CI") == 0) { current_paragraph->done_italic(); current_paragraph->done_tt(); } else if (strcmp(fontname, "CB") == 0) { current_paragraph->done_bold(); current_paragraph->done_tt(); } else if (strcmp(fontname, "CBI") == 0) { current_paragraph->done_bold(); current_paragraph->done_italic(); current_paragraph->done_tt(); } } /* * start_font - starts the font corresponding to name. */ void html_printer::start_font (const char *fontname) { assert(fontname != 0 /* nullptr */); if (0 /* nullptr */ == fontname) return; if (strcmp(fontname, "R") == 0) { current_paragraph->done_bold(); current_paragraph->done_italic(); current_paragraph->done_tt(); } else if (strcmp(fontname, "B") == 0) { current_paragraph->do_bold(); } else if (strcmp(fontname, "I") == 0) { current_paragraph->do_italic(); } else if (strcmp(fontname, "BI") == 0) { current_paragraph->do_bold(); current_paragraph->do_italic(); } else if (strcmp(fontname, "CR") == 0) { if ((! fill_on) && is_courier_until_eol() && is_line_start(! fill_on)) { current_paragraph->do_pre(); } current_paragraph->do_tt(); } else if (strcmp(fontname, "CI") == 0) { if ((! fill_on) && is_courier_until_eol() && is_line_start(! fill_on)) { current_paragraph->do_pre(); } current_paragraph->do_tt(); current_paragraph->do_italic(); } else if (strcmp(fontname, "CB") == 0) { if ((! fill_on) && is_courier_until_eol() && is_line_start(! fill_on)) { current_paragraph->do_pre(); } current_paragraph->do_tt(); current_paragraph->do_bold(); } else if (strcmp(fontname, "CBI") == 0) { if ((! fill_on) && is_courier_until_eol() && is_line_start(! fill_on)) { current_paragraph->do_pre(); } current_paragraph->do_tt(); current_paragraph->do_italic(); current_paragraph->do_bold(); } } /* * start_size - from is old font size, to is the new font size. * The HTML elements <big> and <small> respectively * increase and decrease the font size by 20%. We try and * map these onto glyph sizes. */ void html_printer::start_size (int from, int to) { if (from < to) { while (from < to) { current_paragraph->do_big(); from += SIZE_INCREMENT; } } else if (from > to) { while (from > to) { current_paragraph->do_small(); from -= SIZE_INCREMENT; } } } /* * do_font - checks to see whether we need to alter the html font. */ void html_printer::do_font (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; /* * check if the output_style.point_size has not been set yet * this allow users to place .ps at the top of their troff files * and grohtml can then treat the .ps value as the base font size (3) */ if (-1 == output_style.point_size) { output_style.point_size = pointsize; } if (g->text_style.f != output_style.f) { if (output_style.f != 0 /* nullptr */) { end_font(output_style.f->get_filename()); } output_style.f = g->text_style.f; if (output_style.f != 0 /* nullptr */) { start_font(output_style.f->get_filename()); } } if (output_style.point_size != g->text_style.point_size) { do_sup_or_sub(g); if ((output_style.point_size > 0) && (g->text_style.point_size > 0)) { start_size(output_style.point_size, g->text_style.point_size); } if (g->text_style.point_size > 0) { output_style.point_size = g->text_style.point_size; } } if (output_style.col != g->text_style.col) { current_paragraph->done_color(); output_style.col = g->text_style.col; current_paragraph->do_color(&output_style.col); } } /* * start_subscript - returns true if, g, looks like a subscript start. */ // TODO: boolify int html_printer::start_subscript (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return false; int r = font::res; int height = output_style.point_size*r/72; return ((output_style.point_size != 0) && (output_vpos < g->minv) && (output_vpos-height > g->maxv) && (output_style.point_size > g->text_style.point_size)); } /* * start_superscript - returns true if, g, looks like a superscript * start. */ // TODO: boolify int html_printer::start_superscript (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return false; int r = font::res; int height = output_style.point_size*r/72; return ((output_style.point_size != 0) && (output_vpos > g->minv) && (output_vpos-height < g->maxv) && (output_style.point_size > g->text_style.point_size)); } /* * end_subscript - returns true if, g, looks like the end of a * subscript. */ // TODO: boolify int html_printer::end_subscript (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return false; int r = font::res; int height = output_style.point_size*r/72; return ((output_style.point_size != 0) && (g->minv < output_vpos) && (output_vpos-height > g->maxv) && (output_style.point_size < g->text_style.point_size)); } /* * end_superscript - returns true if, g, looks like the end of a * superscript. */ // TODO: boolify int html_printer::end_superscript (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return false; int r = font::res; int height = output_style.point_size*r/72; return ((output_style.point_size != 0) && (g->minv > output_vpos) && (output_vpos-height < g->maxv) && (output_style.point_size < g->text_style.point_size)); } /* * do_sup_or_sub - checks to see whether the next glyph is a * subscript/superscript start/end and it calls the * services of html-text to issue the appropriate tags. */ void html_printer::do_sup_or_sub (text_glob *g) { if (! suppress_sub_sup) { if (start_subscript(g)) { current_paragraph->do_sub(); } else if (start_superscript(g)) { current_paragraph->do_sup(); } else if (end_subscript(g)) { current_paragraph->done_sub(); } else if (end_superscript(g)) { current_paragraph->done_sup(); } } } /* * do_end_para - writes out the html text after shutting down the * current paragraph. */ void html_printer::do_end_para (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; do_font(g); current_paragraph->done_para(); current_paragraph->remove_para_space(); html.put_string(g->text_string+9); output_vpos = g->minv; output_hpos = g->maxh; output_vpos_max = g->maxv; suppress_sub_sup = false; } /* * emit_html - write out the html text */ void html_printer::emit_html (text_glob *g) { assert(g != 0 /* nullptr */); if (0 /* nullptr */ == g) return; do_font(g); determine_space(g); current_paragraph->do_emittext(g->text_string, g->text_length); output_vpos = g->minv; output_hpos = g->maxh; output_vpos_max = g->maxv; suppress_sub_sup = false; } /* * flush_sbuf - flushes the current sbuf into the list of glyphs. */ void html_printer::flush_sbuf() { if (sbuf.length() > 0) { int r=font::res; // resolution of the device set_style(sbuf_style); if (overstrike_detected && (! is_bold(sbuf_style.f))) { font *bold_font = make_bold(sbuf_style.f); if (bold_font != 0 /* nullptr */) sbuf_style.f = bold_font; } page_contents->add(&sbuf_style, sbuf, line_number, (sbuf_vpos - (sbuf_style.point_size * r / 72)), sbuf_start_hpos, sbuf_vpos, sbuf_end_hpos); output_hpos = sbuf_end_hpos; output_vpos = sbuf_vpos; last_sbuf_length = 0; sbuf_prev_hpos = sbuf_end_hpos; overstrike_detected = false; sbuf.clear(); } } void html_printer::set_line_thickness(const environment *env) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return; line_thickness = env->size; } void html_printer::draw(int code, int *p, int np, const environment *env) { assert(p != 0 /* nullptr */); assert(env != 0 /* nullptr */); if ((0 /* nullptr */ == p) || (0 /* nullptr */ == env)) return; switch (code) { case 'l': # if 0 if (2 == np) { page_contents->add_line(&sbuf_style, line_number, env->hpos, env->vpos, (env->hpos + p[0]), (env->vpos + p[1]), line_thickness); } else { error("2 arguments required for line"); } # endif break; case 't': { if (0 == np) { line_thickness = -1; } else { // troff gratuitously adds an extra 0 if ((np != 1) && (np != 2)) { error("0 or 1 argument required for thickness"); break; } line_thickness = p[0]; } break; } case 'P': break; case 'p': break; case 'E': break; case 'e': break; case 'C': break; case 'c': break; case 'a': break; case '~': break; case 'f': break; case 'F': // fill with color env->fill delete background; try { background = new color; } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for color object"); } *background = *env->fill; break; default: error("unrecognized drawing command '%1'", char(code)); break; } } html_printer::html_printer() : html(0, MAX_LINE_LENGTH), no_of_printed_pages(0), last_sbuf_length(0), overstrike_detected(false), output_hpos(-1), output_vpos(-1), output_vpos_max(-1), line_thickness(-1), inside_font_style(0), page_number(0), header_indent(-1), suppress_sub_sup(true), cutoff_heading(100), indent(0), table(0), end_center(0), end_tempindent(0), next_tag(INLINE), fill_on(true), max_linelength(-1), linelength(0), pageoffset(0), troff_indent(0), device_indent(0), temp_indent(0), pointsize(base_point_size), line_number(0), background(default_background), seen_indent(false), next_indent(0), seen_pageoffset(false), next_pageoffset(0), seen_linelength(false), next_linelength(0), seen_center(false), next_center(0), seen_space(0), seen_break(0), current_column(0), row_space(false) { file_list.add_new_file(xtmpfile()); html.set_file(file_list.get_file()); if (font::hor != 24) fatal("horizontal motion quantum must be 24"); if (font::vert != 40) fatal("vertical motion quantum must be 40"); #if 0 // should be sorted html.. if ((font::res % (font::sizescale*72)) != 0) fatal("res must be a multiple of 72*sizescale"); #endif int r = font::res; int point = 0; while ((r % 10) == 0) { r /= 10; point++; } res = r; html.set_fixed_point(point); space_glyph = name_to_glyph("space"); space_width = font::hor; paper_length = font::paperlength; linelength = font::res*13/2; if (0 == paper_length) paper_length = 11*font::res; try { page_contents = new page; } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for page object"); } } /* * add_to_sbuf - adds character code or name to the sbuf. */ void html_printer::add_to_sbuf (glyph *g, const string &s) { if (0 /* nullptr */ == sbuf_style.f) return; const char *html_glyph = 0; unsigned int code = sbuf_style.f->get_code(g); if (s.empty()) { if (sbuf_style.f->contains(g)) html_glyph = get_html_entity(sbuf_style.f->get_code(g)); else html_glyph = 0; if ((0 /* nullptr */ == html_glyph) && (code >= UNICODE_DESC_START)) html_glyph = static_cast<bool>(charset_encoding) ? to_utf8_string(code) : to_numerical_char_ref(code); } else html_glyph = get_html_translation(sbuf_style.f, s); last_sbuf_length = sbuf.length(); if (0 /* nullptr */ == html_glyph) sbuf += ((char)code); else sbuf += html_glyph; } // TODO: boolify int html_printer::sbuf_continuation (glyph *g, const char *name, const environment *env, int w) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return false; /* * lets see whether the glyph is closer to the end of sbuf */ if ((sbuf_end_hpos == env->hpos) || ((sbuf_prev_hpos < sbuf_end_hpos) && (env->hpos < sbuf_end_hpos) && ((sbuf_end_hpos-env->hpos < env->hpos-sbuf_prev_hpos)))) { add_to_sbuf(g, name); sbuf_prev_hpos = sbuf_end_hpos; sbuf_end_hpos += w + sbuf_kern; return true; } else { if ((env->hpos >= sbuf_end_hpos) && ((0 == sbuf_kern) || (sbuf_end_hpos - sbuf_kern != env->hpos))) { /* * lets see whether a space is needed or not */ if (env->hpos-sbuf_end_hpos < space_width) { add_to_sbuf(g, name); sbuf_prev_hpos = sbuf_end_hpos; sbuf_end_hpos = env->hpos + w; return true; } } } return false; } /* * get_html_translation - given the position of the character and its * name return the device encoding for such * character. */ const char *get_html_translation (font *f, const string &name) { if ((0 /* nullptr */ == f) || name.empty()) return 0 /* nullptr */; else { glyph *g = name_to_glyph((name + '\0').contents()); if (f->contains(g)) return get_html_entity(f->get_code(g)); else return 0 /* nullptr */; } } /* * get_html_entity - given a Unicode character's code point, return an * HTML entity that represents the character, if the * character cannot represent itself in all contexts. * the return value, if not a null pointer, is * allocated in a static buffer and is only valid * until the next call of this function. */ static const char *get_html_entity (unsigned int code) { if (code < UNICODE_DESC_START) { switch (code) { case 0x0022: return """; case 0x0026: return "&"; case 0x0027: return "'"; case 0x003C: return "<"; case 0x003E: return ">"; default: return 0; } } else if (CHARSET_UTF8 == charset_encoding) { return to_utf8_string(code); } else { if (html4 == dialect) { switch (code) { case 0x00A0: return " "; case 0x00A1: return "¡"; case 0x00A2: return "¢"; case 0x00A3: return "£"; case 0x00A4: return "¤"; case 0x00A5: return "¥"; case 0x00A6: return "¦"; case 0x00A7: return "§"; case 0x00A8: return "¨"; case 0x00A9: return "©"; case 0x00AA: return "ª"; case 0x00AB: return "«"; case 0x00AC: return "¬"; case 0x00AE: return "®"; case 0x00AF: return "¯"; case 0x00B0: return "°"; case 0x00B1: return "±"; case 0x00B2: return "²"; case 0x00B3: return "³"; case 0x00B4: return "´"; case 0x00B5: return "µ"; case 0x00B6: return "¶"; case 0x00B7: return "·"; case 0x00B8: return "¸"; case 0x00B9: return "¹"; case 0x00BA: return "º"; case 0x00BB: return "»"; case 0x00BC: return "¼"; case 0x00BD: return "½"; case 0x00BE: return "¾"; case 0x00BF: return "¿"; case 0x00C0: return "À"; case 0x00C1: return "Á"; case 0x00C2: return "Â"; case 0x00C3: return "Ã"; case 0x00C4: return "Ä"; case 0x00C5: return "Å"; case 0x00C6: return "Æ"; case 0x00C7: return "Ç"; case 0x00C8: return "È"; case 0x00C9: return "É"; case 0x00CA: return "Ê"; case 0x00CB: return "Ë"; case 0x00CC: return "Ì"; case 0x00CD: return "Í"; case 0x00CE: return "Î"; case 0x00CF: return "Ï"; case 0x00D0: return "Ð"; case 0x00D1: return "Ñ"; case 0x00D2: return "Ò"; case 0x00D3: return "Ó"; case 0x00D4: return "Ô"; case 0x00D5: return "Õ"; case 0x00D6: return "Ö"; case 0x00D7: return "×"; case 0x00D8: return "Ø"; case 0x00D9: return "Ù"; case 0x00DA: return "Ú"; case 0x00DB: return "Û"; case 0x00DC: return "Ü"; case 0x00DD: return "Ý"; case 0x00DE: return "Þ"; case 0x00DF: return "ß"; case 0x00E0: return "à"; case 0x00E1: return "á"; case 0x00E2: return "â"; case 0x00E3: return "ã"; case 0x00E4: return "ä"; case 0x00E5: return "å"; case 0x00E6: return "æ"; case 0x00E7: return "ç"; case 0x00E8: return "è"; case 0x00E9: return "é"; case 0x00EA: return "ê"; case 0x00EB: return "ë"; case 0x00EC: return "ì"; case 0x00ED: return "í"; case 0x00EE: return "î"; case 0x00EF: return "ï"; case 0x00F0: return "ð"; case 0x00F1: return "ñ"; case 0x00F2: return "ò"; case 0x00F3: return "ó"; case 0x00F4: return "ô"; case 0x00F5: return "õ"; case 0x00F6: return "ö"; case 0x00F7: return "÷"; case 0x00F8: return "ø"; case 0x00F9: return "ù"; case 0x00FA: return "ú"; case 0x00FB: return "û"; case 0x00FC: return "ü"; case 0x00FD: return "ý"; case 0x00FE: return "þ"; case 0x00FF: return "ÿ"; case 0x0152: return "Œ"; case 0x0153: return "œ"; case 0x0160: return "Š"; case 0x0161: return "š"; case 0x0178: return "Ÿ"; case 0x0192: return "ƒ"; case 0x0391: return "Α"; case 0x0392: return "Β"; case 0x0393: return "Γ"; case 0x0394: return "Δ"; case 0x0395: return "Ε"; case 0x0396: return "Ζ"; case 0x0397: return "Η"; case 0x0398: return "Θ"; case 0x0399: return "Ι"; case 0x039A: return "Κ"; case 0x039B: return "Λ"; case 0x039C: return "Μ"; case 0x039D: return "Ν"; case 0x039E: return "Ξ"; case 0x039F: return "Ο"; case 0x03A0: return "Π"; case 0x03A1: return "Ρ"; case 0x03A3: return "Σ"; case 0x03A4: return "Τ"; case 0x03A5: return "Υ"; case 0x03A6: return "Φ"; case 0x03A7: return "Χ"; case 0x03A8: return "Ψ"; case 0x03A9: return "Ω"; case 0x03B1: return "α"; case 0x03B2: return "β"; case 0x03B3: return "γ"; case 0x03B4: return "δ"; case 0x03B5: return "ε"; case 0x03B6: return "ζ"; case 0x03B7: return "η"; case 0x03B8: return "θ"; case 0x03B9: return "ι"; case 0x03BA: return "κ"; case 0x03BB: return "λ"; case 0x03BC: return "μ"; case 0x03BD: return "ν"; case 0x03BE: return "ξ"; case 0x03BF: return "ο"; case 0x03C0: return "π"; case 0x03C1: return "ρ"; case 0x03C2: return "ς"; case 0x03C3: return "σ"; case 0x03C4: return "τ"; case 0x03C5: return "υ"; case 0x03C6: return "φ"; case 0x03C7: return "χ"; case 0x03C8: return "ψ"; case 0x03C9: return "ω"; case 0x03D1: return "ϑ"; case 0x03D6: return "ϖ"; case 0x2013: return "–"; case 0x2014: return "—"; case 0x2018: return "‘"; case 0x2019: return "’"; case 0x201A: return "‚"; case 0x201C: return "“"; case 0x201D: return "”"; case 0x201E: return "„"; case 0x2020: return "†"; case 0x2021: return "‡"; case 0x2022: return "•"; case 0x2030: return "‰"; case 0x2032: return "′"; case 0x2033: return "″"; case 0x2039: return "‹"; case 0x203A: return "›"; case 0x203E: return "‾"; case 0x2044: return "⁄"; case 0x20AC: return "€"; case 0x2111: return "ℑ"; case 0x2118: return "℘"; case 0x211C: return "ℜ"; case 0x2122: return "™"; case 0x2135: return "ℵ"; case 0x2190: return "←"; case 0x2191: return "↑"; case 0x2192: return "→"; case 0x2193: return "↓"; case 0x2194: return "↔"; case 0x21D0: return "⇐"; case 0x21D1: return "⇑"; case 0x21D2: return "⇒"; case 0x21D3: return "⇓"; case 0x21D4: return "⇔"; case 0x2200: return "∀"; case 0x2202: return "∂"; case 0x2203: return "∃"; case 0x2205: return "∅"; case 0x2207: return "∇"; case 0x2208: return "∈"; case 0x2209: return "∉"; case 0x220B: return "∋"; case 0x220F: return "∏"; case 0x2211: return "∑"; case 0x2212: return "−"; case 0x2217: return "∗"; case 0x221A: return "√"; case 0x221D: return "∝"; case 0x221E: return "∞"; case 0x2220: return "∠"; case 0x2227: return "∧"; case 0x2228: return "∨"; case 0x2229: return "∩"; case 0x222A: return "∪"; case 0x222B: return "∫"; case 0x2234: return "∴"; case 0x223C: return "∼"; case 0x2245: return "≅"; case 0x2248: return "≈"; case 0x2260: return "≠"; case 0x2261: return "≡"; case 0x2264: return "≤"; case 0x2265: return "≥"; case 0x2282: return "⊂"; case 0x2283: return "⊃"; case 0x2284: return "⊄"; case 0x2286: return "⊆"; case 0x2287: return "⊇"; case 0x2295: return "⊕"; case 0x2297: return "⊗"; case 0x22A5: return "⊥"; case 0x22C5: return "⋅"; case 0x2308: return "⌈"; case 0x2309: return "⌉"; case 0x230A: return "⌊"; case 0x230B: return "⌋"; case 0x2329: return "⟨"; case 0x232A: return "⟩"; case 0x25CA: return "◊"; case 0x2660: return "♠"; case 0x2663: return "♣"; case 0x2665: return "♥"; case 0x2666: return "♦"; case 0x27E8: return "⟨"; case 0x27E9: return "⟩"; default: return (static_cast<bool>(charset_encoding) ? to_utf8_string(code) : to_numerical_char_ref(code)); } } else { return (static_cast<bool>(charset_encoding) ? to_utf8_string(code) : to_numerical_char_ref(code)); } } } /* * overstrike - returns true if the glyph (i, name) is going to * overstrike a previous glyph in sbuf. If true the font * is changed to bold and the previous sbuf is flushed. */ // TODO: boolify int html_printer::overstrike(glyph *g, const char *name, const environment *env, int w) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return false; if ((env->hpos < sbuf_end_hpos) || ((sbuf_kern != 0) && (sbuf_end_hpos - sbuf_kern < env->hpos))) { /* * at this point we have detected an overlap */ if (overstrike_detected) { /* already detected, remove previous glyph and use this glyph */ sbuf.set_length(last_sbuf_length); add_to_sbuf(g, name); sbuf_end_hpos = env->hpos + w; return true; } else { /* first time we have detected an overstrike in the sbuf */ sbuf.set_length(last_sbuf_length); /* remove previous glyph */ if (! is_bold(sbuf_style.f)) flush_sbuf(); overstrike_detected = true; add_to_sbuf(g, name); sbuf_end_hpos = env->hpos + w; return true; } } return false; } /* * set_char - adds a character into the sbuf if it is a continuation * with the previous word otherwise flush the current sbuf * and add character anew. */ void html_printer::set_char(glyph *g, font *f, const environment *env, int w, const char *name) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return; style sty(f, env->size, env->height, env->slant, env->fontno, *env->col); if (sty.slant != 0) { if ((sty.slant > 80) || (sty.slant < -80)) { error("slant of %1 degrees out of range", sty.slant); sty.slant = 0; } } if (((!sbuf.empty()) && (sty == sbuf_style) && (sbuf_vpos == env->vpos)) && (sbuf_continuation(g, name, env, w) || overstrike(g, name, env, w))) return; flush_sbuf(); if (0 /* nullptr */ == sbuf_style.f) sbuf_style = sty; add_to_sbuf(g, name); sbuf_end_hpos = env->hpos + w; sbuf_start_hpos = env->hpos; sbuf_prev_hpos = env->hpos; sbuf_vpos = env->vpos; sbuf_style = sty; sbuf_kern = 0; } /* * set_numbered_char - handle numbered characters. Negative values are * interpreted as unbreakable spaces; the value * (taken positive) gives the width. */ void html_printer::set_numbered_char(int num, const environment *env, int *widthp) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return; int nbsp_width = 0; if (num < 0) { nbsp_width = -num; num = 160; // } glyph *g = number_to_glyph(num); int fn = env->fontno; if ((fn < 0) || (fn >= nfonts)) { error("invalid font position '%1'", fn); return; } font *f = font_table[fn]; if (0 /* nullptr */ == f) { error("no font mounted at position %1", fn); return; } if (!f->contains(g)) { error("font description file '%1' has no glyph at index %2", f->get_filename(), num); return; } int w; if (nbsp_width > 0) w = nbsp_width; else w = f->get_width(g, env->size); w = round_width(w); if (widthp != 0 /* nullptr */) *widthp = w; set_char(g, f, env, w, 0 /* nullptr */); } // XXX: Except for `w = round_width(w);`, this seems to be identical // to the overridden `printer::set_char_and_width()`. glyph *html_printer::set_char_and_width(const char *nm, const environment *env, int *widthp, font **f) { assert(nm != 0 /* nullptr */); assert(env != 0 /* nullptr */); assert(f != 0 /* nullptr */); if ((0 /* nullptr */ == nm) || (0 /* nullptr */ == env) || (0 /* nullptr */ == f)) return UNDEFINED_GLYPH; glyph *g = name_to_glyph(nm); int fn = env->fontno; if ((fn < 0) || (fn >= nfonts)) { error("invalid font position '%1'", fn); return UNDEFINED_GLYPH; } *f = font_table[fn]; if (0 /* nullptr */ == *f) { error("no font mounted at position %1", fn); return UNDEFINED_GLYPH; } if (!(*f)->contains(g)) { if ((nm[0] != '\0') && ('\0' == nm[1])) error("font description file '%1' lacks glyph for ordinary" " character '%2'", (*f)->get_filename(), nm[0]); else error("font description file '%1' lacks glyph for special" " character '%2'", (*f)->get_filename(), nm); return UNDEFINED_GLYPH; } int w = (*f)->get_width(g, env->size); w = round_width(w); if (widthp != 0 /* nullptr */) *widthp = w; return g; } /* * write_title - writes the title to this document */ void html_printer::write_title (int in_head) { if (title.has_been_found) { if (in_head) { html.put_string("<title>"); html.put_string(title.text); html.put_string("</title>").nl().nl(); } else { title.has_been_written = true; if (title.with_h1) { if (xhtml == dialect) html.put_string("<h1>"); else html.put_string("<h1 align=\"center\">"); html.put_string(title.text); html.put_string("</h1>").nl().nl(); } } } else if (in_head) { // place empty title tags to help conform to 'tidy' html.put_string("<title></title>").nl(); } } /* * write_rule - emits HTML rule element if the auto_rule is true. */ static void write_rule (void) { if (auto_rule) { if (xhtml == dialect) fputs("<hr/>\n", stdout); else fputs("<hr>\n", stdout); } } void html_printer::begin_page(int n) { page_number = n; #if defined(DEBUGGING) html.begin_comment("Page: ") .put_string(i_to_a(page_number)).end_comment();; #endif no_of_printed_pages++; output_style.f = 0; output_style.point_size= -1; output_space_code = 32; output_draw_point_size = -1; output_line_thickness = -1; output_hpos = -1; output_vpos = -1; output_vpos_max = -1; try { current_paragraph = new html_text(&html, dialect); } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for text object"); } do_indent(get_troff_indent(), pageoffset, linelength); current_paragraph->do_para("", false); } void html_printer::end_page(int) { flush_sbuf(); flush_page(); } font *html_printer::make_font(const char *nm) { return html_font::load_html_font(nm); } void html_printer::do_body (void) { if (0 /* nullptr */ == background) fputs("<body>\n\n", stdout); else { char buf[(INT_HEXDIGITS * 3) + 1]; unsigned int r, g, b; background->get_rgb(&r, &g, &b); // we have to scale 0..0xFFFF to 0..0xFF sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101); fputs("<body bgcolor=\"#", stdout); fputs(buf, stdout); fputs("\">\n\n", stdout); } } /* * emit_link - generates: <a href="to">name</a> */ void html_printer::emit_link (const string &to, const char *name) { assert(name != 0 /* nullptr */); if (0 /* nullptr */ == name) return; fputs("<a href=\"", stdout); fputs(to.contents(), stdout); fputs("\">", stdout); fputs(name, stdout); fputs("</a>", stdout); } /* * write_navigation - writes out the links which navigate between * file fragments. */ void html_printer::write_navigation (const string &top, const string &prev, const string &next, const string ¤t) { int need_bar = false; if (multiple_files) { current_paragraph->done_para(); write_rule(); if (groff_sig) fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n" "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n" "<colgroup><col class=\"left\"></col>" "<col class=\"right\"></col></colgroup>\n" "<tr><td class=\"left\">", stdout); handle_valid_flag(false); fputs("[ ", stdout); if ((strcmp(prev.contents(), "") != 0) && (prev != top) && (prev != current)) { emit_link(prev, "prev"); need_bar = true; } if ((strcmp(next.contents(), "") != 0) && (next != top) && (next != current)) { if (need_bar) fputs(" | ", stdout); emit_link(next, "next"); need_bar = true; } if (top != "<standard input>" && (strcmp(top.contents(), "") != 0) && (top != current)) { if (need_bar) fputs(" | ", stdout); emit_link(top, "top"); } fputs(" ]\n", stdout); if (groff_sig) { fputs("</td><td class=\"right\"><i><small>" "This document was produced using " "<a href=\"http://www.gnu.org/software/groff/\">" "groff-", stdout); fputs(Version_string, stdout); fputs("</a>.</small></i></td></tr></table>\n", stdout); } write_rule(); } } /* * do_file_components - scan the file list copying each temporary file * in turn. This has twofold use: firstly to emit * section heading links, between file fragments * if required and secondly to generate jobname * file fragments if required. */ void html_printer::do_file_components (void) { int fragment_no = 1; string top; string prev; string next; string current; file_list.start_of_list(); top = string(job_name); if (xhtml == dialect) top += string(".xhtml"); else top += string(".html"); top += '\0'; next = file_list.next_file_name(); next += '\0'; current = next; while (file_list.get_file() != 0 /* nullptr */) { if (fseek(file_list.get_file(), 0L, SEEK_SET) < 0) fatal("unable to seek within temporary file: %1", strerror(errno)); html.copy_file(file_list.get_file()); fclose(file_list.get_file()); file_list.move_next(); if (file_list.is_new_output_file()) { struct tm *t; if (fragment_no > 1) write_navigation(top, prev, next, current); prev = current; current = next; next = file_list.next_file_name(); next += '\0'; string split_file = file_list.file_name(); split_file += '\0'; fflush(stdout); if (!freopen(split_file.contents(), "w", stdout)) { fatal("unable to reopen standard output stream: %1", strerror(errno)); } fragment_no++; if (xhtml == dialect) writeHeadMetaStyle(); if (do_write_creator_comment) { html.begin_comment("Creator : ") .put_string("groff ") .put_string("version ") .put_string(Version_string) .end_comment(); } if (do_write_date_comment) { t = current_time(); html.begin_comment("CreationDate: ") .put_string(asctime(t), strlen(asctime(t))-1) .end_comment(); } if (html4 == dialect) writeHeadMetaStyle(); html.put_string("<title>"); html.put_string(split_file.contents()); html.put_string("</title>").nl().nl(); fputs(head_info.contents(), stdout); fputs("</head>\n", stdout); write_navigation(top, prev, next, current); } if (file_list.are_links_required()) header.write_headings(stdout, true); } if (fragment_no > 1) write_navigation(top, prev, next, current); else { assert(current_paragraph != 0 /* nullptr */); // If assertion skipped because `NDEBUG` is defined, we don't want // to return early from this function; we want to wrap up the // document and get out. if (current_paragraph != 0 /* nullptr */) current_paragraph->done_para(); write_rule(); if (valid_flag) { if (groff_sig) fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n" "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n" "<colgroup><col class=\"left\"></col>" "<col class=\"right\"></col></colgroup>\n" "<tr><td class=\"left\">", stdout); handle_valid_flag(true); if (groff_sig) { fputs("</td><td class=\"right\"><i><small>" "This document was produced using " "<a href=\"http://www.gnu.org/software/groff/\">" "groff-", stdout); fputs(Version_string, stdout); fputs("</a>.</small></i></td></tr></table>\n", stdout); } write_rule(); } } } /* * writeHeadMetaStyle - emits the <head> <meta> and <style> tags and * related information. */ void html_printer::writeHeadMetaStyle (void) { if (html4 == dialect) { fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional" "//EN\"\n", stdout); fputs("\"http://www.w3.org/TR/html4/loose.dtd\">\n", stdout); fputs("<html>\n", stdout); fputs("<head>\n", stdout); fputs("<meta name=\"generator\" " "content=\"groff -Thtml, see www.gnu.org\">\n", stdout); fputs("<meta http-equiv=\"Content-Type\" " "content=\"text/html; charset=", stdout); fputs(static_cast<bool>(charset_encoding) ? "UTF-8" : "US-ASCII", stdout); fputs("\">\n", stdout); fputs("<meta name=\"Content-Style\" content=\"text/css\">\n", stdout); fputs("<style type=\"text/css\">\n", stdout); } else { fputs("<?xml version=\"1.0\" encoding=\"", stdout); fputs(static_cast<bool>(charset_encoding) ? "UTF-8" : "US-ASCII", stdout); fputs("\"?>\n", stdout); fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" " "xml:lang=\"en\">\n", stdout); fputs("<head>\n", stdout); fputs("<meta name=\"generator\" " "content=\"groff -Txhtml, see www.gnu.org\"/>\n", stdout); fputs("<meta http-equiv=\"Content-Type\" " "content=\"application/xhtml+xml; charset=", stdout); fputs(static_cast<bool>(charset_encoding) ? "UTF-8" : "US-ASCII", stdout); fputs("\"/>\n", stdout); fputs("<style type=\"text/css\">\n", stdout); fputs(" .center { text-align: center }\n", stdout); fputs(" .right { text-align: right }\n", stdout); } fputs(" p { margin-top: 0; margin-bottom: 0; " "vertical-align: top }\n", stdout); fputs(" pre { margin-top: 0; margin-bottom: 0; " "vertical-align: top }\n", stdout); fputs(" table { margin-top: 0; margin-bottom: 0; " "vertical-align: top }\n", stdout); fputs(" h1 { text-align: center }\n", stdout); fputs(" body { max-width: 80ch; margin-left: auto; " "margin-right: auto; padding: 1em; }\n", stdout); fputs(" img { max-width: 100%; height: auto; }\n", stdout); fputs(" .code-display { display: block; " "font-family: monospace; }\n", stdout); fputs(" .code-display p { margin-top: 0; margin-bottom: 0;" " }\n", stdout); fputs("</style>\n", stdout); } html_printer::~html_printer() { struct tm *t; current_lineno = 0; // At this point, we've read all the input. if (current_paragraph) current_paragraph->flush_text(); html.end_line(); html.set_file(stdout); if (xhtml == dialect) writeHeadMetaStyle(); if (do_write_creator_comment) { html.begin_comment("Creator : ") .put_string("groff ") .put_string("version ") .put_string(Version_string) .end_comment(); } if (do_write_date_comment) { t = current_time(); html.begin_comment("CreationDate: ") .put_string(asctime(t), strlen(asctime(t))-1) .end_comment(); } if (html4 == dialect) writeHeadMetaStyle(); write_title(true); head_info += '\0'; fputs(head_info.contents(), stdout); fputs("</head>\n", stdout); do_body(); write_title(false); header.write_headings(stdout, false); write_rule(); #if defined(DEBUGGING) html.begin_comment("Total number of pages: ") .put_string(i_to_a(no_of_printed_pages)).end_comment(); #endif html.end_line(); html.end_line(); if (multiple_files) { fputs("</body>\n", stdout); fputs("</html>\n", stdout); do_file_components(); } else { do_file_components(); fputs("</body>\n", stdout); fputs("</html>\n", stdout); } } /* * get_str - returns a duplicate of string, s. The duplicate * string is terminated at the next ',' or ']'. */ static char *get_str (const char *s, char **n) { assert(s != 0 /* nullptr */); assert(n != 0 /* nullptr */); if ((0 /* nullptr */ == s) || (0 /* nullptr */ == n)) return 0 /* nullptr */; int i = 0; char *v = 0 /* nullptr */; while ((s[i] != '\0') && (s[i] != ',') && (s[i] != ']')) i++; if (i>0) { size_t amount = i + 1; try { v = new char[amount]; } catch (const std::bad_alloc &exc) { fatal("cannot allocate %1 bytes for char array", amount); } memcpy(v, s, i+1); v[i] = '\0'; if (',' == s[i]) (*n) = const_cast<char *>(&s[i+1]); else (*n) = const_cast<char *>(&s[i]); return v; } if (',' == s[i]) (*n) = const_cast<char *>(&s[1]); else (*n) = const_cast<char *>(s); return 0 /* nullptr */; } /* * make_val - creates a string from `v` if `s` is a null pointer. */ // XXX: Can our callers handle a null pointer return? static char *make_val (char *s, int v, char *id, char *f, char *l) { assert(id != 0 /* nullptr */); assert(f != 0 /* nullptr */); assert(l != 0 /* nullptr */); if ((0 /* nullptr */ == id) || (0 /* nullptr */ == f) || (0 /* nullptr */ == l)) return 0 /* nullptr */; if (0 /* nullptr */ == s) { char buf[30]; sprintf(buf, "%d", v); return strsave(buf); } else { /* * check that value, s, is the same as, v. */ char *t = s; while ('=' == *t) t++; if (atoi(t) != v) { if (0 /* nullptr */ == f) f = const_cast<char *>("stdin"); if (0 /* nullptr */ == l) f = const_cast<char *>("<none>"); fprintf(stderr, "%s:%s:%s: assertion failed at id%s;" " expected %d, got %s\n", program_name, f, l, id, v, s); } return s; } } /* * handle_assertion - handles the assertions created via .www:ASSERT * in www.tmac. See www.tmac for examples. This * method should be called as we are parsing the * ditroff input. It checks the x, y position * assertions. It does _not_ check the troff state * assertions as these are unknown at this point. */ void html_printer::handle_assertion (int minv, int minh, int maxv, int maxh, const char *s) { char *n; char *cmd = get_str(s, &n); char *id = get_str(n, &n); char *val = get_str(n, &n); char *file= get_str(n, &n); char *line= get_str(n, &n); if (strcmp(cmd, "assertion:[x") == 0) as.addx(cmd, id, make_val(val, minh, id, file, line), file, line); else if (strcmp(cmd, "assertion:[y") == 0) as.addy(cmd, id, make_val(val, minv, id, file, line), file, line); else if (strncmp(cmd, "assertion:[", strlen("assertion:[")) == 0) page_contents->add_tag(&sbuf_style, string(s), line_number, minv, minh, maxv, maxh); } /* * build_state_assertion - builds the troff state assertions. */ void html_printer::handle_state_assertion (text_glob *g) { if ((g != 0 /* nullptr */) && g->is_a_tag() && (strncmp(g->text_string, "assertion:[", 11) == 0)) { char *n = const_cast<char *>(&g->text_string[11]); char *cmd = get_str(n, &n); char *val = get_str(n, &n); (void)get_str(n, &n); // unused char *file= get_str(n, &n); char *line= get_str(n, &n); as.build(cmd, val, file, line); } } /* * special - handle all x X requests from troff. For post-html they * allow users to pass raw HTML commands, turn auto linked * headings off/on, and so forth. */ void html_printer::special(char *s, const environment *env, char type) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return; if (type != 'p') return; if (s != 0 /* nullptr */) { flush_sbuf(); if (env->fontno >= 0) { style sty(get_font_from_index(env->fontno), env->size, env->height, env->slant, env->fontno, *env->col); sbuf_style = sty; } if (strncmp(s, "html:", 5) == 0) { int r=font::res; /* resolution of the device */ font *f=sbuf_style.f; if (0 /* nullptr */ == f) f = font::load_font("TR"); /* * pass rest of string through to html output during flush */ page_contents->add_and_encode(&sbuf_style, string(&s[5]), line_number, env->vpos-env->size*r/72, env->hpos, env->vpos , env->hpos, false); /* * assume that the html command has no width, if it does then * hopefully troff will have fudged this in a macro by requesting * that the formatting move right by the appropriate amount. */ } else if ((strncmp(s, "html</p>:", 9) == 0) || (strncmp(s, "html<?p>:", 9) == 0) || (strncmp(s, "math<?p>:", 9) == 0)) { int r=font::res; /* resolution of the device */ font *f=sbuf_style.f; string t; if (0 /* nullptr */ == f) f = font::load_font("TR"); if (strncmp(s, "math<?p>:", 9) == 0) { if (strncmp(&s[9], "<math>", 6) == 0) { s[9] = '\0'; t = s; t += "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">"; t += &s[15]; t += '\0'; s = &t[0]; } } /* * need to pass all of string through to html output during flush */ page_contents->add_and_encode(&sbuf_style, string(s), line_number, env->vpos-env->size*r/72, env->hpos, env->vpos , env->hpos, true); /* * assume that the html command has no width, if it does then * hopefully troff will have fudged this in a macro by * requesting that the formatting move right by the appropriate * amount. */ } else if (strncmp(s, "index:", 6) == 0) { cutoff_heading = atoi(&s[6]); } else if (strncmp(s, "assertion:[", 11) == 0) { int r=font::res; /* resolution of the device */ handle_assertion(env->vpos-env->size*r/72, env->hpos, env->vpos, env->hpos, s); } } } /* * devtag - handles device troff tags sent from the 'troff'. * These include the troff state machine tags: * .br, .sp, .in, .tl, .ll etc * * (see man 5 grohtml_tags). */ void html_printer::devtag (char *s, const environment *env, char type) { assert(env != 0 /* nullptr */); if (0 /* nullptr */ == env) return; if (type != 'p') return; if (s != 0 /* nullptr */) { flush_sbuf(); if (env->fontno >= 0) { style sty(get_font_from_index(env->fontno), env->size, env->height, env->slant, env->fontno, *env->col); sbuf_style = sty; } if (strncmp(s, "devtag:", strlen("devtag:")) == 0) { int r=font::res; /* resolution of the device */ page_contents->add_tag(&sbuf_style, string(s), line_number, env->vpos-env->size*r/72, env->hpos, env->vpos , env->hpos); } } } /* * taken from number.cpp in src/roff/troff, [hunits::hunits(units x)] */ int html_printer::round_width(int x) { int r = font::hor; int n; // don't depend on rounding direction for division of negative ints if (1 == r) n = x; else n = (x < 0 ? -((-x + r/2 - 1)/r) : (x + r/2 - 1)/r); return n * r; } /* * handle_valid_flag - emits a valid XHTML 1.1 or HTML 4.01 button, * provided -V was supplied on the command line. */ void html_printer::handle_valid_flag (int needs_para) { if (valid_flag) { if (needs_para) fputs("<p>", stdout); if (xhtml == dialect) fputs("<a href=\"http://validator.w3.org/check?uri=referer\">" "<img src=\"http://www.w3.org/Icons/valid-xhtml11-blue\" " "alt=\"Valid XHTML 1.1 Transitional\" " "height=\"31\" width=\"88\" /></a>\n", stdout); else fputs("<a href=\"http://validator.w3.org/check?uri=referer\">" "<img src=\"http://www.w3.org/Icons/valid-html401-blue\" " "alt=\"Valid HTML 4.01 Transitional\" " "height=\"31\" width=\"88\"></a>\n", stdout); if (needs_para) fputs("</p>", stdout); } } int main(int argc, char **argv) { program_name = argv[0]; static char stderr_buf[BUFSIZ]; setbuf(stderr, stderr_buf); int c; static const struct option long_options[] = { { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 }, { "version", no_argument, 0 /* nullptr */, 'v' }, { 0 /* nullptr */, 0, 0, 0 } }; while ((c = getopt_long(argc, argv, ":a:bCdD:eF:g:Ghi:I:j:k:l" "no:prs:S:vVx:y", long_options, 0 /* nullptr */)) != EOF) switch (c) { case 'a': /* text antialiasing bits - handled by pre-html */ break; case 'b': // set background color to white try { default_background = new color; } catch (const std::bad_alloc &exc) { fatal("cannot allocate storage for color object"); } default_background->set_gray(color::MAX_COLOR_VAL); break; case 'C': // Don't write CreationDate HTML comments. do_write_date_comment = false; break; case 'd': /* handled by pre-html */ break; case 'D': /* handled by pre-html */ break; case 'e': /* handled by pre-html */ break; case 'F': font::command_line_font_dir(optarg); break; case 'g': /* graphic antialiasing bits - handled by pre-html */ break; case 'G': // Don't write Creator HTML comments. do_write_creator_comment = false; break; case 'h': /* do not use the Hn headings of html, but manufacture our own */ manufacture_headings = true; break; case 'i': /* handled by pre-html */ break; case 'I': /* handled by pre-html */ break; case 'j': multiple_files = true; job_name = optarg; break; case 'k': if (strcasecmp(optarg, "ascii") == 0) charset_encoding = CHARSET_ASCII; else if (strcasecmp(optarg, "mixed") == 0) charset_encoding = CHARSET_MIXED; else if ((strcasecmp(optarg, "utf8") == 0) || (strcasecmp(optarg, "utf-8") == 0)) charset_encoding = CHARSET_UTF8; else { warning("unsupported character encoding '%1'; assuming UTF-8", optarg); charset_encoding = CHARSET_UTF8; } break; case 'l': auto_links = false; break; case 'n': simple_anchors = true; break; case 'o': /* handled by pre-html */ break; case 'p': /* handled by pre-html */ break; case 'r': auto_rule = false; break; case 's': base_point_size = atoi(optarg); break; case 'S': split_level = atoi(optarg) + 1; break; case 'v': printf("GNU post-grohtml (groff) version %s\n", Version_string); exit(EXIT_SUCCESS); break; case 'V': valid_flag = true; break; case 'x': if (strcmp(optarg, "x") == 0) { dialect = xhtml; simple_anchors = true; } else if (strcmp(optarg, "4") == 0) dialect = html4; else warning("unsupported HTML dialect: '%1'", optarg); break; case 'y': groff_sig = true; break; case CHAR_MAX + 1: // --help usage(stdout); exit(EXIT_SUCCESS); break; case '?': if (optopt != 0) error("unrecognized command-line option '%1'", char(optopt)); else error("unrecognized command-line option '%1'", argv[(optind - 1)]); usage(stderr); exit(2); break; case ':': error("command-line option '%1' requires an argument", char(optopt)); usage(stderr); exit(2); break; default: assert(0 == "unhandled getopt_long return value"); } if (optind >= argc) { interpret_troff_output_file("-"); } else { for (int i = optind; i < argc; i++) interpret_troff_output_file(argv[i]); } exit(EXIT_SUCCESS); } static void usage(FILE *stream) { assert(stream != 0 /* nullptr */); if (0 /* nullptr */ == stream) stream = stderr; fprintf(stream, "usage: %s [-bCGhlnrVy] [-F font-directory] [-j output-stem]" " [-k encoding] [-s base-type-size] [-S heading-level]" " [-x html-dialect] [file ...]\n" "usage: %s {-v | --version}\n" "usage: %s --help\n", program_name, program_name, program_name); if (stdout == stream) fputs("\n" "Translate the output of troff(1) into (X)HTML. See the grohtml(1)\n" "manual page.\n", stream); } // Local Variables: // fill-column: 72 // mode: C++ // End: // vim: set cindent noexpandtab shiftwidth=2 textwidth=72: