// -*- C++ -*- /* Copyright (C) 1999 Free Software Foundation, Inc. * * Gaius Mulley (gaius@glam.ac.uk) wrote grohtml * but it owes a huge amount of ideas and raw code from * James Clark (jjc@jclark.com) grops/ps.cc. */ /* This file is part of groff. groff is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, 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 groff; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "driver.h" #include "stringclass.h" #include "cset.h" #include "html.h" #include "html_chars.h" #include #ifdef HAVE_UNISTD_H #include #endif extern "C" { // SunOS 4.1.3 fails to declare this in stdlib.h char *mktemp(char *); } #include #include #ifndef _POSIX_VERSION #ifdef HAVE_LIMITS_H #include #endif /* HAVE_LIMITS_H */ #ifdef HAVE_DIRENT_H #include #else /* not HAVE_DIRENT_H */ #ifdef HAVE_SYS_DIR_H #include #endif /* HAVE_SYS_DIR_H */ #endif /* not HAVE_DIRENT_H */ #ifndef NAME_MAX #ifdef MAXNAMLEN #define NAME_MAX MAXNAMLEN #else /* !MAXNAMLEN */ #ifdef MAXNAMELEN #define NAME_MAX MAXNAMELEN #else /* !MAXNAMELEN */ #define NAME_MAX 14 #endif /* !MAXNAMELEN */ #endif /* !MAXNAMLEN */ #endif /* !NAME_MAX */ #endif /* not _POSIX_VERSION */ #include "nonposix.h" #include "ordered_list.h" #if !defined(TRUE) # define TRUE (1==1) #endif #if !defined(FALSE) # define FALSE (1==0) #endif #define MAX_TEMP_NAME 1024 #define MAX_STRING_LENGTH 4096 #define MAX_CHAR_SIZE 50 // maximum length of character name #define Y_FUDGE_MARGIN +0.83 #define A4_PAGE_LENGTH (11.6944-Y_FUDGE_MARGIN) #define DEFAULT_IMAGE_RES 80 #define IMAGE_BOARDER_PIXELS 10 #define MAX_WORDS_PER_LINE 1000 // only used for table indentation #define GAP_SPACES 3 // how many spaces needed to guess a gap? #define GAP_WIDTH_ONE_LINE 2 // 1/GAP_WIDTH_ONE_LINE inches required for one line table #define CENTER_TOLERANCE 2 // how many pixels off center will we think a line or region is centered #define MIN_COLUMN 7 // minimum column size pixels for multiple lines #define MIN_COLUMN_FOR_TWO_LINES 20 // minimum column size pixels for a 2 line table #define MIN_TEXT_PERCENT 5 // try and round to this percentage value for used columns #define PERCENT_THRESHOLD 20 // don't bother trying to increase and width greater than this /* * Only uncomment one of the following to determine default image type. */ #define IMAGE_DEFAULT_PNG /* #define IMAGE_DEFAULT_GIF */ #if defined(IMAGE_DEFAULT_GIF) static enum { gif, png } image_type = gif; static char *image_device = "gif"; #elif defined(IMAGE_DEFAULT_PNG) static enum { gif, png } image_type = png; static char *image_device = "png256"; #else # error "you must define either IMAGE_DEFAULT_GIF or IMAGE_DEFAULT_PNG" #endif static int debug_on = FALSE; static int guess_on = TRUE; static int margin_on = FALSE; static int auto_on = TRUE; static int table_on = TRUE; static int image_res = DEFAULT_IMAGE_RES; static int debug_table_on = FALSE; static int table_image_on = TRUE; // default is to create images for tbl static int linewidth = -1; #define DEFAULT_LINEWIDTH 40 /* in ems/1000 */ #define MAX_LINE_LENGTH 72 #define FILL_MAX 1000 void stop () {} /* * start with a few favorites */ 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_subsection - returns TRUE if a1..a2 is within b1..b2 */ static int is_subsection (int a1, int a2, int b1, int b2) { // easier to see whether this is not the case return( !((a1 < b1) || (a1 > b2) || (a2 < b1) || (a2 > b2)) ); } /* * is_intersection - returns TRUE if range a1..a2 intersects with b1..b2 */ static int is_intersection (int a1, int a2, int b1, int b2) { // again easier to prove NOT outside limits return( ! ((a1 > b2) || (a2 < b1)) ); } /* * is_digit - returns TRUE if character, ch, is a digit. */ static int is_digit (char ch) { return( (ch >= '0') && (ch <= '9') ); } /* * more_than_line_break - returns TRUE should v1 and v2 differ by more than * a simple line break. */ static int more_than_line_break (int v1, int v2, int size) { return( abs(v1-v2)>size ); } /* * the class and methods for styles */ struct style { font *f; int point_size; int font_no; int height; int slant; style (); style (font *, int, int, int, int); int operator == (const style &) const; int operator != (const style &) const; }; style::style() : f(0) { } style::style(font *p, int sz, int h, int sl, int no) : f(p), point_size(sz), font_no(no), height(h), slant(sl) { } int style::operator==(const style &s) const { return (f == s.f && point_size == s.point_size && height == s.height && slant == s.slant); } int style::operator!=(const style &s) const { return !(*this == s); } /* * the class and methods for retaining ascii text */ struct char_block { enum { SIZE = 256 }; char buffer[SIZE]; int used; char_block *next; char_block(); }; char_block::char_block() : used(0), next(0) { } class char_buffer { public: char_buffer(); ~char_buffer(); char *add_string(char *, unsigned int); private: char_block *head; char_block *tail; }; char_buffer::char_buffer() : head(0), tail(0) { } char_buffer::~char_buffer() { while (head != 0) { char_block *temp = head; head = head->next; delete temp; } } char *char_buffer::add_string (char *s, unsigned int length) { int i=0; unsigned int old_used; if (tail == 0) { tail = new char_block; head = tail; } else { if (tail->used + length+1 > char_block::SIZE) { tail->next = new char_block; tail = tail->next; } } // at this point we have a tail which is ready for the string. if (tail->used + length+1 > char_block::SIZE) { fatal("need to increase char_block::SIZE"); } old_used = tail->used; do { tail->buffer[tail->used] = s[i]; tail->used++; i++; length--; } while (length>0); // add terminating nul character tail->buffer[tail->used] = '\0'; tail->used++; // and return start of new string return( &tail->buffer[old_used] ); } /* * the classes and methods for maintaining pages and text positions and graphic regions */ class text_glob { public: int is_less (text_glob *a, text_glob *b); text_glob (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal, int is_command, int is_html); text_glob (void); ~text_glob (void); style text_style; char *text_string; unsigned int text_length; int minv, maxv, minh, maxh; int is_raw_command; // should the text be sent directly to the device? int is_html_command; // is the raw command definitely for the html device ie not an eqn? }; text_glob::text_glob (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal, int is_command, int is_html) : text_style(*s), text_string(string), text_length(length), minv(min_vertical), maxv(max_vertical), minh(min_horizontal), maxh(max_horizontal), is_raw_command(is_command), is_html_command(is_html) { } text_glob::text_glob () : text_string(0), text_length(0), minv(-1), maxv(-1), minh(-1), maxh(-1), is_raw_command(FALSE), is_html_command(FALSE) { } text_glob::~text_glob () { } int text_glob::is_less (text_glob *a, text_glob *b) { if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) { return( a->minh < b->minh ); } else { return( a->maxv < b->maxv ); } } struct xycoord { int x; int y; }; class graphic_glob { public: int is_less (graphic_glob *a, graphic_glob *b); graphic_glob (int troff_code); graphic_glob (void); ~graphic_glob (void); int minv, maxv, minh, maxh; int xc, yc; int nopoints; // number of points allocated in array below struct xycoord *point; int size; int fill; int code; }; graphic_glob::graphic_glob () : minv(-1), maxv(-1), minh(-1), maxh(-1), nopoints(0), point(0), size(0), code(0) { } graphic_glob::~graphic_glob () { if (point != 0) { free(point); } } graphic_glob::graphic_glob (int troff_code) : minv(-1), maxv(-1), minh(-1), maxh(-1), nopoints(0), point(0), size(0), code(troff_code) { } int graphic_glob::is_less (graphic_glob *a, graphic_glob *b) { return( (a->minv < b->minv) || ((a->minv == b->minv) && (a->minh < b->minh)) ); } class region_glob { public: region_glob (void); ~region_glob (void); int is_less (region_glob *a, region_glob *b); int minv, maxv, minh, maxh; }; int region_glob::is_less (region_glob *a, region_glob *b) { return( (a->minv < b->minv) || ((a->minv == b->minv) && (a->minh < b->minh)) ); } region_glob::region_glob (void) : minv(-1), maxv(-1), minh(-1), maxh(-1) { } region_glob::~region_glob (void) { } class page { public: page (void); void add (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal); void add_html_command (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal); void add_special_char (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal); void add_line (int code, int x1, int y1, int x2, int y2, int size, int fill); void add_arc (int code, int xc, int yc, int *p, double *c, int size, int fill); void add_polygon (int code, int np, int *p, int oh, int ov, int size, int fill); void add_spline (int code, int xc, int yc, int np, int *p, int size, int fill); void calculate_region (void); int is_in_region (graphic_glob *g); int can_grow_region (graphic_glob *g); void make_new_region (graphic_glob *g); int has_line (region_glob *r); int has_word (region_glob *r); int no_raw_commands (int minv, int maxv); // and the data ordered_list regions; // squares of bitmapped pics,eqn,tbl's ordered_list words; // position of words on page ordered_list lines; // position of lines on page char_buffer buffer; // all characters for this page int is_in_graphic; // should graphics and words go below or above ordered_list region_words; // temporary accumulation of words in a region ordered_list region_lines; // (as above) and used so that we can determine // the regions vertical limits }; page::page() : is_in_graphic(FALSE) { } void page::add (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { if (length > 0) { text_glob *g=new text_glob(s, buffer.add_string(string, length), length, min_vertical, min_horizontal, max_vertical, max_horizontal, FALSE, FALSE); if (is_in_graphic) { region_words.add(g); } else { words.add(g); } } } /* * add_html_command - it only makes sense to add html commands when we are not inside * a graphical entity. */ void page::add_html_command (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { if ((length > 0) && (! is_in_graphic)) { text_glob *g=new text_glob(s, buffer.add_string(string, length), length, min_vertical, min_horizontal, max_vertical, max_horizontal, TRUE, TRUE); words.add(g); } } /* * add_special_char - it only makes sense to add special characters when we are inside * a graphical entity. */ void page::add_special_char (style *s, char *string, unsigned int length, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal) { if ((length > 0) && (is_in_graphic)) { text_glob *g=new text_glob(s, buffer.add_string(string, length), length, min_vertical, min_horizontal, max_vertical, max_horizontal, TRUE, FALSE); region_words.add(g); } } void page::add_line (int code, int x1, int y1, int x2, int y2, int size, int fill) { graphic_glob *g = new graphic_glob(code); g->minh = min(x1, x2); g->maxh = max(x1, x2); g->minv = min(y1, y2); g->maxv = max(y1, y2); g->point = (struct xycoord *)malloc(sizeof(xycoord)*2); g->nopoints = 2; g->point[0].x = x1 ; g->point[0].y = y1 ; g->point[1].x = x2 ; g->point[1].y = y2 ; g->xc = 0; g->yc = 0; g->size = size; g->fill = fill; if (is_in_graphic) { region_lines.add(g); } else { lines.add(g); } } /* * assign_min_max_for_arc - works out the smallest box that will encompass an * arc defined by: origin: g->xc, g->xc * and vector (p[0], p[1]) and (p[2], p[3]) */ void assign_min_max_for_arc (graphic_glob *g, int *p, double *c) { int radius = (int) sqrt(c[0]*c[0]+c[1]*c[1]); int xv1 = p[0]; int yv1 = p[1]; int xv2 = p[2]; int yv2 = p[3]; int x1 = g->xc+xv1; int y1 = g->yc+yv1; int x2 = g->xc+xv1+xv2; int y2 = g->yc+yv1+yv2; // firstly lets use the 'circle' limitation g->minh = x1-radius; g->maxh = x1+radius; g->minv = y1-radius; g->maxv = y1+radius; // incidentally I'm sure there is a better way to do this, but I don't know it // please can someone let me know or "improve" this function // now see which min/max can be reduced and increased for the limits of the arc // // // Q2 | Q1 // -----+----- // Q3 | Q4 // if ((xv1>=0) && (yv1>=0)) { // first vector in Q3 if ((xv2>=0) && (yv2>=0)) { // second in Q1 g->maxh = x2; g->minv = y1; } else if ((xv2<0) && (yv2>=0)) { // second in Q2 g->maxh = x2; g->minv = y1; } else if ((xv2>=0) && (yv2<0)) { // second in Q4 g->minv = min(y1, y2); } else if ((xv2<0) && (yv2<0)) { // second in Q3 if (x1>=x2) { g->minh = x2; g->maxh = x1; g->minv = min(y1, y2); g->maxv = max(y1, y2); } else { // xv2, yv2 could all be zero? } } } else if ((xv1>=0) && (yv1<0)) { // first vector in Q2 if ((xv2>=0) && (yv2>=0)) { // second in Q1 g->maxh = max(x1, x2); g->minh = min(x1, x2); g->minv = y1; } else if ((xv2<0) && (yv2>=0)) { // second in Q2 if (x1maxh = x2; g->minh = x1; g->minv = min(y1, y2); g->maxv = max(y1, y2); } else { // otherwise almost full circle anyway } } else if ((xv2>=0) && (yv2<0)) { // second in Q4 g->minv = y2; g->minh = x1; } else if ((xv2<0) && (yv2<0)) { // second in Q3 g->minh = min(x1, x2); } } else if ((xv1<0) && (yv1<0)) { // first vector in Q1 if ((xv2>=0) && (yv2>=0)) { // second in Q1 if (x1minh = x1; g->maxh = x2; g->minv = min(y1, y2); g->maxv = max(y1, y2); } else { // nearly full circle } } else if ((xv2<0) && (yv2>=0)) { // second in Q2 g->maxv = max(y1, y2); } else if ((xv2>=0) && (yv2<0)) { // second in Q4 g->minv = min(y1, y2); g->maxv = max(y1, y2); g->minh = min(x1, x2); } else if ((xv2<0) && (yv2<0)) { // second in Q3 g->minh = x2; g->maxv = y1; } } else if ((xv1<0) && (yv1>=0)) { // first vector in Q4 if ((xv2>=0) && (yv2>=0)) { // second in Q1 g->maxh = max(x1, x2); } else if ((xv2<0) && (yv2>=0)) { // second in Q2 g->maxv = max(y1, y2); g->maxh = max(x1, x2); } else if ((xv2>=0) && (yv2<0)) { // second in Q4 if (x1>=x2) { g->minv = min(y1, y2); g->maxv = max(y1, y2); g->minh = min(x1, x2); g->maxh = max(x2, x2); } else { // nearly full circle } } else if ((xv2<0) && (yv2<0)) { // second in Q3 g->maxv = max(y1, y2); g->minh = min(x1, x2); g->maxh = max(x1, x2); } } // this should *never* happen but if it does it means a case above is wrong.. // this code is only present for safety sake if (g->maxh < g->minh) { if (debug_on) { fprintf(stderr, "assert failed minh > maxh\n"); fflush(stderr); // stop(); } g->maxh = g->minh; } if (g->maxv < g->minv) { if (debug_on) { fprintf(stderr, "assert failed minv > maxv\n"); fflush(stderr); // stop(); } g->maxv = g->minv; } } void page::add_arc (int code, int xc, int yc, int *p, double *c, int size, int fill) { graphic_glob *g = new graphic_glob(code); g->point = (struct xycoord *)malloc(sizeof(xycoord)*2); g->nopoints = 2; g->point[0].x = p[0] ; g->point[0].y = p[1] ; g->point[1].x = p[2] ; g->point[1].y = p[3] ; g->xc = xc; g->yc = yc; g->size = size; g->fill = fill; assign_min_max_for_arc(g, p, c); if (is_in_graphic) { region_lines.add(g); } else { lines.add(g); } } void page::add_polygon (int code, int np, int *p, int oh, int ov, int size, int fill) { graphic_glob *g = new graphic_glob(code); int j = 0; int i; g->point = (struct xycoord *)malloc(sizeof(xycoord)*np/2); g->nopoints = np/2; for (i=0; inopoints; i++) { g->point[i].x = p[j]; j++; g->point[i].y = p[j]; j++; } // now calculate min/max g->minh = g->point[0].x; g->minv = g->point[0].y; g->maxh = g->point[0].x; g->maxv = g->point[0].y; for (i=1; inopoints; i++) { g->minh = min(g->minh, g->point[i].x); g->minv = min(g->minv, g->point[i].y); g->maxh = max(g->maxh, g->point[i].x); g->maxv = max(g->maxv, g->point[i].y); } g->size = size; g->xc = oh; g->yc = ov; g->fill = fill; if (is_in_graphic) { region_lines.add(g); } else { lines.add(g); } } void page::add_spline (int code, int xc, int yc, int np, int *p, int size, int fill) { graphic_glob *g = new graphic_glob(code); int j = 0; int i; g->point = (struct xycoord *)malloc(sizeof(xycoord)*np/2); g->nopoints = np/2; for (i=0; inopoints; i++) { g->point[i].x = p[j]; j++; g->point[i].y = p[j]; j++; } // now calculate min/max g->minh = min(g->point[0].x, g->point[0].x/2); g->minv = min(g->point[0].y, g->point[0].y/2); g->maxh = max(g->point[0].x, g->point[0].x/2); g->maxv = max(g->point[0].y, g->point[0].y/2); /* tnum/tden should be between 0 and 1; the closer it is to 1 the tighter the curve will be to the guiding lines; 2/3 is the standard value */ const int tnum = 2; const int tden = 3; for (i=1; inopoints-1; i++) { g->minh = min(g->minh, g->point[i].x*tnum/(2*tden)); g->minv = min(g->minv, g->point[i].y*tnum/(2*tden)); g->maxh = max(g->maxh, g->point[i].x*tnum/(2*tden)); g->maxv = max(g->maxv, g->point[i].y*tnum/(2*tden)); g->minh = min(g->minh, g->point[i].x/2+(g->point[i+1].x*(tden-tden))/(2*tden)); g->minv = min(g->minv, g->point[i].y/2+(g->point[i+1].y*(tden-tden))/(2*tden)); g->maxh = max(g->maxh, g->point[i].x/2+(g->point[i+1].x*(tden-tden))/(2*tden)); g->maxv = max(g->maxv, g->point[i].y/2+(g->point[i+1].y*(tden-tden))/(2*tden)); g->minh = min(g->minh, (g->point[i].x-g->point[i].x/2) + g->point[i+1].x/2); g->minv = min(g->minv, (g->point[i].y-g->point[i].y/2) + g->point[i+1].y/2); g->maxh = max(g->maxh, (g->point[i].x-g->point[i].x/2) + g->point[i+1].x/2); g->maxv = max(g->maxv, (g->point[i].y-g->point[i].y/2) + g->point[i+1].y/2); } i = g->nopoints-1; g->minh = min(g->minh, (g->point[i].x-g->point[i].x/2)) + xc; g->minv = min(g->minv, (g->point[i].y-g->point[i].y/2)) + yc; g->maxh = max(g->maxh, (g->point[i].x-g->point[i].x/2)) + xc; g->maxv = max(g->maxv, (g->point[i].y-g->point[i].y/2)) + yc; g->size = size; g->xc = xc; g->yc = yc; g->fill = fill; if (is_in_graphic) { region_lines.add(g); } else { lines.add(g); } } class html_font : public font { html_font(const char *); public: int encoding_index; char *encoding; char *reencoded_name; ~html_font(); static html_font *load_html_font(const char *); }; html_font *html_font::load_html_font(const char *s) { html_font *f = new html_font(s); if (!f->load()) { delete f; return 0; } 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; char text[MAX_STRING_LENGTH]; }; title_desc::title_desc () : has_been_written(FALSE), has_been_found(FALSE) { } title_desc::~title_desc () { } class header_desc { public: header_desc (); ~header_desc (); int no_of_headings; // how many headings have we found? char_buffer headings; // all the headings used in the document ordered_list headers; int header_level; // current header level int written_header; // have we written the header yet? char header_buffer[MAX_STRING_LENGTH]; // current header text void write_headings (FILE *f); }; header_desc::header_desc () : no_of_headings(0), header_level(2), written_header(0) { } header_desc::~header_desc () { } /* * paragraph_type - alignment for a new paragraph */ typedef enum { left_alignment, center_alignment } paragraph_type; /* * text_defn - defines the limit of text, initially these are stored in the * column array as words. Later we examine the white space between * the words in successive lines to find out whether we can detect * distinct columns. The columns are generated via html tables. */ struct text_defn { int left; // the start of a word or text int right; // the end of the text and beginning of white space int is_used; // will this this column be used for words or space int right_hits; // count of the number of words touching right position int percent; // what percentage width should we use for this cell? }; /* * introduce a paragraph class so that we can nest paragraphs * from plain html text and html tables. */ class html_paragraph { public: html_paragraph (int in, int need, paragraph_type type, html_paragraph *prev); ~html_paragraph (); int in_paragraph; int need_paragraph; paragraph_type para_type; html_paragraph *previous; }; /* * html_paragraph - constructor, fill in the public fields. */ html_paragraph::html_paragraph (int in, int need, paragraph_type type, html_paragraph *prev) : in_paragraph(in), need_paragraph(need), para_type(type), previous(prev) { } /* * html_paragraph - deconstructor */ html_paragraph::~html_paragraph () { } /* * note that html_tables are currently only used to provide a better * indentation mechanism for html text (in particular it allows grohtml * to render .IP and .2C together with autoformatting). */ class html_table { public: html_table (); ~html_table (); int no_of_columns; // how many columns are we using? struct text_defn *columns; // left and right margins for each column int vertical_limit; // the limit of the table int wrap_margin; // is the current rightmost margin able to wrap words? }; html_table::html_table () : no_of_columns(0), columns(0), vertical_limit(0), wrap_margin(0) { } html_table::~html_table () { } class html_printer : public printer { FILE *tempfp; simple_output html; simple_output troff; int res; int postscript_res; int space_char_index; int no_of_printed_pages; int paper_length; enum { SBUF_SIZE = 8192 }; char sbuf[SBUF_SIZE]; int sbuf_len; int sbuf_start_hpos; int sbuf_vpos; int sbuf_end_hpos; int sbuf_kern; style sbuf_style; int sbuf_dmark_hpos; style output_style; int output_hpos; int output_vpos; int output_draw_point_size; int line_thickness; int output_line_thickness; int fill; unsigned char output_space_code; string defs; char *inside_font_style; int page_number; title_desc title; header_desc header; int header_indent; page *page_contents; html_table indentation; int left_margin_indent; int right_margin_indent; int need_one_newline; int issued_newline; html_paragraph *current_paragraph; char image_name[MAX_STRING_LENGTH]; int image_number; int graphic_level; int supress_sub_sup; int start_region_vpos; int start_region_hpos; int end_region_vpos; int end_region_hpos; int cutoff_heading; struct graphic_glob *start_graphic; struct text_glob *start_text; void flush_sbuf (); void set_style (const style &); void set_space_code (unsigned char c); void do_exec (char *, const environment *); void do_import (char *, const environment *); void do_def (char *, const environment *); void do_mdef (char *, const environment *); void do_file (char *, const environment *); void set_line_thickness (const environment *); void change_font (text_glob *g, int is_to_html); void terminate_current_font (void); void flush_font (void); void flush_page (void); void add_char_to_sbuf (unsigned char code); void add_to_sbuf (char code, const char *name); void display_word (text_glob *g, int is_to_html); void html_display_word (text_glob *g); void troff_display_word (text_glob *g); void display_line (graphic_glob *g, int is_to_html); void display_fill (graphic_glob *g); void calculate_margin (void); void traverse_page_regions (void); void dump_page (void); int is_within_region (graphic_glob *g); int is_within_region (text_glob *t); int is_less (graphic_glob *g, text_glob *t); void display_globs (int is_to_html); void move_horizontal (text_glob *g, int left_margin); void move_vertical (text_glob *g, paragraph_type p); void write_html_font_face (const char *fontname, const char *left, const char *right); void write_html_font_type (const char *fontname, const char *left, const char *right); void html_change_font (text_glob *g, const char *fontname, int size); char *html_position_text (text_glob *g, int left_margin, int right_margin); int html_position_region (void); void troff_change_font (const char *fontname, int size, int font_no); void troff_position_text (text_glob *g); int pretend_is_on_same_line (text_glob *g, int left_margin, int right_margin); int is_on_same_line (text_glob *g, int vpos); int looks_like_subscript (text_glob *g); int looks_like_superscript (text_glob *g); int looks_like_smaller_font (text_glob *g); int looks_like_larger_font (text_glob *g); void begin_paragraph (paragraph_type p); void begin_paragraph_no_height (paragraph_type p); void force_begin_paragraph (void); void end_paragraph (void); void save_paragraph (void); void restore_paragraph (void); void html_newline (void); void convert_to_image (char *troff_src, char *image_name); void write_title (int in_head); void find_title (void); int is_bold (text_glob *g); void write_header (text_glob *g); void determine_header_level (void); void build_header (text_glob *g); void make_html_indent (int indent); int is_whole_line_bold (text_glob *g); int is_a_header (text_glob *g); int processed_header (text_glob *g); void make_new_image_name (void); void calculate_region_margins (region_glob *r); void remove_redundant_regions (void); void remove_duplicate_regions (void); void move_region_to_page (void); void calculate_region_range (graphic_glob *r); void flush_graphic (void); void write_string (graphic_glob *g, int is_to_html); void prologue (void); int gs_x (int x); int gs_y (int y); void display_regions (void); int check_able_to_use_table (text_glob *g); int using_table_for_indent (void); int collect_columns (struct text_defn *next_words, struct text_defn *next_cols, struct text_defn *last_words, struct text_defn *last_cols, int max_words); void include_into_list (struct text_defn *line, struct text_defn *item); int is_in_column (struct text_defn *line, struct text_defn *item, int max_words); int is_column_match (struct text_defn *match, struct text_defn *line1, struct text_defn *line2, int max_words); int count_columns (struct text_defn *line); void rewind_text_to (text_glob *g); int found_use_for_table (text_glob *start); void column_display_word (int cell, int vert, int left, int right, int next); void start_table (void); void end_table (void); void foreach_column_include_text (text_glob *start); void define_cell (int i); int column_calculate_left_margin (int left, int right); int column_calculate_right_margin (int left, int right); void display_columns (const char *word, const char *name, text_defn *line); void calculate_right (struct text_defn *line, int max_words); void determine_right_most_column (struct text_defn *line, int max_words); int remove_white_using_words (struct text_defn *next_guess, struct text_defn *last_guess, struct text_defn *next_line); void copy_line (struct text_defn *dest, struct text_defn *src); void combine_line (struct text_defn *dest, struct text_defn *src); int conflict_with_words (struct text_defn *column_guess, struct text_defn *words); void remove_entry_in_line (struct text_defn *line, int j); void remove_redundant_columns (struct text_defn *line); void add_column_gaps (struct text_defn *line); int continue_searching_column (text_defn *next_col, text_defn *last_col, text_defn *all_words); void add_right_full_width (struct text_defn *line, int mingap); int is_continueous_column (text_defn *last_col, text_defn *next_line); int is_exact_left (text_defn *last_col, text_defn *next_line); int find_column_index_in_line (text_glob *t, text_defn *line); void emit_space (text_glob *g, int force_space); int is_in_middle (int left, int right); int check_able_to_use_center (text_glob *g); void write_centered_line (text_glob *g); int single_centered_line (text_defn *first, text_defn *second, text_glob *g); int determine_row_limit (text_glob *start, int v); void assign_used_columns (text_glob *start); int find_column_index (text_glob *t); int large_enough_gap (text_defn *last_col); int is_worth_column (int left, int right); int is_subset_of_columns (text_defn *a, text_defn *b); void count_hits (text_defn *col, int no_of_columns, int limit); void count_right_hits (text_defn *col, int no_of_columns); int calculate_min_gap (text_glob *g); int right_indentation (struct text_defn *last_guess); void calculate_percentage_width (text_glob *start); int able_to_steal_width (void); int need_to_steal_width (void); int can_distribute_fairly (void); void utilize_round_off (void); int will_wrap_text (int i, text_glob *start); int next_line_on_left_column (int i, text_glob *start); void remove_table_column (int i); void remove_unnecessary_unused (text_glob *start); int is_small_table (int lines, struct text_defn *last_guess, struct text_defn *words_1, struct text_defn *cols_1, struct text_defn *words_2, struct text_defn *cols_2, int *limit, int *limit_1); int is_column_subset (struct text_defn *cols_1, struct text_defn *cols_2); int is_appropriate_to_start_table (struct text_defn *cols_1, struct text_defn *cols_2, struct text_defn *last_guess); int is_a_full_width_column (void); int right_most_column (struct text_defn *col); int large_enough_gap_for_two (struct text_defn *col); void remove_zero_percentage_column (void); void translate_to_html (text_glob *g); int html_knows_about (char *troff); void determine_diacritical_mark (const char *name, const environment *env); int sbuf_continuation (unsigned char code, const char *name, const environment *env, int w); char *remove_last_char_from_sbuf (); const char *check_diacritical_combination (unsigned char code, const char *name); int seen_backwards_escape (char *s, int l); int should_defer_table (int lines, struct text_glob *start, struct text_defn *cols_1); int is_new_exact_right (struct text_defn *last_guess, struct text_defn *last_cols, struct text_defn *next_cols); void issue_left_paragraph (void); void adjust_margin_percentages (void); int total_percentages (void); int get_left (void); void can_loose_column (text_glob *start, struct text_defn *last_guess, int limit); int check_lack_of_hits (struct text_defn *next_guess, struct text_defn *last_guess, text_glob *start, int limit); int is_in_table (void); // ADD HERE public: html_printer(); ~html_printer(); void set_char(int i, font *f, const environment *env, int w, const char *name); void draw(int code, int *p, int np, const environment *env); void begin_page(int); void end_page(int); void special(char *arg, const environment *env); font *make_font(const char *); void end_of_line(); }; html_printer::html_printer() : html(0, MAX_LINE_LENGTH), troff(0, MAX_LINE_LENGTH), no_of_printed_pages(0), sbuf_len(0), sbuf_dmark_hpos(-1), output_hpos(-1), output_vpos(-1), line_thickness(-1), fill(FILL_MAX + 1), inside_font_style(0), page_number(0), header_indent(-1), left_margin_indent(0), right_margin_indent(0), need_one_newline(0), issued_newline(0), image_number(0), graphic_level(0), supress_sub_sup(TRUE), start_region_vpos(0), start_region_hpos(0), end_region_vpos(0), end_region_hpos(0), cutoff_heading(100) { tempfp = xtmpfile(); html.set_file(tempfp); if (linewidth < 0) linewidth = DEFAULT_LINEWIDTH; if (font::hor != 1) fatal("horizontal resolution must be 1"); if (font::vert != 1) fatal("vertical resolution must be 1"); #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_char_index = font::name_to_index("space"); paper_length = font::paperlength; if (paper_length == 0) paper_length = 11*font::res; page_contents = new page; postscript_res = 72000; current_paragraph = new html_paragraph(FALSE, FALSE, left_alignment, 0); } /* * add_char_to_sbuf - adds a single character to the sbuf. */ void html_printer::add_char_to_sbuf (unsigned char code) { if (sbuf_len < SBUF_SIZE) { sbuf[sbuf_len] = code; sbuf_len++; } else { fatal("need to increase SBUF_SIZE"); } } /* * add_to_sbuf - adds character code or name to the sbuf. * It escapes \ with \\ * We need to preserve the name of characters if they exist * because we may need to send this character to two different * devices: html and postscript. */ void html_printer::add_to_sbuf (char code, const char *name) { if (name == 0) { if (code == '\\') { add_char_to_sbuf('\\'); } add_char_to_sbuf(code); } else { int l=strlen(name); int i=0; add_char_to_sbuf('\\'); add_char_to_sbuf('('); while (ihpos) || (sbuf_dmark_hpos == env->hpos)) { name = check_diacritical_combination(code, name); add_to_sbuf(code, name); determine_diacritical_mark(name, env); sbuf_end_hpos += w + sbuf_kern; return( TRUE ); } else { if ((sbuf_len < SBUF_SIZE-1) && (env->hpos >= sbuf_end_hpos) && ((sbuf_kern == 0) || (sbuf_end_hpos - sbuf_kern != env->hpos))) { /* * lets see whether a space is needed or not */ int space_width = sbuf_style.f->get_space_width(sbuf_style.point_size); if (env->hpos-sbuf_end_hpos < space_width) { name = check_diacritical_combination(code, name); add_to_sbuf(code, name); determine_diacritical_mark(name, env); sbuf_end_hpos = env->hpos + w; return( TRUE ); } } else if ((sbuf_len > 0) && (sbuf_dmark_hpos)) { /* * check whether the diacritical mark is on the same character */ int space_width = sbuf_style.f->get_space_width(sbuf_style.point_size); if (abs(sbuf_dmark_hpos-env->hpos) < space_width) { name = check_diacritical_combination(code, name); add_to_sbuf(code, name); determine_diacritical_mark(name, env); sbuf_end_hpos = env->hpos + w; return( TRUE ); } } } return( FALSE ); } /* * seen_backwards_escape - returns TRUE if we can see a escape at position i..l in s */ int html_printer::seen_backwards_escape (char *s, int l) { /* * this is tricky so it is broken into components for clarity * (we let the compiler put in all back into a complex expression) */ if ((l>0) && (sbuf[l] == '(') && (sbuf[l-1] == '\\')) { /* * ok seen '\(' but we must now check for '\\(' */ if ((l>1) && (sbuf[l-2] == '\\')) { /* * escaped the escape */ return( FALSE ); } else { return( TRUE ); } } else { return( FALSE ); } } /* * reverse - return reversed string. */ char *reverse (char *s) { int i=0; int j=strlen(s)-1; char t; while (i0) { l--; if ((sbuf[l] == ')') && (l>0) && (sbuf[l-1] == '\\')) { /* * found terminating escape */ int i=0; l -= 2; while ((l>0) && (! seen_backwards_escape(sbuf, l))) { if (sbuf[l] == '\\') { if (sbuf[l-1] == '\\') { last[i] = sbuf[l]; i++; l--; } } else { last[i] = sbuf[l]; i++; } l--; } last[i] = (char)0; sbuf_len = l; if (seen_backwards_escape(sbuf, l)) { sbuf_len--; } return( reverse(last) ); } else { if ((sbuf[l] == '\\') && (l>0) && (sbuf[l-1] == '\\')) { l -= 2; sbuf_len = l; return( "\\" ); } else { sbuf_len--; last[0] = sbuf[sbuf_len]; last[1] = (char)0; return( last ); } } } else { return( NULL ); } } /* * check_diacriticial_combination - checks to see whether the character code * if combined with the previous diacriticial mark * forms a new character. */ const char *html_printer::check_diacritical_combination (unsigned char code, const char *name) { static char troff_char[2]; if ((name == 0) && (sbuf_dmark_hpos >= 0)) { // last character was a diacritical mark char *last = remove_last_char_from_sbuf(); int i=0; int j; while (diacritical_table[i].mark != NULL) { if (strcmp(diacritical_table[i].mark, last) == 0) { j=0; while ((diacritical_table[i].second_troff_char[j] != (char)0) && (diacritical_table[i].second_troff_char[j] != code)) { j++; } if (diacritical_table[i].second_troff_char[j] == code) { troff_char[0] = diacritical_table[i].translation; troff_char[1] = code; troff_char[2] = (char)0; return( troff_char ); } } i++; } add_to_sbuf(last[0], last); } return( name ); } /* * determine_diacritical_mark - if name is a diacriticial mark the record the position. * --fixme-- is there a better way of doing this * this must be done in troff somewhere. */ void html_printer::determine_diacritical_mark (const char *name, const environment *env) { if (name != 0) { int i=0; while (diacritical_table[i].mark != NULL) { if (strcmp(name, diacritical_table[i].mark) == 0) { sbuf_dmark_hpos = env->hpos; return; } i++; } } sbuf_dmark_hpos = -1; } /* * 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(int i, font *f, const environment *env, int w, const char *name) { unsigned char code = f->get_code(i); #if 0 if (code == ' ') { stop(); } #endif style sty(f, env->size, env->height, env->slant, env->fontno); if (sty.slant != 0) { if (sty.slant > 80 || sty.slant < -80) { error("silly slant `%1' degrees", sty.slant); sty.slant = 0; } } if ((name != 0) && (page_contents->is_in_graphic)) { flush_sbuf(); int r=font::res; // resolution of the device page_contents->add_special_char(&sty, (char *)name, strlen(name), env->vpos-sty.point_size*r/72, env->hpos, env->vpos , env->hpos+w); sbuf_end_hpos = env->hpos + w; sbuf_start_hpos = env->hpos; sbuf_vpos = env->vpos; sbuf_style = sty; sbuf_kern = 0; } else { if ((sbuf_len > 0) && (sbuf_len < SBUF_SIZE) && (sty == sbuf_style) && (sbuf_vpos == env->vpos) && (sbuf_continuation(code, name, env, w))) { return; } else { flush_sbuf(); sbuf_len = 0; add_to_sbuf(code, name); determine_diacritical_mark(name, env); sbuf_end_hpos = env->hpos + w; sbuf_start_hpos = env->hpos; sbuf_vpos = env->vpos; sbuf_style = sty; sbuf_kern = 0; } } } /* * file_name_max - return the maximum file-name length permitted * by the underlying filesystem. * * (Code shamelessly stolen from indxbib/dirnamemax.c.) */ static size_t file_name_max (const char *fname) { #ifdef _POSIX_VERSION return pathconf (fname, _PC_NAME_MAX); #else return NAME_MAX; #endif } /* * make_new_image_name - creates a new file name ready for a image file. */ void html_printer::make_new_image_name (void) { image_number++; if ((current_filename == 0) || (strcmp(current_filename, "") == 0) || (strcmp(current_filename, "-") == 0) || (strcspn(current_filename, DIR_SEPS) < strlen(current_filename))) { if (file_name_max(".") > 14) sprintf(image_name, "groff-html-%d-%ld", image_number, (long)getpid()); else // The "-gh" part might be truncated on MS-DOS, but there's enough // space for the PID and up to 99 image numbers. That's why "-gh" // comes last. sprintf(image_name, "%d-%ld-gh", image_number, (long)getpid()); } else if (file_name_max(".") > 14) { sprintf(image_name, "%s-%d-%ld", current_filename, image_number, (long)getpid()); } else { // see the commentary above sprintf(image_name, "%d-%ld-%s", image_number, (long)getpid(), current_filename); // Make sure image_name does not have a dot in its trunk, since // convert_to_image will append .gif or .png to it, and DOS doesn't // allow more than a single dot in a file name. int i = strlen(image_name); for ( ; i > 0; i--) { if (strchr(DIR_SEPS, image_name[i - 1])) break; if (image_name[i - 1] == '.') { image_name[i - 1] = '\0'; break; } } } } /* * 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(""); html.put_string(title.text); html.put_string("\n"); } else { title.has_been_written = TRUE; html.put_string("

"); html.put_string(title.text); html.put_string("

\n"); } } } /* * get_html_translation - given the position of the character and its name * return the device encoding for such character. */ char *get_html_translation (font *f, char *name) { int index; if ((f == 0) || (name == 0) || (strcmp(name, "") == 0)) { return( NULL ); } else { index = f->name_to_index(name); if (index == 0) { error("character `%s' not found", name); return( NULL ); } else { return( (char *)f->get_special_device_encoding(index) ); } } } /* * str_translate_to_html - converts a string, str, into html text. It places * the output input buffer, buf. It truncates string, str, if * there is not enough space in buf. * It looks up the html character encoding of single characters * if, and_single, is TRUE. Characters such as < > & etc. */ void str_translate_to_html (font *f, char *buf, int buflen, char *str, int len, int and_single) { int l; char *translation; int e; char escaped_char[MAX_STRING_LENGTH]; int i=0; int b=0; int t=0; #if 0 if (strcmp(str, "\\(\\\\-\\)") == 0) { stop(); } #endif while (str[i] != (char)0) { if ((str[i]=='\\') && (i+1 0) { translation = get_html_translation(f, escaped_char); if (translation) { l = strlen(translation); t = max(0, min(l, buflen-b)); strncpy(&buf[b], translation, t); b += t; } else { int index=f->name_to_index(escaped_char); if (index != 0) { buf[b] = f->get_code(index); b++; } } } } } else { if (and_single) { char name[2]; name[0] = str[i]; name[1] = (char)0; translation = get_html_translation(f, name); if (translation) { l = strlen(translation); t = max(0, min(l, buflen-b)); strncpy(&buf[b], translation, t); b += t; } else { if (bwords.is_empty()) { int end_title_hpos = 0; int start_title_vpos = 0; int found_title_start = FALSE; int height = 0; int start_region =-1; if (! page_contents->regions.is_empty()) { region_glob *r; page_contents->regions.start_from_head(); r = page_contents->regions.get_data(); if (r->minv > 0) { start_region = r->minv; } } page_contents->words.start_from_head(); do { t = page_contents->words.get_data(); removed_from_head = FALSE; if ((found_title_start) && (start_region != -1) && (t->maxv >= start_region)) { /* * we have just encountered the first graphic region so * we stop looking for a title. */ title.has_been_found = TRUE; return; } else if (t->is_raw_command) { // skip raw commands page_contents->words.move_right(); // move onto next word } else if ((!found_title_start) && (t->minh > left_margin_indent) && ((start_region == -1) || (t->maxv < start_region))) { start_title_vpos = t->minv; end_title_hpos = t->minh; str_translate_to_html(t->text_style.f, buf, MAX_STRING_LENGTH, t->text_string, t->text_length, TRUE); strcpy((char *)title.text, buf); height = t->text_style.point_size*r/72; found_title_start = TRUE; page_contents->words.sub_move_right(); removed_from_head = ((!page_contents->words.is_empty()) && (page_contents->words.is_equal_to_head())); } else if (found_title_start) { if ((t->minv == start_title_vpos) || ((!more_than_line_break(start_title_vpos, t->minv, (height*3)/2)) && (t->minh > left_margin_indent)) || (is_bold(t) && (t->minh > left_margin_indent))) { start_title_vpos = min(t->minv, start_title_vpos); end_title_hpos = max(t->maxh, end_title_hpos); strcat(title.text, " "); str_translate_to_html(t->text_style.f, buf, MAX_STRING_LENGTH, t->text_string, t->text_length, TRUE); strcat(title.text, buf); page_contents->words.sub_move_right(); removed_from_head = ((!page_contents->words.is_empty()) && (page_contents->words.is_equal_to_head())); } else { // end of title title.has_been_found = TRUE; return; } } else if (t->minh <= left_margin_indent) { // no margin exists return; } else { // move onto next word page_contents->words.move_right(); } } while ((! page_contents->words.is_equal_to_head()) || (removed_from_head)); } } } /* * html_newline - generates a newline
*/ void html_printer::html_newline (void) { int r = font::res; int height = output_style.point_size*r/72; if (current_paragraph->in_paragraph) { // safe to generate a pretty newline html.put_string("
\n"); } else { html.put_string("
"); } output_vpos += height; issued_newline = TRUE; } /* * issue_left_paragraph - emits a left paragraph together with appropriate * margin if header_indent is < left_margin_indent. */ void html_printer::issue_left_paragraph (void) { if ((header_indent < left_margin_indent) && (! using_table_for_indent())) { html.put_string("

"); } else { html.put_string("

"); } } /* * force_begin_paragraph - force the begin_paragraph to be emitted. */ void html_printer::force_begin_paragraph (void) { if (current_paragraph->in_paragraph && current_paragraph->need_paragraph) { switch (current_paragraph->para_type) { case left_alignment: issue_left_paragraph(); break; case center_alignment: html.put_string("

"); break; default: fatal("unknown paragraph alignment type"); } current_paragraph->need_paragraph = FALSE; } } /* * begin_paragraph - starts a new paragraph. It does nothing if a paragraph * has already been started. */ void html_printer::begin_paragraph (paragraph_type p) { if (! current_paragraph->in_paragraph) { int r = font::res; int height = output_style.point_size*r/72; if (output_vpos >=0) { // we leave it alone if it is set to the top of page output_vpos += height; } current_paragraph->need_paragraph = TRUE; // delay the

just in case we don't actually emit text current_paragraph->in_paragraph = TRUE; current_paragraph->para_type = p; issued_newline = TRUE; } } /* * begin_paragraph_no_height - starts a new paragraph. It does nothing if a paragraph * has already been started. Note it does not alter output_vpos. */ void html_printer::begin_paragraph_no_height (paragraph_type p) { if (! current_paragraph->in_paragraph) { current_paragraph->need_paragraph = TRUE; // delay the

just in case we don't actually emit text current_paragraph->in_paragraph = TRUE; current_paragraph->para_type = p; issued_newline = TRUE; } } /* * end_paragraph - end the current paragraph. It does nothing if a paragraph * has not been started. */ void html_printer::end_paragraph (void) { if (current_paragraph->in_paragraph) { // check whether we have generated any text inbetween the potential paragraph begin end if (! current_paragraph->need_paragraph) { int r = font::res; int height = output_style.point_size*r/72; output_vpos += height; terminate_current_font(); html.put_string("

\n"); } else { terminate_current_font(); } current_paragraph->para_type = left_alignment; current_paragraph->in_paragraph = FALSE; } } /* * save_paragraph - saves the current paragraph state and * creates new paragraph state. */ void html_printer::save_paragraph (void) { if (current_paragraph == 0) { fatal("current_paragraph is NULL"); } current_paragraph = new html_paragraph(current_paragraph->in_paragraph, current_paragraph->need_paragraph, current_paragraph->para_type, current_paragraph); terminate_current_font(); } /* * restore_paragraph - restores the previous paragraph state. */ void html_printer::restore_paragraph (void) { html_paragraph *old = current_paragraph; current_paragraph = current_paragraph->previous; free(old); } /* * calculate_margin - runs through the words and graphics globs * and finds the start of the left most margin. */ void html_printer::calculate_margin (void) { text_glob *w; graphic_glob *g; // remove margin right_margin_indent = 0; if (! page_contents->words.is_empty()) { // firstly check the words to determine the right margin page_contents->words.start_from_head(); do { w = page_contents->words.get_data(); if ((w->maxh >= 0) && (w->maxh > right_margin_indent)) { right_margin_indent = w->maxh; #if 0 if (right_margin_indent == 758) stop(); #endif } page_contents->words.move_right(); } while (! page_contents->words.is_equal_to_head()); /* * only examine graphics if no words present */ if (! page_contents->lines.is_empty()) { // now check for diagrams for right margin page_contents->lines.start_from_head(); do { g = page_contents->lines.get_data(); if ((g->maxh >= 0) && (g->maxh > right_margin_indent)) { right_margin_indent = g->maxh; #if 0 if (right_margin_indent == 950) stop(); #endif } page_contents->lines.move_right(); } while (! page_contents->lines.is_equal_to_head()); } /* * now we know the right margin lets do the same to find left margin */ if (header_indent == -1) { header_indent = right_margin_indent; } left_margin_indent = right_margin_indent; if (! page_contents->words.is_empty()) { do { w = page_contents->words.get_data(); if ((w->minh >= 0) && (w->minh < left_margin_indent)) { if (! is_a_header(w) && (! w->is_raw_command)) { left_margin_indent = w->minh; } } page_contents->words.move_right(); } while (! page_contents->words.is_equal_to_head()); } /* * only examine graphic for margins if text yields nothing */ if (! page_contents->lines.is_empty()) { // now check for diagrams page_contents->lines.start_from_head(); do { g = page_contents->lines.get_data(); if ((g->minh >= 0) && (g->minh < left_margin_indent)) { left_margin_indent = g->minh; } page_contents->lines.move_right(); } while (! page_contents->lines.is_equal_to_head()); } } } /* * calculate_region - runs through the graphics globs and text globs * and ensures that all graphic routines * are defined by the region lists. * This then allows us to easily * determine the range of vertical and * horizontal boundaries for pictures, * tbl's and eqn's. * */ void page::calculate_region (void) { graphic_glob *g; if (! lines.is_empty()) { lines.start_from_head(); do { g = lines.get_data(); if (! is_in_region(g)) { if (! can_grow_region(g)) { make_new_region(g); } } lines.move_right(); } while (! lines.is_equal_to_head()); } } /* * remove_redundant_regions - runs through the regions and ensures that * all are needed. This is required as * a picture may be empty, or EQ EN pair * maybe empty. */ void html_printer::remove_redundant_regions (void) { region_glob *r; // firstly run through the region making sure that all are needed // ie all contain a line or word if (! page_contents->regions.is_empty()) { page_contents->regions.start_from_tail(); do { r = page_contents->regions.get_data(); calculate_region_margins(r); if (page_contents->has_line(r) || page_contents->has_word(r)) { page_contents->regions.move_right(); } else { page_contents->regions.sub_move_right(); } } while ((! page_contents->regions.is_empty()) && (! page_contents->regions.is_equal_to_tail())); } } void html_printer::display_regions (void) { if (debug_table_on) { region_glob *r; fprintf(stderr, "==========s t a r t===========\n"); if (! page_contents->regions.is_empty()) { page_contents->regions.start_from_head(); do { r = page_contents->regions.get_data(); fprintf(stderr, "region minv %d maxv %d\n", r->minv, r->maxv); page_contents->regions.move_right(); } while (! page_contents->regions.is_equal_to_head()); } fprintf(stderr, "============e n d=============\n"); fflush(stderr); } } /* * remove_duplicate_regions - runs through the regions and ensures that * no duplicates exist. */ void html_printer::remove_duplicate_regions (void) { region_glob *r; region_glob *l=0; if (! page_contents->regions.is_empty()) { page_contents->regions.start_from_head(); l = page_contents->regions.get_data(); page_contents->regions.move_right(); r = page_contents->regions.get_data(); if (l != r) { do { r = page_contents->regions.get_data(); // we have a legit region so we check for an intersection if (is_intersection(r->minv, r->minv, l->minv, l->maxv) && is_intersection(r->minh, r->maxh, l->minh, l->maxh)) { l->minv = min(r->minv, l->minv); l->maxv = max(r->maxv, l->maxv); l->minh = min(r->minh, l->minh); l->maxh = max(r->maxh, l->maxh); calculate_region_margins(l); page_contents->regions.sub_move_right(); } else { l = r; page_contents->regions.move_right(); } } while ((! page_contents->regions.is_empty()) && (! page_contents->regions.is_equal_to_head())); } } } int page::has_line (region_glob *r) { graphic_glob *g; if (! lines.is_empty()) { lines.start_from_head(); do { g = lines.get_data(); if (is_subsection(g->minv, g->maxv, r->minv, r->maxv) && is_subsection(g->minh, g->maxh, r->minh, r->maxh)) { return( TRUE ); } lines.move_right(); } while (! lines.is_equal_to_head()); } return( FALSE ); } int page::has_word (region_glob *r) { text_glob *g; if (! words.is_empty()) { words.start_from_head(); do { g = words.get_data(); if (is_subsection(g->minv, g->maxv, r->minv, r->maxv) && is_subsection(g->minh, g->maxh, r->minh, r->maxh)) { return( TRUE ); } words.move_right(); } while (! words.is_equal_to_head()); } return( FALSE ); } void html_printer::calculate_region_margins (region_glob *r) { text_glob *w; graphic_glob *g; r->minh = right_margin_indent; r->maxh = left_margin_indent; if (! page_contents->lines.is_empty()) { page_contents->lines.start_from_head(); do { g = page_contents->lines.get_data(); if (is_subsection(g->minv, g->maxv, r->minv, r->maxv)) { r->minh = min(r->minh, g->minh); r->maxh = max(r->maxh, g->maxh); } page_contents->lines.move_right(); } while (! page_contents->lines.is_equal_to_head()); } if (! page_contents->words.is_empty()) { page_contents->words.start_from_head(); do { w = page_contents->words.get_data(); if (is_subsection(w->minv, w->maxv, r->minv, r->maxv)) { r->minh = min(r->minh, w->minh); r->maxh = max(r->maxh, w->maxh); } page_contents->words.move_right(); } while (! page_contents->words.is_equal_to_head()); } } int page::is_in_region (graphic_glob *g) { region_glob *r; if (! regions.is_empty()) { regions.start_from_head(); do { r = regions.get_data(); if (is_subsection(g->minv, g->maxv, r->minv, r->maxv) && is_subsection(g->minh, g->maxh, r->minh, r->maxh)) { return( TRUE ); } regions.move_right(); } while (! regions.is_equal_to_head()); } return( FALSE ); } /* * no_raw_commands - returns TRUE if no html raw commands exist between * minv and maxv. */ int page::no_raw_commands (int minv, int maxv) { text_glob *g; if (! words.is_empty()) { words.start_from_head(); do { g = words.get_data(); if ((g->is_raw_command) && (g->is_html_command) && (is_intersection(g->minv, g->maxv, minv, maxv))) { return( FALSE ); } words.move_right(); } while (! words.is_equal_to_head()); } return( TRUE ); } /* * can_grow_region - returns TRUE if a region exists which can be extended * to include graphic_glob *g. The region is extended. */ int page::can_grow_region (graphic_glob *g) { region_glob *r; int quarter_inch=font::res/4; if (! regions.is_empty()) { regions.start_from_head(); do { r = regions.get_data(); // must prevent grohtml from growing a region through a html raw command if (is_intersection(g->minv, g->maxv, r->minv, r->maxv+quarter_inch) && (no_raw_commands(r->minv, r->maxv+quarter_inch))) { #if defined(DEBUGGING) stop(); printf("r minh=%d minv=%d maxh=%d maxv=%d\n", r->minh, r->minv, r->maxh, r->maxv); printf("g minh=%d minv=%d maxh=%d maxv=%d\n", g->minh, g->minv, g->maxh, g->maxv); #endif r->minv = min(r->minv, g->minv); r->maxv = max(r->maxv, g->maxv); r->minh = min(r->minh, g->minh); r->maxh = max(r->maxh, g->maxh); #if defined(DEBUGGING) printf(" r minh=%d minv=%d maxh=%d maxv=%d\n", r->minh, r->minv, r->maxh, r->maxv); #endif return( TRUE ); } regions.move_right(); } while (! regions.is_equal_to_head()); } return( FALSE ); } /* * make_new_region - creates a new region to contain, g. */ void page::make_new_region (graphic_glob *g) { region_glob *r=new region_glob; r->minv = g->minv; r->maxv = g->maxv; r->minh = g->minh; r->maxv = g->maxv; regions.add(r); } void html_printer::dump_page(void) { text_glob *g; printf("\n\ndebugging start\n"); page_contents->words.start_from_head(); do { g = page_contents->words.get_data(); printf("%s ", g->text_string); page_contents->words.move_right(); } while (! page_contents->words.is_equal_to_head()); printf("\ndebugging end\n\n"); } /* * traverse_page_regions - runs through the regions in current_page * and generate html for text, and troff output * for all graphics. */ void html_printer::traverse_page_regions (void) { region_glob *r; start_region_vpos = 0; start_region_hpos = 0; end_region_vpos = -1; end_region_hpos = -1; if (! page_contents->regions.is_empty()) { page_contents->regions.start_from_head(); do { r = page_contents->regions.get_data(); if (r->minv > 0) { end_region_vpos = r->minv-1; } else { end_region_vpos = 0; } end_region_hpos = -1; display_globs(TRUE); calculate_region_margins(r); start_region_vpos = end_region_vpos; end_region_vpos = r->maxv; start_region_hpos = r->minh; end_region_hpos = r->maxh; display_globs(FALSE); start_region_vpos = end_region_vpos+1; start_region_hpos = 0; page_contents->regions.move_right(); } while (! page_contents->regions.is_equal_to_head()); start_region_vpos = end_region_vpos+1; start_region_hpos = 0; end_region_vpos = -1; end_region_hpos = -1; } display_globs(TRUE); } int html_printer::is_within_region (text_glob *t) { int he, ve, hs; if (start_region_hpos == -1) { hs = t->minh; } else { hs = start_region_hpos; } if (end_region_vpos == -1) { ve = t->maxv; } else { ve = end_region_vpos; } if (end_region_hpos == -1) { he = t->maxh; } else { he = end_region_hpos; } return( is_subsection(t->minv, t->maxv, start_region_vpos, ve) && is_subsection(t->minh, t->maxh, hs, he) ); } int html_printer::is_within_region (graphic_glob *g) { int he, ve, hs; if (start_region_hpos == -1) { hs = g->minh; } else { hs = start_region_hpos; } if (end_region_vpos == -1) { ve = g->maxv; } else { ve = end_region_vpos; } if (end_region_hpos == -1) { he = g->maxh; } else { he = end_region_hpos; } return( is_subsection(g->minv, g->maxv, start_region_vpos, ve) && is_subsection(g->minh, g->maxh, hs, he) ); } int html_printer::is_less (graphic_glob *g, text_glob *t) { return( (g->minv < t->minv) || ((g->minv == t->minv) && (g->minh < t->minh)) ); } void html_printer::convert_to_image (char *troff_src, char *image_name) { char buffer[MAX_STRING_LENGTH*2 + 200]; char *ps_src = mktemp(xtmptemplate("-ps-")); sprintf(buffer, "grops%s %s > %s", EXE_EXT, troff_src, ps_src); if (debug_on) { fprintf(stderr, "%s\n", buffer); } int status = system(buffer); if (status == -1) { fprintf(stderr, "\"%s\" failed (no grops on PATH?)\n", buffer); return; } else if (status) { fprintf(stderr, "\"%s\" returned status %d\n", buffer, status); } if (image_type == gif) { sprintf(buffer, "echo showpage | gs%s -q -dSAFER -sDEVICE=ppmraw -r%d -g%dx%d -sOutputFile=- %s - | ppmquant%s 256 | ppmtogif%s > %s.gif", EXE_EXT, image_res, (end_region_hpos-start_region_hpos)*image_res/font::res+IMAGE_BOARDER_PIXELS, (end_region_vpos-start_region_vpos)*image_res/font::res+IMAGE_BOARDER_PIXELS, ps_src, EXE_EXT, EXE_EXT, image_name); } else { sprintf(buffer, "echo showpage | gs%s -q -dSAFER -sDEVICE=%s -r%d -g%dx%d -sOutputFile=- %s - > %s.png", EXE_EXT, image_device, image_res, (end_region_hpos-start_region_hpos)*image_res/font::res+IMAGE_BOARDER_PIXELS, (end_region_vpos-start_region_vpos)*image_res/font::res+IMAGE_BOARDER_PIXELS, ps_src, image_name); #if 0 sprintf(buffer, "echo showpage | gs -q -dSAFER -sDEVICE=ppmraw -r%d -g%dx%d -sOutputFile=- %s.ps - > %s.pnm ; pnmtopng -transparent white %s.pnm > %s.png \n", /* image_device, */ image_res, (end_region_hpos-start_region_hpos)*image_res/font::res+IMAGE_BOARDER_PIXELS, (end_region_vpos-start_region_vpos)*image_res/font::res+IMAGE_BOARDER_PIXELS, name, name, name, image_name); #endif } if (debug_on) { fprintf(stderr, "%s\n", buffer); } // Redirect standard error to the null device. This is more // portable than using "2> /dev/null" inside the commands above, // since it doesn't require a Unixy shell. int save_stderr = dup(2); if (save_stderr > 2) { int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666); if (fdnull > 2) dup2(fdnull, 2); if (fdnull >= 0) close(fdnull); } status = system(buffer); dup2(save_stderr, 2); if (status == -1) { fprintf(stderr, "Conversion to image failed (no gs/ppmquant/ppmtogif on PATH?)\n"); } else if (status) { fprintf(stderr, "Conversion to image returned status %d\n", status); } unlink(ps_src); unlink(troff_src); } void html_printer::prologue (void) { troff.put_string("x T ps\nx res "); troff.put_number(postscript_res); troff.put_string(" 1 1\nx init\np1\n"); } void html_printer::display_globs (int is_to_html) { text_glob *t=0; graphic_glob *g=0; FILE *f=0; char *troff_src; int something=FALSE; int is_center=FALSE; end_paragraph(); if (! is_to_html) { is_center = html_position_region(); make_new_image_name(); f = xtmpfile(&troff_src, "-troff-", FALSE); troff.set_file(f); prologue(); output_style.f = 0; } if (! page_contents->words.is_empty()) { page_contents->words.start_from_head(); t = page_contents->words.get_data(); } if (! page_contents->lines.is_empty()) { page_contents->lines.start_from_head(); g = page_contents->lines.get_data(); } do { #if 0 if ((t != 0) && (strcmp(t->text_string, "(1.a)") == 0)) { stop(); } #endif if ((t == 0) && (g != 0)) { if (is_within_region(g)) { something = TRUE; display_line(g, is_to_html); } if (page_contents->lines.is_empty() || page_contents->lines.is_equal_to_tail()) { g = 0; } else { g = page_contents->lines.move_right_get_data(); } } else if ((g == 0) && (t != 0)) { if (is_within_region(t)) { display_word(t, is_to_html); something = TRUE; } if (page_contents->words.is_empty() || page_contents->words.is_equal_to_tail()) { t = 0; } else { t = page_contents->words.move_right_get_data(); } } else { if ((g == 0) || (t == 0)) { // hmm nothing to print out... } else if (is_less(g, t)) { if (is_within_region(g)) { display_line(g, is_to_html); something = TRUE; } if (page_contents->lines.is_empty() || page_contents->lines.is_equal_to_tail()) { g = 0; } else { g = page_contents->lines.move_right_get_data(); } } else { if (is_within_region(t)) { display_word(t, is_to_html); something = TRUE; } if (page_contents->words.is_empty() || page_contents->words.is_equal_to_tail()) { t = 0; } else { t = page_contents->words.move_right_get_data(); } } } } while ((t != 0) || (g != 0)); if ((! is_to_html) && (f != 0)) { fclose(troff.get_file()); if (something) { convert_to_image(troff_src, image_name); if (is_center) { end_paragraph(); begin_paragraph(center_alignment); force_begin_paragraph(); } html.put_string("\n"); html_newline(); if (is_center) { end_paragraph(); } output_vpos = end_region_vpos; output_hpos = 0; need_one_newline = FALSE; output_style.f = 0; end_paragraph(); } } } void html_printer::flush_page (void) { calculate_margin(); output_vpos = -1; output_hpos = get_left(); supress_sub_sup = TRUE; #if 0 dump_page(); #endif html.begin_comment("left margin: ").comment_arg(i_to_a(left_margin_indent)).end_comment();; html.begin_comment("right margin: ").comment_arg(i_to_a(right_margin_indent)).end_comment();; remove_redundant_regions(); page_contents->calculate_region(); remove_duplicate_regions(); find_title(); supress_sub_sup = TRUE; traverse_page_regions(); terminate_current_font(); if (need_one_newline) { html_newline(); } end_paragraph(); // move onto a new page delete page_contents; page_contents = new page; } static int convertSizeToHTML (int size) { if (size < 6) { return( 0 ); } else if (size < 8) { return( 1 ); } else if (size < 10) { return( 2 ); } else if (size < 12) { return( 3 ); } else if (size < 14) { return( 4 ); } else if (size < 16) { return( 5 ); } else if (size < 18) { return( 6 ); } else { return( 7 ); } } void html_printer::write_html_font_face (const char *fontname, const char *left, const char *right) { switch (fontname[0]) { case 'C': html.put_string(left) ; html.put_string("tt"); html.put_string(right); break; case 'H': break; case 'T': break; default: break; } } void html_printer::write_html_font_type (const char *fontname, const char *left, const char *right) { if (strcmp(&fontname[1], "B") == 0) { html.put_string(left) ; html.put_string("B"); html.put_string(right); } else if (strcmp(&fontname[1], "I") == 0) { html.put_string(left) ; html.put_string("I"); html.put_string(right); } else if (strcmp(&fontname[1], "BI") == 0) { html.put_string(left) ; html.put_string("EM"); html.put_string(right); } } void html_printer::html_change_font (text_glob *g, const char *fontname, int size) { char buffer[1024]; if (output_style.f != 0) { const char *oldfontname = output_style.f->get_name(); // firstly terminate the current font face and type if ((oldfontname != 0) && (oldfontname != fontname)) { write_html_font_face(oldfontname, ""); write_html_font_type(oldfontname, ""); } } if ((output_style.point_size != size) && (output_style.point_size != 0)) { // shutdown the previous font size html.put_string(""); } if ((output_style.point_size != size) && (size != 0)) { // now emit the size if it has changed sprintf(buffer, "", convertSizeToHTML(size)); html.put_string(buffer); output_style.point_size = size; // and remember the size } output_style.f = 0; // no style at present output_style.point_size = size; // remember current font size if (fontname != 0) { if (! g->is_raw_command) { // now emit the new font write_html_font_face(fontname, "<", ">"); // now emit the new font type write_html_font_type(fontname, "<", ">"); output_style = g->text_style; // remember style for next time } } } void html_printer::change_font (text_glob *g, int is_to_html) { if (is_to_html) { if (output_style != g->text_style) { const char *fontname=0; int size=0; if (g->text_style.f != 0) { fontname = g->text_style.f->get_name(); size = (font::res/(72*font::sizescale))*g->text_style.point_size; } html_change_font(g, fontname, size); } } else { // is to troff if (output_style != g->text_style) { if (g->text_style.f != 0) { const char *fontname = g->text_style.f->get_name(); int size = (font::res/(72*font::sizescale))*g->text_style.point_size; if (fontname == 0) { fatal("no internalname specified for font"); } troff_change_font(fontname, size, g->text_style.font_no); output_style = g->text_style; // remember style for next time } } } } /* * is_bold - returns TRUE if the text inside, g, is using a bold face. * It returns FALSE is g contains a raw html command, even if this uses * a bold font. */ int html_printer::is_bold (text_glob *g) { if (g->text_style.f == 0) { // unknown font return( FALSE ); } else if (g->is_raw_command) { return( FALSE ); } else { const char *fontname = g->text_style.f->get_name(); if (strlen(fontname) >= 2) { return( fontname[1] == 'B' ); } else { return( FALSE ); } } } void html_printer::terminate_current_font (void) { text_glob g; // we create a dummy glob just so we can tell html_change_font not to start up // a new font g.is_raw_command = TRUE; html_change_font(&g, 0, 0); } void html_printer::write_header (text_glob *g) { if (strlen(header.header_buffer) > 0) { if (header.header_level > 7) { header.header_level = 7; } if (cutoff_heading+2 > header.header_level) { // firstly we must terminate any font and type faces terminate_current_font(); end_paragraph(); // secondly we generate a tag html.put_string(""); // now we save the header so we can issue a list of link style st; header.no_of_headings++; text_glob *h=new text_glob(&st, header.headings.add_string(header.header_buffer, strlen(header.header_buffer)), strlen(header.header_buffer), header.no_of_headings, header.header_level, header.no_of_headings, header.header_level, FALSE, FALSE); header.headers.add(h); // and add this header to the header list } else { terminate_current_font(); end_paragraph(); } // we adjust the margin if necessary if (g->minh < left_margin_indent) { header_indent = g->minh; } // and now we issue the real header html.put_string(""); html.put_string(header.header_buffer); html.put_string(""); need_one_newline = FALSE; begin_paragraph(left_alignment); header.written_header = TRUE; } } /* * translate_str_to_html - translates a string, str, into html representation. * len indicates the string length. */ void translate_str_to_html (font *f, char *str, int len) { char buf[MAX_STRING_LENGTH]; str_translate_to_html(f, buf, MAX_STRING_LENGTH, str, len, TRUE); strncpy(str, buf, max(len, strlen(buf)+1)); } /* * write_headings - emits a list of links for the headings in this document */ void header_desc::write_headings (FILE *f) { text_glob *g; if (! headers.is_empty()) { headers.start_from_head(); do { g = headers.get_data(); fprintf(f, "%s
\n", g->text_string, g->text_string); headers.move_right(); } while (! headers.is_equal_to_head()); } } void html_printer::determine_header_level (void) { int i; int l=strlen(header.header_buffer); int stops=0; for (i=0; ((i 0) { header.header_level = stops; } } void html_printer::build_header (text_glob *g) { text_glob *l; int current_vpos; char buf[MAX_STRING_LENGTH]; strcpy(header.header_buffer, ""); do { l = g; current_vpos = g->minv; str_translate_to_html(g->text_style.f, buf, MAX_STRING_LENGTH, g->text_string, g->text_length, TRUE); strcat(header.header_buffer, (char *)buf); page_contents->words.move_right(); g = page_contents->words.get_data(); if (g->minv == current_vpos) { strcat(header.header_buffer, " "); } } while ((! page_contents->words.is_equal_to_head()) && ((g->minv == current_vpos) || (l->maxh == right_margin_indent))); determine_header_level(); // finally set the output to neutral for after the header g = page_contents->words.get_data(); output_vpos = g->minv; // set output_vpos to the next line since output_hpos = left_margin_indent; // html header forces a newline anyway page_contents->words.move_left(); // so that next time we use old g need_one_newline = FALSE; } /* * is_whole_line_bold - returns TRUE if the whole line is bold. */ int html_printer::is_whole_line_bold (text_glob *g) { text_glob *n=g; int current_vpos=g->minv; do { if (is_bold(n)) { page_contents->words.move_right(); n = page_contents->words.get_data(); } else { while (page_contents->words.get_data() != g) { page_contents->words.move_left(); } return( FALSE ); } } while ((! page_contents->words.is_equal_to_head()) && (is_on_same_line(n, current_vpos))); // was (n->minv == current_vpos) while (page_contents->words.get_data() != g) { page_contents->words.move_left(); } return( TRUE ); } /* * is_a_header - returns TRUE if the whole sequence of contineous lines are bold. * It checks to see whether a line is likely to be contineous and * then checks that all words are bold. */ int html_printer::is_a_header (text_glob *g) { text_glob *l; text_glob *n=g; int current_vpos; do { l = n; current_vpos = n->minv; if (is_bold(n)) { page_contents->words.move_right(); n = page_contents->words.get_data(); } else { while (page_contents->words.get_data() != g) { page_contents->words.move_left(); } return( FALSE ); } } while ((! page_contents->words.is_equal_to_head()) && ((n->minv == current_vpos) || (l->maxh == right_margin_indent))); while (page_contents->words.get_data() != g) { page_contents->words.move_left(); } return( TRUE ); } int html_printer::processed_header (text_glob *g) { if ((guess_on) && (g->minh <= left_margin_indent) && (! using_table_for_indent()) && (is_a_header(g))) { build_header(g); write_header(g); return( TRUE ); } else { return( FALSE ); } } int is_punctuation (char *s, int length) { return( (length == 1) && ((s[0] == '(') || (s[0] == ')') || (s[0] == '!') || (s[0] == '.') || (s[0] == '[') || (s[0] == ']') || (s[0] == '?') || (s[0] == ',') || (s[0] == ';') || (s[0] == ':') || (s[0] == '@') || (s[0] == '#') || (s[0] == '$') || (s[0] == '%') || (s[0] == '^') || (s[0] == '&') || (s[0] == '*') || (s[0] == '+') || (s[0] == '-') || (s[0] == '=') || (s[0] == '{') || (s[0] == '}') || (s[0] == '|') || (s[0] == '\"') || (s[0] == '\'')) ); } /* * move_horizontal - moves right into the position, g->minh. */ void html_printer::move_horizontal (text_glob *g, int left_margin) { if (g->text_style.f != 0) { int w = g->text_style.f->get_space_width(g->text_style.point_size); if (w == 0) { fatal("space width is zero"); } if ((output_hpos == left_margin) && (g->minh > output_hpos)) { make_html_indent(g->minh-output_hpos); } else { emit_space(g, FALSE); } output_hpos = g->maxh; output_vpos = g->minv; change_font(g, TRUE); } } /* * looks_like_subscript - returns TRUE if, g, looks like a subscript. */ int html_printer::looks_like_subscript (text_glob *g) { int r = font::res; int height = output_style.point_size*r/72; /* was return( ((output_vpos < g->minv) && (output_style.point_size != 0) && * (output_style.point_size > g->text_style.point_size)) ); */ return( (output_style.point_size != 0) && (! supress_sub_sup) && (output_vpos+height < g->maxv) ); } /* * looks_like_superscript - returns TRUE if, g, looks like a superscript. */ int html_printer::looks_like_superscript (text_glob *g) { int r = font::res; int height = output_style.point_size*r/72; /* was * return(((output_vpos > g->minv) && (output_style.point_size != 0) && * (output_style.point_size > g->text_style.point_size))); */ return( (output_style.point_size != 0) && (! supress_sub_sup) && (output_vpos+height > g->maxv) ); } /* * looks_like_larger_font - returns TRUE if, g, can be treated as a larger font. * g needs to be on the same line */ int html_printer::looks_like_larger_font (text_glob *g) { int r = font::res; int height = output_style.point_size*r/72; return( (output_vpos+height == g->maxv) && (output_style.point_size != 0) && (convertSizeToHTML(g->text_style.point_size)+1 == convertSizeToHTML(output_style.point_size)) ); } /* * looks_like_smaller_font - returns TRUE if, g, can be treated as a smaller font. * g needs to be on the same line */ int html_printer::looks_like_smaller_font (text_glob *g) { int r = font::res; int height = output_style.point_size*r/72; return( (output_vpos+height == g->maxv) && (output_style.point_size != 0) && (convertSizeToHTML(g->text_style.point_size) == convertSizeToHTML(output_style.point_size)+1) ); } /* * pretend_is_on_same_line - returns TRUE if we think, g, is on the same line as the previous glob. * Note that it believes a single word spanning the left..right as being * on a different line. */ int html_printer::pretend_is_on_same_line (text_glob *g, int left_margin, int right_margin) { return( auto_on && (right_margin == output_hpos) && (left_margin == g->minh) && (right_margin != g->maxh) && ((! is_whole_line_bold(g)) || (g->text_style.f == output_style.f)) && (! (using_table_for_indent()) || (indentation.wrap_margin)) ); } int html_printer::is_on_same_line (text_glob *g, int vpos) { #if 0 if (g->is_html_command) { stop(); } #endif return( (vpos >= 0) && (is_intersection(vpos, vpos+g->text_style.point_size*font::res/72-1, g->minv, g->maxv)) ); } /* * make_html_indent - creates a relative indentation. */ void html_printer::make_html_indent (int indent) { if ((indent > 0) && ((right_margin_indent-get_left()) > 0) && ((indent*100)/(right_margin_indent-get_left()))) { html.put_string(""); } } /* * using_table_for_indent - returns TRUE if we currently using a table for indentation * purposes. */ int html_printer::using_table_for_indent (void) { return( indentation.no_of_columns != 0 ); } /* * calculate_min_gap - returns the minimum gap by which we deduce columns. * This is a rough heuristic. */ int html_printer::calculate_min_gap (text_glob *g) { text_glob *t = g; while ((t->is_raw_command) && (! page_contents->words.is_equal_to_tail()) && ((t->minv < end_region_vpos) || (end_region_vpos < 0))) { page_contents->words.move_right(); t=page_contents->words.get_data(); } rewind_text_to(g); if (t->is_raw_command) { return( font::res * 10 ); // impossibly large gap width } else { return( t->text_style.f->get_space_width(t->text_style.point_size)*GAP_SPACES ); } } /* * collect_columns - place html text in a column and return the vertical limit reached. */ int html_printer::collect_columns (struct text_defn *next_words, struct text_defn *next_cols, struct text_defn *last_words, struct text_defn *last_cols, int max_words) { text_glob *start = page_contents->words.get_data(); text_glob *t = start; int upper_limit = 0; /* * initialize cols and words */ next_words[0].left = 0; next_words[0].right = 0; next_cols [0].left = 0; next_cols [0].right = 0; /* * if we have not reached the end collect the words on the current line */ if (start != 0) { int graphic_limit = end_region_vpos; if (is_whole_line_bold(t) && (t->minh <= left_margin_indent) && (guess_on)) { /* * found header therefore terminate indentation table. * Return a negative number so we know a header has * stopped the column */ upper_limit = -t->minv; } else { int i =0; // is the index into next_cols int j =0; // is the column index for last_cols int k =0; // is the index into next_words int l =0; // is the index into next_words int prevh =0; int mingap =calculate_min_gap(start); /* * while words on the same line record them and any significant gaps */ while ((t != 0) && (is_on_same_line(t, start->minv) && (i t->minv))) { /* * now find column index from the last line which corresponds to, t. */ j = find_column_index_in_line(t, last_cols); /* * now find word index from the last line which corresponds to, t. */ l = find_column_index_in_line(t, last_words); /* * Note t->minh might equal t->maxh when we are passing a special device character via \X * we currently ignore this when considering tables * * if we have found a significant gap then record it */ if (((t->minh - prevh >= mingap) || ((last_cols != 0) && (last_cols [j].right != 0) && (t->minh == last_cols [j].left))) && (t->minh != t->maxh)) { next_cols[i].left = t->minh; next_cols[i].right = t->maxh; i++; /* * terminate the array */ if (i0) { /* * move previous right hand column to align with, t. */ if (t->minh > next_cols[i-1].left) { /* * a simple precaution in case we get globs which are technically on the same line * (sadly this does occur sometimes - maybe we should be stricter with is_on_same_line) * --fixme-- */ next_cols[i-1].right = max(next_cols[i-1].right, t->maxh); } } /* * remember to record the individual words */ next_words[k].left = t->minh; next_words[k].right = t->maxh; k++; /* * and record the vertical upper limit */ upper_limit = max(t->minv, upper_limit); /* * and update prevh - used to detect a when a different line is seen */ prevh = t->maxh; /* * get next word into, t, which equals 0, if no word is found */ page_contents->words.move_right(); t = page_contents->words.get_data(); if (page_contents->words.is_equal_to_head()) { t = 0; } } /* * and terminate the next_words array */ if (k next_cols[k].right) { fprintf(stderr, "left > right\n"); fflush(stderr); stop(); fatal("next_cols has messed up columns"); } if ((k>0) && (k+1 next_cols[k+1].left)) { fprintf(stderr, "next_cols[k].right > next_cols[k+1].left\n"); fflush(stderr); stop(); fatal("next_cols has messed up columns"); } } #endif } } return( upper_limit ); } /* * conflict_with_words - returns TRUE if a word sequence crosses a column. */ int html_printer::conflict_with_words (struct text_defn *column_guess, struct text_defn *words) { int i=0; int j; while ((column_guess[i].right != 0) && (i= column_guess[i+1].left)) { if (debug_table_on) { fprintf(stderr, "is a conflict with words\n"); fflush(stderr); } return( TRUE ); } j++; } i++; } if (debug_table_on) { fprintf(stderr, "is NOT a conflict with words\n"); fflush(stderr); } return( FALSE ); } /* * combine_line - combines dest and src. */ void html_printer::combine_line (struct text_defn *dest, struct text_defn *src) { int i; for (i=0; (ileft)) { i++; } if (line[i].right == 0) { // add to the end if (i0) && (line[i-1].left > item->left)) { fatal("insertion error"); } line[i].left = item->left; line[i].right = item->right; i++; line[i].left = 0; line[i].right = 0; } } else { if (line[i].left == item->left) { line[i].right = max(item->right, line[i].right); } else { // insert int left = item->left; int right = item->right; int l = line[i].left; int r = line[i].right; while ((i+1left) { return( TRUE ); } else { i++; } } return( FALSE ); } /* * calculate_right - calculate the right most margin for each column in line. */ void html_printer::calculate_right (struct text_defn *line, int max_words) { int i=0; while ((i0) { line[i-1].right = line[i].left; } i++; } } /* * add_right_full_width - adds an extra column to the right to bring the table up to * full width. */ void html_printer::add_right_full_width (struct text_defn *line, int mingap) { int i=0; while ((i0) && (line[i-1].right != right_margin_indent) && (i+10) { // remember right_margin_indent is the right most position for this page line[i-1].right = column_calculate_right_margin(line[i-1].left, right_margin_indent); } } /* * is_column_match - returns TRUE if a word is aligned in the same horizontal alignment * between two lines, line1 and line2. If so then this horizontal * position is saved in match. */ int html_printer::is_column_match (struct text_defn *match, struct text_defn *line1, struct text_defn *line2, int max_words) { int i=0; int j=0; int found=FALSE; int first=(match[0].left==0); if (first) { struct text_defn t; t.left = left_margin_indent; t.right = 0; include_into_list(match, &t); } while ((line1[i].right != 0) && (line2[i].right != 0)) { if (line1[i].left == line2[j].left) { // same horizontal alignment found include_into_list(match, &line1[i]); i++; j++; found = TRUE; } else if (line1[i].left < line2[j].left) { i++; } else { j++; } } calculate_right(match, max_words); return( found ); } /* * check_lack_of_hits - returns TRUE if a column has been moved to a position * of only one hit from a position of more than one hit. */ int html_printer::check_lack_of_hits (struct text_defn *next_guess, struct text_defn *last_guess, text_glob *start, int limit) { text_glob *current=page_contents->words.get_data(); int n=count_columns(last_guess); int m=count_columns(next_guess); int i, j; if (limit > 0) { rewind_text_to(start); count_hits(last_guess, n, limit); rewind_text_to(current); i=0; j=0; while ((i= 2)) { /* * next_guess has to be = 1 as this position is new */ return( TRUE ); } if (last_guess[i].left < next_guess[j].left) { i++; } else { j++; } } } return( FALSE ); } /* * remove_white_using_words - remove white space in, last_guess, by examining, next_line * placing results into next_guess. * It returns TRUE if the same columns exist in next_guess and last_guess * we do allow columns to shrink but if a column disappears then we return FALSE. */ int html_printer::remove_white_using_words (struct text_defn *next_guess, struct text_defn *last_guess, struct text_defn *next_line) { int i=0; int j=0; int k=0; int removed=FALSE; while ((last_guess[j].right != 0) && (next_line[k].right != 0)) { if (last_guess[j].left == next_line[k].left) { // same horizontal alignment found next_guess[i].left = last_guess[j].left; next_guess[i].right = max(last_guess[j].right, next_line[k].right); i++; j++; k++; if ((next_guess[i-1].right > last_guess[j].left) && (last_guess[j].right != 0)) { removed = TRUE; } } else if (last_guess[j].right < next_line[k].left) { next_guess[i].left = last_guess[j].left; next_guess[i].right = last_guess[j].right; i++; j++; } else if (last_guess[j].left > next_line[k].right) { // insert a word sequence from next_line[k] next_guess[i].left = next_line[k].left; next_guess[i].right = next_line[k].right; i++; k++; } else if (is_intersection(last_guess[j].left, last_guess[j].right, next_line[k].left, next_line[k].right)) { // potential for a column disappearing next_guess[i].left = min(last_guess[j].left , next_line[k].left); next_guess[i].right = max(last_guess[j].right, next_line[k].right); i++; j++; k++; if ((next_guess[i-1].right > last_guess[j].left) && (last_guess[j].right != 0)) { removed = TRUE; } } } while (next_line[k].right != 0) { next_guess[i].left = next_line[k].left; next_guess[i].right = next_line[k].right; i++; k++; } if (iwords.get_data() != g) { if (page_contents->words.is_equal_to_head()) { page_contents->words.start_from_tail(); } else { page_contents->words.move_left(); } } } /* * can_loose_column - checks to see whether we should combine two columns. * This is allowed if there are is only one hit on the * left hand edge and the previous column is very close. */ void html_printer::can_loose_column (text_glob *start, struct text_defn *last_guess, int limit) { text_glob *current=page_contents->words.get_data(); int n=count_columns(last_guess); int i; rewind_text_to(start); count_hits(last_guess, n, limit); i=0; while (i (last_guess[i+1].left-last_guess[i].right))) { last_guess[i].right = last_guess[i+1].right; remove_entry_in_line(last_guess, i+1); n = count_columns(last_guess); i = 0; } else { i++; } } rewind_text_to(current); } /* * display_columns - a long overdue debugging function, as this column code is causing me grief :-( */ void html_printer::display_columns (const char *word, const char *name, text_defn *line) { int i=0; fprintf(stderr, "[%s:%s]", name, word); while (line[i].right != 0) { fprintf(stderr, " ", line[i].left, line[i].right, line[i].percent); i++; } fprintf(stderr, "\n"); fflush(stderr); } /* * copy_line - dest = src */ void html_printer::copy_line (struct text_defn *dest, struct text_defn *src) { int k; for (k=0; ((src[k].right != 0) && (k0) && (line[i-1].right != right_margin_indent) && (is_worth_column(line[i-1].right, right_margin_indent))) { t.left = line[i-1].right; t.right = right_margin_indent; include_into_list(line, &t); } } /* * is_continueous_column - returns TRUE if a line has a word on one * of the last_col right most boundaries. */ int html_printer::is_continueous_column (text_defn *last_col, text_defn *next_line) { int w = count_columns(next_line); int c = count_columns(last_col); int i, j; for (i=0; i= MIN_COLUMN ); #endif return( TRUE ); } /* * large_enough_gap - returns TRUE if a large enough gap for one line was seen. * We need to make sure that a single line definitely warrents * a table. * It also removes other smaller gaps. */ int html_printer::large_enough_gap (text_defn *last_col) { int i=0; int found=FALSE; int r=font::res; int gap=r/GAP_WIDTH_ONE_LINE; if (abs(last_col[i].left - left_margin_indent) >= gap) { found = TRUE; } while ((last_col[i].right != 0) && (last_col[i+1].right != 0)) { if (abs(last_col[i+1].left-last_col[i].right) >= gap) { found = TRUE; i++; } else { // not good enough for a single line, remove it last_col[i].right = last_col[i+1].right; remove_entry_in_line(last_col, i+1); } } return( found ); } /* * is_subset_of_columns - returns TRUE if line, a, is a subset of line, b. */ int html_printer::is_subset_of_columns (text_defn *a, text_defn *b) { int i; int j; i=0; while ((iwords.get_data(); text_glob *g = start; // firstly reset the used field for (i=0; iminv <= limit)) { i=0; while ((iminh)) { i++; } if ((col[i].left == g->minh) && (col[i].right != 0)) { col[i].is_used++; } page_contents->words.move_right(); if (page_contents->words.is_equal_to_head()) { g = 0; page_contents->words.start_from_tail(); } else { g=page_contents->words.get_data(); } } } /* * count_right_hits - counts the number of right hits per column. * A right hit is when the left hand position * of a glob hits the right hand column. */ void html_printer::count_right_hits (text_defn *col, int no_of_columns) { int i; text_glob *start = page_contents->words.get_data(); text_glob *g = start; // firstly reset the used field for (i=0; iminv <= indentation.vertical_limit)) { i=0; while ((iminh)) { i++; } if ((imaxh)) { if (debug_table_on) { fprintf(stderr, "found right hit [%s] at %d in %d\n", g->text_string, g->maxh, i); fflush(stderr); } col[i].right_hits++; } page_contents->words.move_right(); if (page_contents->words.is_equal_to_head()) { g = 0; page_contents->words.start_from_tail(); } else { g=page_contents->words.get_data(); } } } /* * right_indentation - returns TRUE if a single column has been found and * it resembles an indentation. Ie .RS/.RE or ABSTACT */ int html_printer::right_indentation (struct text_defn *last_guess) { // it assumes that last_guess contains a single column return( (last_guess[0].left > left_margin_indent) ); } /* * able_to_steal_width - returns TRUE if we have an unused column which we can steal from. * It must have more than MIN_TEXT_PERCENT to do this. */ int html_printer::able_to_steal_width (void) { int i; for (i=0; i MIN_TEXT_PERCENT)) { return( TRUE ); } } return( FALSE ); } /* * is_divisible_by - returns TRUE if n is divisible by d leaving no remainder. */ static int is_divisible_by (int n, int d) { return( (n % d) == 0 ); } /* * need_to_steal_width - returns TRUE if a used column need to be * given a little extra width for safty sake. */ int html_printer::need_to_steal_width (void) { int i; for (i=0; i0); i++) { if ((indentation.columns[i].is_used) && (indentation.columns[i].percent < PERCENT_THRESHOLD)) { indentation.columns[i].percent++; excess--; } } // we might as well try and keep any numbers simple if possible for (i=0; (i0); i++) { if ((indentation.columns[i].is_used) && (! is_divisible_by(indentation.columns[i].percent, MIN_TEXT_PERCENT))) { indentation.columns[i].percent++; excess--; } } // forget the niceties lets just use excess up now! for (i=0; (i0); i++) { if (indentation.columns[i].is_used) { indentation.columns[i].percent++; excess--; } } } /* * can_distribute_fairly - returns TRUE if we can redistribute some of the unused width into * columns that are used. */ int html_printer::can_distribute_fairly (void) { int i; int total=0; int used =0; int excess; // firstly total up all percentages - so we can use round offs for (i=0; i MIN_TEXT_PERCENT) { indentation.columns[i].percent--; excess++; } } } } if (excess >= used) { for (i=0; iminv; while ((start != 0) && (start->minv < indentation.vertical_limit) && (is_on_same_line(start, current_vpos))) { if (page_contents->words.is_equal_to_tail()) { start = 0; } else { page_contents->words.move_right(); start = page_contents->words.get_data(); } } if ((start != 0) && (start->minv < indentation.vertical_limit)) { // onto next line now current_vpos=start->minv; while ((start != 0) && (start->minv < indentation.vertical_limit) && (is_on_same_line(start, current_vpos))) { if (start->minh == indentation.columns[i].left) { return( TRUE ); } if (page_contents->words.is_equal_to_tail()) { start = 0; } else { page_contents->words.move_right(); start = page_contents->words.get_data(); } } } return( FALSE ); } /* * will_wrap_text - returns TRUE if text is wrapped in column, i. */ int html_printer::will_wrap_text (int i, text_glob *start) { text_glob *current=page_contents->words.get_data(); if (auto_on) { rewind_text_to(start); while ((start != 0) && (start->minv < indentation.vertical_limit)) { if (indentation.columns[i].right == start->maxh) { // ok right word is on column boarder - check next line if (next_line_on_left_column(i, start)) { rewind_text_to(current); return( TRUE ); } } if (page_contents->words.is_equal_to_tail()) { start = 0; } else { page_contents->words.move_right(); start = page_contents->words.get_data(); } } } rewind_text_to(current); return( FALSE ); } /* * remove_unnecessary_unused - runs through a table and decides whether an unused * column can be removed. This is only true if the * column to the left does not wrap text. */ void html_printer::remove_unnecessary_unused (text_glob *start) { int i=0; int left=get_left(); int right; while (itext_string, "[b4 steal] indentation.columns", indentation.columns); } // now steal from the unused columns.. remove_unnecessary_unused(start); if (debug_table_on) { display_columns(start->text_string, "[after steal] indentation.columns", indentation.columns); } #if 0 utilize_round_off(); #endif remove_zero_percentage_column(); } /* * is_column_subset - returns TRUE if the columns described by small can be contained in * the columns in large. */ int html_printer::is_column_subset (struct text_defn *small, struct text_defn *large) { int ns=count_columns(small); int nl=count_columns(large); int found; int i=0; int j; while (i0 ); } /* * right_most_column - returns the right most column position. */ int html_printer::right_most_column (struct text_defn *col) { int i = count_columns(col); if (i>0) { return( col[i-1].right ); } else { return( 0 ); } } /* * large_enough_gap_for_two - returns TRUE if there exists a large enough gap * for two lines. */ int html_printer::large_enough_gap_for_two (struct text_defn *col) { int i=0; int found=FALSE; int gap=MIN_COLUMN_FOR_TWO_LINES; if (abs(col[i].left - left_margin_indent) >= gap) { found = TRUE; } while ((col[i].right != 0) && (col[i+1].right != 0)) { if (abs(col[i+1].left-col[i].right) >= gap) { found = TRUE; i++; } else { // not good enough for this table, remove it col[i].right = col[i+1].right; remove_entry_in_line(col, i+1); } } return( found ); } /* * is_small_table - applies some rigorous rules to test whether we should start this * table at this point. */ int html_printer::is_small_table (int lines, struct text_defn *last_guess, struct text_defn *words_1, struct text_defn *cols_1, struct text_defn *words_2, struct text_defn *cols_2, int *limit, int *limit_1) { /* * firstly we check for an indented paragraph */ if ((lines >= 2) && (count_columns(cols_1) == count_columns(cols_2)) && (count_columns(cols_1) == 1) && right_indentation(cols_1) && (! right_indentation(cols_2)) && (cols_1[0].right == right_margin_indent)) { return( FALSE ); } if (lines == 2) { /* * as we only have two lines in our table we need to examine in detail whether * we should construct a table from these two lines. * For example if the text is the start of an indented paragraph and * line1 and line2 are contineous then they should form one row in our table but * if line1 and line2 are not contineous it is safer to treat them separately. * * We are prepared to reduce the table to one line */ if (((count_columns(cols_1) != count_columns(cols_2)) && (cols_1[0].left > cols_2[0].left)) || (! ((is_column_subset(cols_1, cols_2)) || (is_column_subset(cols_2, cols_1))))) { /* * now we must check to see whether line1 and line2 join */ if ((right_most_column(cols_1) == right_margin_indent) && (cols_2[0].left == left_margin_indent)) { /* * looks like they join, we don't want a table at all. */ return( FALSE ); } /* * use single line table */ lines--; *limit = *limit_1; copy_line(last_guess, cols_1); } } if ((count_columns(last_guess)==1) && (right_indentation(last_guess))) { if (lines == 1) { *limit = *limit_1; } return( TRUE ); } /* * check for large gap with single line or if multiple lines with more than one column */ if (lines == 1) { if (large_enough_gap(last_guess)) { *limit = *limit_1; return( TRUE ); } } else if (count_columns(last_guess)>1) { if (lines == 2) { return( large_enough_gap_for_two(last_guess) ); } return( TRUE ); } return( FALSE ); } /* * is_appropriate_to_start_table - returns TRUE if it is appropriate to start the table * at this point. */ int html_printer::is_appropriate_to_start_table (struct text_defn *cols_1, struct text_defn *cols_2, struct text_defn *last_guess) { if (count_columns(last_guess) == 1) { if (debug_table_on) { display_columns("", "[is] cols_1" , cols_1); display_columns("", "[is] cols_2" , cols_2); display_columns("", "[is] last_guess", last_guess); } if (! ((is_column_subset(cols_1, cols_2)) || (is_column_subset(cols_2, cols_1)))) { return( FALSE ); } if ((count_columns(cols_1) == 1) && (cols_1[0].left > left_margin_indent) && (cols_1[0].right < right_margin_indent) && (cols_1[0].right != cols_2[0].right) && (count_columns(last_guess) == 1)) { return( FALSE ); } } return( TRUE ); } /* * is_a_full_width_column - returns TRUE if there exists a full width column. */ int html_printer::is_a_full_width_column (void) { int i=0; while (i 2) { int i=0; int c=count_columns(cols_1); count_hits(cols_1, count_columns(cols_1), indentation.vertical_limit); rewind_text_to(start); count_right_hits(cols_1, count_columns(cols_1)); rewind_text_to(start); while (i 1) || (cols_1[i].right_hits > 1)) { return( FALSE ); } i++; } /* * first line (cols_1) is not aligned on any future column, we defer. */ return( TRUE ); } return( FALSE ); } /* * is_new_exact_right - returns TRUE if the, next_cols, has a word sitting * on the right hand margin of last_guess. But only * if no exact right word was found in last_cols. */ int html_printer::is_new_exact_right (struct text_defn *last_guess, struct text_defn *last_cols, struct text_defn *next_cols) { int n=count_columns(last_guess)-1; return( FALSE ); if ((n>=0) && (last_guess[n].right != 0) && (last_cols[n].right != 0) && (next_cols[n].right != 0)) { if ((last_cols[n].right != last_guess[n].right) && ((next_cols[n].right == last_guess[n].right) || (next_cols[n].right == right_margin_indent))) { return( TRUE ); } } return( FALSE ); } /* * found_use_for_table - checks whether the some words on one line directly match * the horizontal alignment of the line below. * This is rather complex as we need to detect text tables * such as .2C .IP Abstracts and indentations * * Algorithm is: * * read first line of text and calculate the significant * gaps between words * next next line of text and do the same * if a conflict between these lines exists and * first line is centered * then * return centered line * elsif start of a table is found * then * repeat * read next line of text and calculate significant gaps * until conflict between the gaps is found * record table * return table found * else * return no table found * fi */ int html_printer::found_use_for_table (text_glob *start) { text_glob *t; struct text_defn all_words [MAX_WORDS_PER_LINE]; // logical OR of words on each line struct text_defn words_1 [MAX_WORDS_PER_LINE]; // actual words found on first line struct text_defn words_2 [MAX_WORDS_PER_LINE]; // actual words found on second line struct text_defn cols_1 [MAX_WORDS_PER_LINE]; // columns found on line 1 struct text_defn cols_2 [MAX_WORDS_PER_LINE]; // columns found on line 2 struct text_defn last_words [MAX_WORDS_PER_LINE]; // actual words found on last line struct text_defn last_cols [MAX_WORDS_PER_LINE]; // columns found so far struct text_defn next_words [MAX_WORDS_PER_LINE]; // actual words found on last line (new) struct text_defn next_cols [MAX_WORDS_PER_LINE]; // columns found on next line struct text_defn last_guess [MAX_WORDS_PER_LINE]; // columns found on last line // (logical AND of gaps (treat gaps = true)) struct text_defn next_guess [MAX_WORDS_PER_LINE]; // columns found on next line // (logical AND of gaps (treat gaps = true)) struct text_defn prev_guess [MAX_WORDS_PER_LINE]; // temporary copy of last_guess int i =0; int lines =1; // number of lines read int limit; // vertical limit reached in our table int limit_1; // vertical position after line 1 #if 0 if (strcmp(start->text_string, "
") == 0) { stop(); } #endif /* * get first set of potential columns into last_line, call this last_guess */ limit = collect_columns(words_1, cols_1, 0, 0, MAX_WORDS_PER_LINE); limit_1 = limit; copy_line(last_guess, cols_1); /* * initialize the all_words columns - if this should ever equal a complete line * with no gaps then we terminate the table. */ copy_line(all_words, cols_1); /* * and set the current limit found */ indentation.vertical_limit = limit; /* * have we reached the end of page? */ if (page_contents->words.is_equal_to_head() || (limit == 0)) { cols_2[0].left = 0; cols_2[0].right = 0; } else { /* * the answer to the previous question was no. * So we need to examine the next line */ limit = collect_columns(words_2, cols_2, words_1, cols_1, MAX_WORDS_PER_LINE); if (limit >= 0) { lines++; } } /* * now check to see whether the first line looks like a single centered line */ if (single_centered_line(cols_1, cols_2, start)) { rewind_text_to(start); write_centered_line(start); /* * indicate to caller than we have centered text, not found a table. */ indentation.no_of_columns = 0; return( TRUE ); } else if (! table_on) { /* * user does not allow us to find a table (we are allowed to find centered lines (above)) */ rewind_text_to(start); return( FALSE ); } /* * remove any gaps from all_words */ combine_line(all_words, cols_2); if (debug_table_on) { display_columns(start->text_string, "[1] all_words" , all_words); display_columns(start->text_string, "[1] cols_1" , cols_1); display_columns(start->text_string, "[1] words_1" , words_1); display_columns(start->text_string, "[1] cols_2" , cols_2); display_columns(start->text_string, "[1] words_2" , words_2); display_columns(start->text_string, "[1] last_guess", last_guess); } /* * next_guess = last_guess AND next_cols (where gap = true) */ if (remove_white_using_words(prev_guess, last_guess, cols_2)) { } if (remove_white_using_words(next_guess, prev_guess, all_words)) { } if (debug_table_on) { display_columns(start->text_string, "[2] next_guess", next_guess); } copy_line(prev_guess, cols_1); combine_line(prev_guess, cols_2); /* * if no sequence of words crosses a column and * both the last column and all_words are not a full solid line of text */ if ((! conflict_with_words(next_guess, all_words)) && (continue_searching_column(next_guess, next_guess, all_words)) && (is_appropriate_to_start_table(cols_1, cols_2, prev_guess)) && (! page_contents->words.is_equal_to_head()) && ((end_region_vpos < 0) || (limit < end_region_vpos)) && (limit > 0)) { /* * subtract any columns which are bridged by a sequence of words */ copy_line(next_cols , cols_2); copy_line(next_words, words_2); do { copy_line(prev_guess, next_guess); // copy next_guess away so we can compare it later combine_line(last_guess, next_guess); if (debug_table_on) { t = page_contents->words.get_data(); display_columns(t->text_string, "[l] last_guess", last_guess); } indentation.vertical_limit = limit; copy_line(last_cols, next_cols); copy_line(last_words, next_words); if (page_contents->words.is_equal_to_head()) { /* * terminate the search */ next_cols[0].left = 0; next_cols[0].right = 0; } else { limit = collect_columns(next_words, next_cols, last_words, last_cols, MAX_WORDS_PER_LINE); lines++; } combine_line(all_words, next_cols); if (debug_table_on) { display_columns(t->text_string, "[l] all_words" , all_words); display_columns(t->text_string, "[l] last_cols" , last_cols); display_columns(t->text_string, "[l] next_words", next_words); display_columns(t->text_string, "[l] next_cols" , next_cols); } if (limit >= 0) { /* * (if limit is < 0 then the table ends anyway.) * we check to see whether we should combine close columns. */ can_loose_column(start, last_guess, limit); } t = page_contents->words.get_data(); #if 0 if (strcmp(t->text_string, "heT") == 0) { stop(); } #endif } while ((! remove_white_using_words(next_guess, last_guess, next_cols)) && (! conflict_with_words(next_guess, all_words)) && (continue_searching_column(next_guess, last_guess, all_words)) && ((is_continueous_column(prev_guess, last_cols)) || (is_exact_left(last_guess, next_cols))) && (! is_new_exact_right(last_guess, last_cols, next_cols)) && (! page_contents->words.is_equal_to_head()) && (! check_lack_of_hits(next_guess, last_guess, start, limit)) && ((end_region_vpos <= 0) || (t->minv < end_region_vpos)) && (limit >= 0)); lines--; } if (limit < 0) { indentation.vertical_limit = limit; } if (page_contents->words.is_equal_to_head()) { // end of page check whether we should include everything if ((! conflict_with_words(next_guess, all_words)) && (continue_searching_column(next_guess, last_guess, all_words)) && ((is_continueous_column(prev_guess, last_cols)) || (is_exact_left(last_guess, next_cols)))) { // end of page reached - therefore include everything page_contents->words.start_from_tail(); t = page_contents->words.get_data(); combine_line(last_guess, next_guess); indentation.vertical_limit = t->minv; } } else { t = page_contents->words.get_data(); if (((! conflict_with_words(last_guess, all_words))) && (t->minv > end_region_vpos) && (end_region_vpos > 0)) { indentation.vertical_limit = limit; } if ((end_region_vpos > 0) && (t->minv > end_region_vpos)) { indentation.vertical_limit = min(indentation.vertical_limit, end_region_vpos+1); } else if (indentation.vertical_limit < 0) { // -1 as we don't want to include section heading itself indentation.vertical_limit = -indentation.vertical_limit-1; } } if (debug_table_on) { display_columns(start->text_string, "[1] all_words" , all_words); display_columns(start->text_string, "[1] cols_1" , cols_1); display_columns(start->text_string, "[1] words_1" , words_1); display_columns(start->text_string, "[1] cols_2" , cols_2); display_columns(start->text_string, "[1] words_2" , words_2); display_columns(start->text_string, "[1] last_guess", last_guess); display_columns(start->text_string, "[1] next_guess", next_guess); } rewind_text_to(start); i = count_columns(last_guess); if ((i>1) || (right_indentation(last_guess))) { // was (continue_searching_column(last_guess, last_guess, all_words)))) { if (should_defer_table(lines, start, cols_1)) { /* * yes, but let us check for a single line table */ lines = 1; copy_line(last_guess, cols_1); } if (is_small_table(lines, last_guess, words_1, cols_1, words_2, cols_2, &indentation.vertical_limit, &limit_1)) { // copy match into permenant html_table if (indentation.columns != 0) { free(indentation.columns); } if (debug_table_on) { display_columns(start->text_string, "[x] last_guess", last_guess); } add_column_gaps(last_guess); if (debug_table_on) { display_columns(start->text_string, "[g] last_guess", last_guess); } /* * +1 for the potential header_margin * +1 for null */ indentation.no_of_columns = count_columns(last_guess); indentation.columns = (struct text_defn *)malloc((indentation.no_of_columns+2)*sizeof(struct text_defn)); i=0; while (i<=indentation.no_of_columns) { indentation.columns[i].left = last_guess[i].left; indentation.columns[i].right = last_guess[i].right; i++; } if (indentation.no_of_columns>0) { assign_used_columns(start); rewind_text_to(start); calculate_percentage_width(start); if (debug_table_on) { display_columns(start->text_string, "[g] indentation.columns", indentation.columns); } /* * clearly a single column 100% is not worth using a table. * Also we check to see whether the first line is sensibly * part of this table. */ if (is_a_full_width_column()) { indentation.no_of_columns = 0; free( indentation.columns ); indentation.columns = 0; } else { return( TRUE ); } } } } return( FALSE ); } /* * define_cell - creates a table cell using the percentage width. */ void html_printer::define_cell (int i) { html.put_string("\n"); } /* * column_display_word - given a left, right pair and the indentation.vertical_limit * write out html text within this region. */ void html_printer::column_display_word (int cell, int vert, int left, int right, int next) { text_glob *g=page_contents->words.get_data(); supress_sub_sup = TRUE; if (left != next) { define_cell(cell); begin_paragraph_no_height(left_alignment); while ((g != 0) && (g->minv <= vert)) { if ((left <= g->minh) && (g->minhis_raw_command) { html.put_string((char *)g->text_string); } else { translate_to_html(g); } if (postword != 0) { html.put_string(postword); } issued_newline = FALSE; } } if (page_contents->words.is_equal_to_tail()) { g = 0; } else { page_contents->words.move_right(); g=page_contents->words.get_data(); } } end_paragraph(); html.put_string("\n"); if (g != 0) { page_contents->words.move_left(); // and correct output_vpos g=page_contents->words.get_data(); output_vpos = g->minv; } } } /* * total_percentages - returns the total of all the percentages in the table. */ int html_printer::total_percentages () { int i; int sum=0; for (i=0; i\n"); } /* * end_table - finishes off a table. */ void html_printer::end_table (void) { html.put_string("\n"); indentation.no_of_columns = 0; restore_paragraph(); supress_sub_sup = TRUE; } /* * is_in_table - returns TRUE if we are inside an html table. */ int html_printer::is_in_table (void) { return( indentation.no_of_columns != 0 ); } /* * column_calculate_right_margin - scan through the column and find the right most margin */ int html_printer::column_calculate_right_margin (int left, int right) { if (left == right) { return( right ); } else { int rightmost =-1; int count = 0; text_glob *start = page_contents->words.get_data(); text_glob *g = start; while ((g != 0) && (g->minv <= indentation.vertical_limit)) { if ((left <= g->minh) && (g->minhtext_string, g->maxh); fflush(stderr); } if (g->maxh == rightmost) { count++; } else if (g->maxh > rightmost) { count = 1; rightmost = g->maxh; } if (g->maxh > right) { if (debug_on) { fprintf(stderr, "problem as right word = %s %d [%d..%d]\n", g->text_string, right, g->minh, g->maxh); fflush(stderr); // stop(); } } } page_contents->words.move_right(); if (page_contents->words.is_equal_to_head()) { g = 0; page_contents->words.start_from_tail(); } else { g=page_contents->words.get_data(); } } rewind_text_to(start); if (rightmost == -1) { return( right ); // no words in this column } else { return( rightmost ); } } } /* * column_calculate_left_margin - scan through the column and find the left most margin */ int html_printer::column_calculate_left_margin (int left, int right) { if (left == right) { return( left ); } else { int leftmost=right; text_glob *start = page_contents->words.get_data(); text_glob *g = start; while ((g != 0) && (g->minv <= indentation.vertical_limit)) { if ((left <= g->minh) && (g->minhminh, leftmost); } page_contents->words.move_right(); if (page_contents->words.is_equal_to_head()) { g = 0; page_contents->words.start_from_tail(); } else { g=page_contents->words.get_data(); } } rewind_text_to(start); if (leftmost == right) { return( left ); // no words in this column } else { return( leftmost ); } } } /* * find_column_index - returns the index to the column in which glob, t, exists. */ int html_printer::find_column_index_in_line (text_glob *t, text_defn *line) { int i=0; while ((line != 0) && ((line[i].right != 0) || (line[i].right != 0)) && (! ((line[i].left<=t->minh) && (line[i].right>t->minh)))) { i++; } return( i ); } /* * find_column_index - returns the index to the column in which glob, t, exists. */ int html_printer::find_column_index (text_glob *t) { int i=0; while ((iminh) && (indentation.columns[i].right>t->minh)))) { i++; } return( i ); } /* * determine_row_limit - checks each row to see if there is a gap in a cell. * We return the vertical position after the empty cell * at the start of the next line. */ int html_printer::determine_row_limit (text_glob *start, int v) { text_glob *t; int i; int vpos, last, prev; text_glob *is_gap[MAX_WORDS_PER_LINE]; text_glob zero(&start->text_style, 0, 0, 0, 0, 0, 0, 0, 0); #if 1 if ((v == -1) && (strcmp(start->text_string, "CASE") == 0)) { stop(); } #endif if (v >= indentation.vertical_limit) { return( v+1 ); } else { /* * initially we start with all gaps in our table * after a gap we start a new row * here we set the gap array to the previous line */ if (v>=0) { t = page_contents->words.get_data(); if (t->minv < v) { do { page_contents->words.move_right(); t = page_contents->words.get_data(); } while ((! page_contents->words.is_equal_to_head()) && (t->minv <= v)); } } if (page_contents->words.is_equal_to_head()) { t = &zero; } else { page_contents->words.move_left(); t = page_contents->words.get_data(); } prev = t->minv; for (i=0; iwords.is_equal_to_tail()) { rewind_text_to(start); return( indentation.vertical_limit ); } else { page_contents->words.move_right(); } t = page_contents->words.get_data(); vpos = t->minv; // now check each row for a gap do { last = vpos; vpos = t->minv; if (vpos > indentation.vertical_limit) { // we have reached the end of the table, quit rewind_text_to(start); return( indentation.vertical_limit ); } i = find_column_index(t); if (i>=indentation.no_of_columns) { error("find_column_index has failed"); stop(); } else { if (! is_on_same_line(t, last)) { prev = last; } if ((! is_on_same_line(is_gap[i], vpos)) && (! is_on_same_line(is_gap[i], prev)) && (indentation.columns[i].is_used)) { // no word on previous line - must be a gap - force alignment of row rewind_text_to(start); return( prev ); } is_gap[i] = t; } page_contents->words.move_right(); t = page_contents->words.get_data(); } while ((! page_contents->words.is_equal_to_head()) && (vpos < indentation.vertical_limit) && (vpos >= last)); page_contents->words.move_left(); t = page_contents->words.get_data(); rewind_text_to(start); return( indentation.vertical_limit ); } } /* * assign_used_columns - sets the is_used field of the column array of records. */ void html_printer::assign_used_columns (text_glob *start) { text_glob *t = start; int i; for (i=0; iwords.is_empty()) { do { i = find_column_index(t); if (indentation.columns[i].right != 0) { if (debug_table_on) { fprintf(stderr, "[%s] in column %d at %d..%d limit %d\n", t->text_string, i, t->minv, t->maxv, indentation.vertical_limit); fflush(stderr); } indentation.columns[i].is_used = TRUE; } page_contents->words.move_right(); t = page_contents->words.get_data(); } while ((t->minvwords.is_equal_to_head())); } if (debug_table_on) { for (i=0; i ", indentation.columns[i].left, indentation.columns[i].right, indentation.columns[i].is_used); } fprintf(stderr, "\n"); fflush(stderr); } } /* * adjust_margin_percentages - so far we have ignored the header_indent * and just considered left_margin_indent..right_margin_indent. * (We do this since we can assume 100% is total width for main text). * However as header_indent can be < left_margin_indent we need to * recalculate the real percentages in the light of the extended width. */ void html_printer::adjust_margin_percentages (void) { if ((header_indent < left_margin_indent) && (header_indent != -1)) { /* * recalculation necessary */ int i=0; while (i0) { int i; int left, right; int limit=-1; start_table(); rewind_text_to(start); count_right_hits(indentation.columns, indentation.no_of_columns); rewind_text_to(start); do { limit = determine_row_limit(start, limit); // find the bottom of the next row html.put_string("\n"); i=0; start = page_contents->words.get_data(); while (iminv; output_hpos = indentation.columns[i].left; // and display each column until limit right = column_calculate_right_margin(indentation.columns[i].left, indentation.columns[i].right); left = column_calculate_left_margin(indentation.columns[i].left, indentation.columns[i].right); if (right>indentation.columns[i].right) { if (debug_on) { fprintf(stderr, "assert calculated right column edge is greater than column\n"); fflush(stderr); // stop(); } } if (lefttext_string); fflush(stderr); // stop(); } } else { indentation.wrap_margin = TRUE; } column_display_word(i, limit, left, right, indentation.columns[i].right); i++; } if (page_contents->words.is_equal_to_tail()) { start = 0; } else { page_contents->words.sub_move_right(); if (page_contents->words.is_empty()) { start = 0; } else { start = page_contents->words.get_data(); } } html.put_string("\n"); } while (((limit < indentation.vertical_limit) && (start != 0) && (! page_contents->words.is_empty())) || (limit == -1)); end_table(); if (start == 0) { // finished page remove all words page_contents->words.start_from_head(); while (! page_contents->words.is_empty()) { page_contents->words.sub_move_right(); } } else if (! page_contents->words.is_empty()) { page_contents->words.move_left(); } } } /* * write_centered_line - generates a line of centered text. */ void html_printer::write_centered_line (text_glob *g) { int current_vpos=g->minv; move_vertical(g, center_alignment); header.written_header = FALSE; supress_sub_sup = TRUE; output_vpos = g->minv; output_hpos = g->minh; do { char *postword=html_position_text(g, left_margin_indent, right_margin_indent); if (! header.written_header) { if (g->is_raw_command) { html.put_string((char *)g->text_string); } else { translate_to_html(g); } if (postword != 0) { html.put_string(postword); } need_one_newline = TRUE; issued_newline = FALSE; } page_contents->words.move_right(); g = page_contents->words.get_data(); } while ((! page_contents->words.is_equal_to_head()) && (is_on_same_line(g, current_vpos))); page_contents->words.move_left(); // so when we move right we land on the word following this centered line need_one_newline = TRUE; } /* * is_in_middle - returns TRUE if the text defn, t, is in the middle of the page. */ int html_printer::is_in_middle (int left, int right) { return( abs(abs(left-left_margin_indent) - abs(right_margin_indent-right)) <= CENTER_TOLERANCE ); } /* * single_centered_line - returns TRUE if first is a centered line with a different * margin to second. */ int html_printer::single_centered_line (text_defn *first, text_defn *second, text_glob *g) { return( ((count_columns(first) == 1) && (first[0].left != left_margin_indent) && (first[0].left != second[0].left) && is_in_middle(first->left, first->right)) ); } /* * check_able_to_use_center - returns TRUE if we can see a centered line. */ int html_printer::check_able_to_use_center (text_glob *g) { if (auto_on && table_on && ((! is_on_same_line(g, output_vpos)) || issued_newline) && (! using_table_for_indent())) { // we are allowed to check for centered line // first check to see whether we might be looking at a set of columns struct text_defn last_guess[MAX_WORDS_PER_LINE]; struct text_defn last_words[MAX_WORDS_PER_LINE]; collect_columns(last_words, last_guess, 0, 0, MAX_WORDS_PER_LINE); rewind_text_to(g); if ((count_columns(last_guess) == 1) && (is_in_middle(last_guess[0].left, last_guess[0].right))) { write_centered_line(g); return( TRUE ); } } return( FALSE ); } /* * check_able_to_use_table - examines forthcoming text to see whether we can * better format it by using an html transparent table. */ int html_printer::check_able_to_use_table (text_glob *g) { if (auto_on && ((! is_on_same_line(g, output_vpos)) || issued_newline) && (! using_table_for_indent())) { // we are allowed to check for table if ((output_hpos != right_margin_indent) && (found_use_for_table(g))) { foreach_column_include_text(g); return( TRUE ); } } return( FALSE ); } /* * move_vertical - if we are using html auto formatting then decide whether to * break the line via a
or a

sequence. */ void html_printer::move_vertical (text_glob *g, paragraph_type p) { int r = font::res; int height = (g->text_style.point_size+2)*r/72; // --fixme-- we always assume VS is PS+2 (could do better) int temp_vpos; if (auto_on) { if ((more_than_line_break(output_vpos, g->minv, height)) || (p != current_paragraph->para_type)) { end_paragraph(); begin_paragraph(p); } else { html_newline(); } } else { if (output_vpos == -1) { temp_vpos = g->minv; } else { temp_vpos = output_vpos; } force_begin_paragraph(); if (need_one_newline) { html_newline(); temp_vpos += height; } else { need_one_newline = TRUE; } while ((temp_vpos < g->minv) && (more_than_line_break(temp_vpos, g->minv, height))) { html_newline(); temp_vpos += height; } } } /* * emit_space - emits a space within html, it checks for the font type and * will change font depending upon, g. Courier spaces are larger * than roman so we need consistancy when changing between them. */ void html_printer::emit_space (text_glob *g, int force_space) { if (! current_paragraph->need_paragraph) { // only generate a space if we have written a word - as html will ignore it otherwise if ((output_style != g->text_style) && (g->text_style.f != 0)) { terminate_current_font(); } if (force_space || (g->minh > output_hpos)) { html.put_string(" "); } change_font(g, TRUE); } } /* * html_position_text - determine whether the text is subscript/superscript/normal * or a header. */ char *html_printer::html_position_text (text_glob *g, int left_margin, int right_margin) { char *postword=0; begin_paragraph(left_alignment); if ((! header.written_header) && (is_on_same_line(g, output_vpos) || pretend_is_on_same_line(g, left_margin, right_margin))) { /* * check whether we should supress superscripts and subscripts. * I guess we might be able to do better by examining text on this line * --fixme-- */ if ((! is_on_same_line(g, output_vpos)) && (pretend_is_on_same_line(g, left_margin, right_margin))) { supress_sub_sup = TRUE; } header.written_header = FALSE; force_begin_paragraph(); // check whether we need to insert white space between words on 'same' line if (pretend_is_on_same_line(g, left_margin, right_margin)) { emit_space(g, TRUE); } // check whether the font was reset after generating an image if (output_style.f == 0) { change_font(g, TRUE); } if (looks_like_subscript(g)) { g->text_style.point_size = output_style.point_size; g->minv = output_vpos; // this ensures that output_vpos doesn't alter // which allows multiple subscripted words move_horizontal(g, left_margin); html.put_string(""); postword = ""; } else if (looks_like_superscript(g)) { g->text_style.point_size = output_style.point_size; g->minv = output_vpos; move_horizontal(g, left_margin); html.put_string(""); postword = ""; } else { move_horizontal(g, left_margin); } supress_sub_sup = FALSE; } else { // we have found a new line if (! header.written_header) { move_vertical(g, left_alignment); } header.written_header = FALSE; if (processed_header(g)) { // we must not alter output_vpos as we have peeped at the next word // and set vpos to this - to ensure we do not generate a
after // a heading. (The html heading automatically generates a line break) output_hpos = left_margin; return( postword ); } else { force_begin_paragraph(); if ((! is_in_table()) && (margin_on)) { make_html_indent(left_margin); } if (g->minh-left_margin != 0) { make_html_indent(g->minh-left_margin); } change_font(g, TRUE); supress_sub_sup = FALSE; } } output_vpos = g->minv; output_hpos = g->maxh; return( postword ); } int html_printer::html_position_region (void) { int r = font::res; int height = output_style.point_size*r/72; int temp_vpos; int is_center = FALSE; if (output_style.point_size != 0) { if (output_vpos != start_region_vpos) { // graphic starts on a different line if (output_vpos == -1) { temp_vpos = start_region_vpos; } else { temp_vpos = output_vpos; } supress_sub_sup = TRUE; if (need_one_newline) { html_newline(); temp_vpos += height; } else { need_one_newline = TRUE; } while ((temp_vpos < start_region_vpos) && (more_than_line_break(temp_vpos, start_region_vpos, height))) { html_newline(); temp_vpos += height; } } } if (auto_on && (is_in_middle(start_region_hpos, end_region_hpos))) { is_center = TRUE; } else { if (start_region_hpos > get_left()) { make_html_indent(start_region_hpos-get_left()); } } output_vpos = start_region_vpos; output_hpos = start_region_hpos; return( is_center ); } /* * gs_x - translate and scale the x axis */ int html_printer::gs_x (int x) { x += IMAGE_BOARDER_PIXELS/2; return((x-start_region_hpos)*postscript_res/font::res); } /* * gs_y - translate and scale the y axis */ int html_printer::gs_y (int y) { int yoffset=((int)(A4_PAGE_LENGTH*(double)font::res))-end_region_vpos; y += IMAGE_BOARDER_PIXELS/2; return( (y+yoffset)*postscript_res/font::res ); } void html_printer::troff_position_text (text_glob *g) { change_font(g, FALSE); troff.put_string("V"); troff.put_number(gs_y(g->maxv)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->minh)); troff.put_string("\n"); } void html_printer::troff_change_font (const char *fontname, int size, int font_no) { troff.put_string("x font "); troff.put_number(font_no); troff.put_string(" "); troff.put_string(fontname); troff.put_string("\nf"); troff.put_number(font_no); troff.put_string("\ns"); troff.put_number(size*1000); troff.put_string("\n"); } void html_printer::set_style(const style &sty) { #if 0 const char *fontname = sty.f->get_name(); if (fontname == 0) fatal("no internalname specified for font"); change_font(fontname, (font::res/(72*font::sizescale))*sty.point_size); #endif } void html_printer::end_of_line() { flush_sbuf(); output_hpos = -1; } void html_printer::html_display_word (text_glob *g) { #if 0 if (strcmp(g->text_string, "ot") == 0) { stop(); } #endif if (! check_able_to_use_table(g)) { char *postword=html_position_text(g, left_margin_indent, right_margin_indent); if (! header.written_header) { if (g->is_raw_command) { html.put_string((char *)g->text_string); } else { translate_to_html(g); } if (postword != 0) { html.put_string(postword); } need_one_newline = TRUE; issued_newline = FALSE; } } } void html_printer::troff_display_word (text_glob *g) { troff_position_text(g); if (g->is_raw_command) { int l=strlen((char *)g->text_string); if (l == 1) { troff.put_string("c"); troff.put_string((char *)g->text_string); troff.put_string("\n"); } else if (l > 1) { troff.put_string("C"); troff.put_troffps_char((char *)g->text_string); troff.put_string("\n"); } } else { troff_position_text(g); troff.put_string("t"); troff.put_translated_string((const char *)g->text_string); troff.put_string("\n"); } } void html_printer::display_word (text_glob *g, int is_to_html) { if (is_to_html) { html_display_word(g); } else if ((g->is_raw_command) && (g->is_html_command)) { // found a raw html command inside a graphic glob. // We should emit the command to the html device, but of course we // cannot place it correctly as we are dealing with troff words. // Remember output_vpos will refer to troff and not html. html.put_string((char *)g->text_string); } else { troff_display_word(g); } } /* * translate_to_html - translates a textual string into html text */ void html_printer::translate_to_html (text_glob *g) { char buf[MAX_STRING_LENGTH]; str_translate_to_html(g->text_style.f, buf, MAX_STRING_LENGTH, g->text_string, g->text_length, TRUE); html.put_string(buf); } /* * html_knows_about - given a character name, troff, return TRUE * if we know how to display this character using * html unicode. */ int html_printer::html_knows_about (char *troff) { // --fixme-- needs to have similar code as above return( FALSE ); } /* * display_fill - generates a troff format fill command */ void html_printer::display_fill (graphic_glob *g) { troff.put_string("Df ") ; troff.put_number(g->fill); troff.put_string(" 0\n"); } /* * display_line - displays a line using troff format */ void html_printer::display_line (graphic_glob *g, int is_to_html) { if (is_to_html) { fatal("cannot emit lines in html"); } if (g->code == 'l') { // straight line troff.put_string("V"); troff.put_number(gs_y(g->point[0].y)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->point[0].x)); troff.put_string("\n"); display_fill(g); troff.put_string("Dl "); troff.put_number((g->point[1].x-g->point[0].x)*postscript_res/font::res); troff.put_string(" "); troff.put_number((g->point[1].y-g->point[0].y)*postscript_res/font::res); troff.put_string("\n"); // printf("line %c %d %d %d %d size %d\n", (char)g->code, g->point[0].x, g->point[0].y, // g->point[1].x, g->point[1].y, g->size); } else if ((g->code == 'c') || (g->code == 'C')) { // circle int xradius = (g->maxh - g->minh) / 2; int yradius = (g->maxv - g->minv) / 2; // center of circle or elipse troff.put_string("V"); troff.put_number(gs_y(g->minv+yradius)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->minh)); troff.put_string("\n"); display_fill(g); if (g->code == 'c') { troff.put_string("Dc "); } else { troff.put_string("DC "); } troff.put_number(xradius*2*postscript_res/font::res); troff.put_string("\n"); } else if ((g->code == 'e') || (g->code == 'E')) { // ellipse int xradius = (g->maxh - g->minh) / 2; int yradius = (g->maxv - g->minv) / 2; // center of elipse - this is untested troff.put_string("V"); troff.put_number(gs_y(g->minv+yradius)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->minh)); troff.put_string("\n"); display_fill(g); if (g->code == 'e') { troff.put_string("De "); } else { troff.put_string("DE "); } troff.put_number(xradius*2*postscript_res/font::res); troff.put_string(" "); troff.put_number(yradius*2*postscript_res/font::res); troff.put_string("\n"); } else if ((g->code == 'p') || (g->code == 'P')) { // polygon troff.put_string("V"); troff.put_number(gs_y(g->yc)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->xc)); troff.put_string("\n"); display_fill(g); if (g->code == 'p') { troff.put_string("Dp"); } else { troff.put_string("DP"); } int i; int xc=g->xc; int yc=g->yc; for (i=0; inopoints; i++) { troff.put_string(" "); troff.put_number((g->point[i].x-xc)*postscript_res/font::res); troff.put_string(" "); troff.put_number((g->point[i].y-yc)*postscript_res/font::res); xc = g->point[i].x; yc = g->point[i].y; } troff.put_string("\n"); } else if (g->code == 'a') { // arc troff.put_string("V"); troff.put_number(gs_y(g->yc)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->xc)); troff.put_string("\n"); display_fill(g); troff.put_string("Da"); int i; for (i=0; inopoints; i++) { troff.put_string(" "); troff.put_number(g->point[i].x*postscript_res/font::res); troff.put_string(" "); troff.put_number(g->point[i].y*postscript_res/font::res); } troff.put_string("\n"); } else if (g->code == '~') { // spline troff.put_string("V"); troff.put_number(gs_y(g->yc)); troff.put_string("\n"); troff.put_string("H"); troff.put_number(gs_x(g->xc)); troff.put_string("\n"); display_fill(g); troff.put_string("D~"); int i; int xc=g->xc; int yc=g->yc; for (i=0; inopoints; i++) { troff.put_string(" "); troff.put_number((g->point[i].x-xc)*postscript_res/font::res); troff.put_string(" "); troff.put_number((g->point[i].y-yc)*postscript_res/font::res); xc = g->point[i].x; yc = g->point[i].y; } troff.put_string("\n"); } } /* * flush_sbuf - flushes the current sbuf into the list of glyphs. */ void html_printer::flush_sbuf() { if (sbuf_len > 0) { int r=font::res; // resolution of the device set_style(sbuf_style); page_contents->add(&sbuf_style, sbuf, sbuf_len, 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; sbuf_len = 0; sbuf_dmark_hpos = -1; } } void html_printer::set_line_thickness(const environment *env) { line_thickness = env->size; printf("line thickness = %d\n", line_thickness); } void html_printer::draw(int code, int *p, int np, const environment *env) { switch (code) { case 'l': if (np == 2) { page_contents->add_line(code, env->hpos, env->vpos, env->hpos+p[0], env->vpos+p[1], env->size, fill); } else { error("2 arguments required for line"); } break; case 't': { if (np == 0) { 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': // fall through case 'p': { if (np & 1) { error("even number of arguments required for polygon"); break; } if (np == 0) { error("no arguments for polygon"); break; } // firstly lets add our current position to polygon int oh=env->hpos; int ov=env->vpos; int i=0; while (iadd_polygon(code, np, p, env->hpos, env->vpos, env->size, fill); } break; case 'E': // fall through case 'e': if (np != 2) { error("2 arguments required for ellipse"); break; } page_contents->add_line(code, env->hpos, env->vpos-p[1]/2, env->hpos+p[0], env->vpos+p[1]/2, env->size, fill); break; case 'C': // fill circle case 'c': { // troff adds an extra argument to C if (np != 1 && !(code == 'C' && np == 2)) { error("1 argument required for circle"); break; } page_contents->add_line(code, env->hpos, env->vpos-p[0]/2, env->hpos+p[0], env->vpos+p[0]/2, env->size, fill); } break; case 'a': { if (np == 4) { double c[2]; if (adjust_arc_center(p, c)) { page_contents->add_arc('a', env->hpos, env->vpos, p, c, env->size, fill); } else { // a straignt line page_contents->add_line('l', env->hpos, env->vpos, p[0]+p[2], p[1]+p[3], env->size, fill); } } else { error("4 arguments required for arc"); } } break; case '~': { if (np & 1) { error("even number of arguments required for spline"); break; } if (np == 0) { error("no arguments for spline"); break; } // firstly lets add our current position to spline int oh=env->hpos; int ov=env->vpos; int i=0; while (iadd_spline('~', env->hpos, env->vpos, np, p, env->size, fill); } break; case 'f': { if (np != 1 && np != 2) { error("1 argument required for fill"); break; } fill = p[0]; if (fill < 0 || fill > FILL_MAX) { // This means fill with the current color. fill = FILL_MAX + 1; } break; } default: error("unrecognised drawing command `%1'", char(code)); break; } } void html_printer::begin_page(int n) { page_number = n; html.begin_comment("Page: ").comment_arg(i_to_a(page_number)).end_comment();; no_of_printed_pages++; output_style.f = 0; output_space_code = 32; output_draw_point_size = -1; output_line_thickness = -1; output_hpos = -1; output_vpos = -1; } void testing (text_glob *g) {} void html_printer::flush_graphic (void) { graphic_glob g; graphic_level = 0; page_contents->is_in_graphic = FALSE; g.minv = -1; g.maxv = -1; calculate_region_range(&g); if (g.minv != -1) { page_contents->make_new_region(&g); } move_region_to_page(); } void html_printer::end_page(int) { flush_sbuf(); flush_graphic(); flush_page(); } font *html_printer::make_font(const char *nm) { return html_font::load_html_font(nm); } html_printer::~html_printer() { if (fseek(tempfp, 0L, 0) < 0) fatal("fseek on temporary file failed"); html.set_file(stdout); fputs("\n", stdout); fputs("\n", stdout); fputs("\n", stdout); write_title(TRUE); fputs("\n", stdout); fputs("\n", stdout); write_title(FALSE); header.write_headings(stdout); { extern const char *Version_string; html.begin_comment("Creator : ") .comment_arg("groff ") .comment_arg("version ") .comment_arg(Version_string) .end_comment(); } { #ifdef LONG_FOR_TIME_T long #else time_t #endif t = time(0); html.begin_comment("CreationDate: ") .comment_arg(ctime(&t)) .end_comment(); } html.begin_comment("Total number of pages: ").comment_arg(i_to_a(no_of_printed_pages)).end_comment(); html.end_line(); html.copy_file(tempfp); fputs("\n", stdout); fputs("\n", stdout); fclose(tempfp); } /* * calculate_region_range - calculates the vertical range for words and lines * within the region lists. */ void html_printer::calculate_region_range (graphic_glob *r) { text_glob *w; graphic_glob *g; if (! page_contents->region_lines.is_empty()) { page_contents->region_lines.start_from_head(); do { g = page_contents->region_lines.get_data(); if ((r->minv == -1) || (g->minv < r->minv)) { r->minv = g->minv; } if ((r->maxv == -1) || (g->maxv > r->maxv)) { r->maxv = g->maxv; } page_contents->region_lines.move_right(); } while (! page_contents->region_lines.is_equal_to_head()); } if (! page_contents->region_words.is_empty()) { page_contents->region_words.start_from_head(); do { w = page_contents->region_words.get_data(); if ((r->minv == -1) || (w->minv < r->minv)) { r->minv = w->minv; } if ((r->maxv == -1) || (w->maxv > r->maxv)) { r->maxv = w->maxv; } page_contents->region_words.move_right(); } while (! page_contents->region_words.is_equal_to_head()); } } /* * move_region_to_page - moves lines and words held in the temporary region * list to the page list. */ void html_printer::move_region_to_page (void) { text_glob *w; graphic_glob *g; page_contents->region_lines.start_from_head(); while (! page_contents->region_lines.is_empty()) { g = page_contents->region_lines.get_data(); // remove from our temporary region list page_contents->lines.add(g); // and add to the page list page_contents->region_lines.sub_move_right(); } page_contents->region_words.start_from_head(); while (! page_contents->region_words.is_empty()) { w = page_contents->region_words.get_data(); // remove from our temporary region list page_contents->words.add(w); // and add to the page list page_contents->region_words.sub_move_right(); } } /* * is_graphic_start - returns TRUE if the start of table, pic, eqn was seen. */ int is_graphic_start (char *s) { return( (strcmp(s, "graphic-start") == 0) || ((strcmp(s, "table-start") == 0) && (table_image_on)) ); } /* * is_graphic_end - return TRUE if the end of a table, pic, eqn was seen. */ int is_graphic_end (char *s) { return( (strcmp(s, "graphic-end") == 0) || ((strcmp(s, "table-end") == 0) && (table_image_on)) ); } /* * special - handle all x X requests from troff. For grohtml they allow users * to pass raw html commands, turn auto linked headings off/on and * also allow tbl, eqn & pic say what commands they have generated. */ void html_printer::special(char *s, const environment *env) { if (s != 0) { if (is_graphic_start(s)) { graphic_level++; if (graphic_level == 1) { page_contents->is_in_graphic = TRUE; // add words and lines to temporary region lists } } else if (is_graphic_end(s) && (graphic_level > 0)) { graphic_level--; if (graphic_level == 0) { flush_graphic(); } } else if (strncmp(s, "html:", 5) == 0) { int r=font::res; // resolution of the device char buf[MAX_STRING_LENGTH]; font *f=sbuf_style.f; if (f == NULL) { int found=FALSE; f = font::load_font("TR", &found); } str_translate_to_html(f, buf, MAX_STRING_LENGTH, &s[5], strlen(s)-5, FALSE); page_contents->add_html_command(&sbuf_style, buf, strlen(buf), // need to pass rest of string through to html output during flush env->vpos-env->size*r/72, env->hpos, env->vpos , env->hpos); // assume that the html command has no width, if it does then we hopefully troff // will have fudged this in a macro and requested that the formatting move right by // the appropriate width } else if (strncmp(s, "index:", 6) == 0) { cutoff_heading = atoi(&s[6]); } } } void set_image_type (char *type) { if (strcmp(type, "gif") == 0) { image_type = gif; } else if (strcmp(type, "png") == 0) { image_type = png; image_device = "png256"; } else if (strncmp(type, "png", 3) == 0) { image_type = png; image_device = type; } } printer *make_printer() { return new html_printer; } static void usage(); int main(int argc, char **argv) { program_name = argv[0]; static char stderr_buf[BUFSIZ]; setbuf(stderr, stderr_buf); int c; while ((c = getopt(argc, argv, "F:atTvdgmx?I:r:")) != EOF) switch(c) { case 'v': { extern const char *Version_string; fprintf(stderr, "grohtml version %s\n", Version_string); fflush(stderr); break; } case 'a': auto_on = FALSE; break; case 't': table_on = FALSE; break; case 'T': table_image_on = FALSE; break; case 'F': font::command_line_font_dir(optarg); break; case 'I': // user specifying the type of images we should generate set_image_type(optarg); break; case 'r': // resolution (dots per inch for an image) image_res = atoi(optarg); break; case 'd': // debugging on debug_on = TRUE; break; case 'x': debug_table_on = TRUE; break; case 'g': // do not guess title and headings guess_on = FALSE; break; case 'm': // leave margins alone margin_on = TRUE; break; case '?': usage(); break; default: assert(0); } if (optind >= argc) { do_file("-"); } else { for (int i = optind; i < argc; i++) do_file(argv[i]); } delete pr; return 0; } static void usage() { fprintf(stderr, "usage: %s [-avdgmt?] [-r resolution] [-F dir] [-I imagetype] [files ...]\n", program_name); exit(1); }