/contrib/groff/src/devices/grohtml/post-html.cpp
https://bitbucket.org/freebsd/freebsd-head/ · C++ · 5053 lines · 3641 code · 623 blank · 789 comment · 1040 complexity · d2253a29ccdb5e77327c9607fe5b9b93 MD5 · raw file
Large files are truncated click here to view the full file
- // -*- C++ -*-
- /* Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005
- * Free Software Foundation, Inc.
- *
- * Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp
- * but it owes a huge amount of ideas and raw code from
- * James Clark (jjc@jclark.com) grops/ps.cpp.
- */
- /*
- This file is part of groff.
- 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, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
- #include "driver.h"
- #include "stringclass.h"
- #include "cset.h"
- #include "html.h"
- #include "html-text.h"
- #include "html-table.h"
- #include <time.h>
- #ifdef HAVE_UNISTD_H
- #include <unistd.h>
- #endif
- #include <stdio.h>
- #include <fcntl.h>
- #include <string.h>
- extern "C" const char *Version_string;
- #if !defined(TRUE)
- # define TRUE (1==1)
- #endif
- #if !defined(FALSE)
- # define FALSE (1==0)
- #endif
- #define MAX_LINE_LENGTH 60 /* maximum characters we want in a line */
- #define SIZE_INCREMENT 2 /* font size increment <big> = +2 */
- #define CENTER_TOLERANCE 2 /* how many pixels off center do we allow */
- #define ANCHOR_TEMPLATE "heading" /* if simple anchor is set we use this */
- #define UNICODE_DESC_START 0x80 /* all character entities above this are */
- /* either encoded by their glyph names or if */
- /* there is no name then we use &#nnn; */
- typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT;
- typedef enum {col_tag, tab_tag, tab0_tag, none} colType;
- #undef DEBUG_TABLES
- // #define DEBUG_TABLES
- /*
- * prototypes
- */
- char *get_html_translation (font *f, const string &name);
- int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single);
- static int auto_links = TRUE; /* by default we enable automatic links at */
- /* top of the document. */
- static int auto_rule = TRUE; /* by default we enable an automatic rule */
- /* at the top and bottom of the document */
- static int simple_anchors = FALSE; /* default to anchors with heading text */
- static int manufacture_headings = FALSE; /* default is to use the Hn html headings, */
- /* rather than manufacture our own. */
- static color *default_background = NULL; /* has user requested initial bg color? */
- static string job_name; /* if set then the output is split into */
- /* multiple files with `job_name'-%d.html */
- static int multiple_files = FALSE; /* must we the output be divided into */
- /* multiple html files, one for each */
- /* heading? */
- static int base_point_size = 0; /* which troff font size maps onto html */
- /* size 3? */
- static int split_level = 2; /* what heading level to split at? */
- static string head_info; /* user supplied information to be placed */
- /* into <head> </head> */
- /*
- * start with a few favorites
- */
- void stop () {}
- static int min (int a, int b)
- {
- if (a < b)
- return a;
- else
- return b;
- }
- static int max (int a, int b)
- {
- if (a > b)
- return a;
- else
- return b;
- }
- /*
- * is_intersection - returns TRUE if range a1..a2 intersects with b1..b2
- */
- static int is_intersection (int a1, int a2, int b1, int b2)
- {
- // easier to prove NOT outside limits
- return ! ((a1 > b2) || (a2 < b1));
- }
- /*
- * is_digit - returns TRUE if character, ch, is a digit.
- */
- static int is_digit (char ch)
- {
- return (ch >= '0') && (ch <= '9');
- }
- /*
- * the classes and methods for maintaining a list of files.
- */
- struct file {
- FILE *fp;
- file *next;
- int new_output_file;
- int require_links;
- string output_file_name;
- file (FILE *f);
- };
- /*
- * file - initialize all fields to NULL
- */
- file::file (FILE *f)
- : fp(f), next(NULL), new_output_file(FALSE),
- require_links(FALSE), output_file_name("")
- {
- }
- class files {
- public:
- files ();
- FILE *get_file (void);
- void start_of_list (void);
- void move_next (void);
- void add_new_file (FILE *f);
- void set_file_name (string name);
- void set_links_required (void);
- int are_links_required (void);
- int is_new_output_file (void);
- string file_name (void);
- string next_file_name (void);
- private:
- file *head;
- file *tail;
- file *ptr;
- };
- /*
- * files - create an empty list of files.
- */
- files::files ()
- : head(NULL), tail(NULL), ptr(NULL)
- {
- }
- /*
- * get_file - returns the FILE associated with ptr.
- */
- FILE *files::get_file (void)
- {
- if (ptr)
- return ptr->fp;
- else
- return NULL;
- }
- /*
- * start_of_list - reset the ptr to the start of the list.
- */
- void files::start_of_list (void)
- {
- ptr = head;
- }
- /*
- * move_next - moves the ptr to the next element on the list.
- */
- void files::move_next (void)
- {
- if (ptr != NULL)
- ptr = ptr->next;
- }
- /*
- * add_new_file - adds a new file, f, to the list.
- */
- void files::add_new_file (FILE *f)
- {
- if (head == NULL) {
- head = new file(f);
- tail = head;
- } else {
- tail->next = new file(f);
- tail = tail->next;
- }
- ptr = tail;
- }
- /*
- * set_file_name - sets the final file name to contain the html
- * data to name.
- */
- void files::set_file_name (string name)
- {
- if (ptr != NULL) {
- ptr->output_file_name = name;
- ptr->new_output_file = TRUE;
- }
- }
- /*
- * set_links_required - issue links when processing this component
- * of the file.
- */
- void files::set_links_required (void)
- {
- if (ptr != NULL)
- ptr->require_links = TRUE;
- }
- /*
- * are_links_required - returns TRUE if this section of the file
- * requires that links should be issued.
- */
- int files::are_links_required (void)
- {
- if (ptr != NULL)
- return ptr->require_links;
- return FALSE;
- }
- /*
- * is_new_output_file - returns TRUE if this component of the file
- * is the start of a new output file.
- */
- int files::is_new_output_file (void)
- {
- if (ptr != NULL)
- return ptr->new_output_file;
- return FALSE;
- }
- /*
- * file_name - returns the name of the file.
- */
- string files::file_name (void)
- {
- if (ptr != NULL)
- return ptr->output_file_name;
- return string("");
- }
- /*
- * next_file_name - returns the name of the next file.
- */
- string files::next_file_name (void)
- {
- if (ptr != NULL && ptr->next != NULL)
- return ptr->next->output_file_name;
- return string("");
- }
- /*
- * the class and methods for styles
- */
- struct style {
- font *f;
- int point_size;
- int font_no;
- int height;
- int slant;
- color col;
- style ();
- style (font *, int, int, int, int, color);
- int operator == (const style &) const;
- int operator != (const style &) const;
- };
- style::style()
- : f(NULL)
- {
- }
- style::style(font *p, int sz, int h, int sl, int no, color c)
- : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c)
- {
- }
- int style::operator==(const style &s) const
- {
- return (f == s.f && point_size == s.point_size
- && height == s.height && slant == s.slant && col == s.col);
- }
- int style::operator!=(const style &s) const
- {
- return !(*this == s);
- }
- /*
- * the class and methods for retaining ascii text
- */
- struct char_block {
- enum { SIZE = 256 };
- char *buffer;
- int used;
- char_block *next;
- char_block();
- char_block(int length);
- ~char_block();
- };
- char_block::char_block()
- : buffer(NULL), used(0), next(NULL)
- {
- }
- char_block::char_block(int length)
- : used(0), next(NULL)
- {
- buffer = new char[max(length, char_block::SIZE)];
- if (buffer == NULL)
- fatal("out of memory error");
- }
- char_block::~char_block()
- {
- if (buffer != NULL)
- a_delete buffer;
- }
- class char_buffer {
- public:
- char_buffer();
- ~char_buffer();
- char *add_string(const char *, unsigned int);
- char *add_string(const string &);
- private:
- char_block *head;
- char_block *tail;
- };
- char_buffer::char_buffer()
- : head(NULL), tail(NULL)
- {
- }
- char_buffer::~char_buffer()
- {
- while (head != NULL) {
- char_block *temp = head;
- head = head->next;
- delete temp;
- }
- }
- char *char_buffer::add_string (const char *s, unsigned int length)
- {
- int i=0;
- unsigned int old_used;
- if (s == NULL || length == 0)
- return NULL;
- if (tail == NULL) {
- tail = new char_block(length+1);
- head = tail;
- } else {
- if (tail->used + length+1 > char_block::SIZE) {
- tail->next = new char_block(length+1);
- tail = tail->next;
- }
- }
- 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];
- }
- char *char_buffer::add_string (const string &s)
- {
- return add_string(s.contents(), s.length());
- }
- /*
- * the classes and methods for maintaining glyph positions.
- */
- class text_glob {
- public:
- void text_glob_html (style *s, char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- void text_glob_special (style *s, char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- void text_glob_line (style *s,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal,
- int thickness);
- void text_glob_auto_image(style *s, char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- void text_glob_tag (style *s, char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
-
- text_glob (void);
- ~text_glob (void);
- int is_a_line (void);
- int is_a_tag (void);
- int is_eol (void);
- int is_auto_img (void);
- int is_br (void);
- int is_in (void);
- int is_po (void);
- int is_ti (void);
- int is_ll (void);
- int is_ce (void);
- int is_tl (void);
- int is_eo_tl (void);
- int is_eol_ce (void);
- int is_col (void);
- int is_tab (void);
- int is_tab0 (void);
- int is_ta (void);
- int is_tab_ts (void);
- int is_tab_te (void);
- int is_nf (void);
- int is_fi (void);
- int is_eo_h (void);
- int get_arg (void);
- int get_tab_args (char *align);
- void remember_table (html_table *t);
- html_table *get_table (void);
- style text_style;
- const char *text_string;
- unsigned int text_length;
- int minv, minh, maxv, maxh;
- int is_tag; // is this a .br, .sp, .tl etc
- int is_img_auto; // image created by eqn delim
- int is_special; // text has come via 'x X html:'
- int is_line; // is the command a <line>?
- int thickness; // the thickness of a line
- html_table *tab; // table description
- private:
- text_glob (style *s, const char *str, int length,
- int min_vertical , int min_horizontal,
- int max_vertical , int max_horizontal,
- bool is_troff_command,
- bool is_auto_image, bool is_special_command,
- bool is_a_line , int thickness);
- };
- text_glob::text_glob (style *s, const char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal,
- bool is_troff_command,
- bool is_auto_image, bool is_special_command,
- bool is_a_line_flag, int line_thickness)
- : text_style(*s), text_string(str), text_length(length),
- minv(min_vertical), minh(min_horizontal), maxv(max_vertical), maxh(max_horizontal),
- is_tag(is_troff_command), is_img_auto(is_auto_image), is_special(is_special_command),
- is_line(is_a_line_flag), thickness(line_thickness), tab(NULL)
- {
- }
- text_glob::text_glob ()
- : text_string(NULL), text_length(0), minv(-1), minh(-1), maxv(-1), maxh(-1),
- is_tag(FALSE), is_special(FALSE), is_line(FALSE), thickness(0), tab(NULL)
- {
- }
- text_glob::~text_glob ()
- {
- if (tab != NULL)
- delete tab;
- }
- /*
- * text_glob_html - used to place html text into the glob buffer.
- */
- void text_glob::text_glob_html (style *s, char *str, int length,
- int min_vertical , int min_horizontal,
- int max_vertical , int max_horizontal)
- {
- text_glob *g = new text_glob(s, str, length,
- min_vertical, min_horizontal, max_vertical, max_horizontal,
- FALSE, FALSE, FALSE, FALSE, 0);
- *this = *g;
- delete g;
- }
- /*
- * text_glob_html - used to place html specials into the glob buffer.
- * This text is essentially html commands coming through
- * from the macro sets, with special designated sequences of
- * characters translated into html. See add_and_encode.
- */
- void text_glob::text_glob_special (style *s, char *str, int length,
- int min_vertical , int min_horizontal,
- int max_vertical , int max_horizontal)
- {
- text_glob *g = new text_glob(s, str, length,
- min_vertical, min_horizontal, max_vertical, max_horizontal,
- FALSE, FALSE, TRUE, FALSE, 0);
- *this = *g;
- delete g;
- }
- /*
- * text_glob_line - record horizontal draw line commands.
- */
- void text_glob::text_glob_line (style *s,
- int min_vertical , int min_horizontal,
- int max_vertical , int max_horizontal,
- int thickness_value)
- {
- text_glob *g = new text_glob(s, "", 0,
- min_vertical, min_horizontal, max_vertical, max_horizontal,
- FALSE, FALSE, FALSE, TRUE, thickness_value);
- *this = *g;
- delete g;
- }
- /*
- * text_glob_auto_image - record the presence of a .auto-image tag command.
- * Used to mark that an image has been created automatically
- * by a preprocessor and (pre-grohtml/troff) combination.
- * Under some circumstances images may not be created.
- * (consider .EQ
- * delim $$
- * .EN
- * .TS
- * tab(!), center;
- * l!l.
- * $1 over x$!recripical of x
- * .TE
- *
- * the first auto-image marker is created via .EQ/.EN pair
- * and no image is created.
- * The second auto-image marker occurs at $1 over x$
- * Currently this image will not be created
- * as the whole of the table is created as an image.
- * (Once html tables are handled by grohtml this will change.
- * Shortly this will be the case).
- */
- void text_glob::text_glob_auto_image(style *s, char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal)
- {
- text_glob *g = new text_glob(s, str, length,
- min_vertical, min_horizontal, max_vertical, max_horizontal,
- TRUE, TRUE, FALSE, FALSE, 0);
- *this = *g;
- delete g;
- }
- /*
- * text_glob_tag - records a troff tag.
- */
- void text_glob::text_glob_tag (style *s, char *str, int length,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal)
- {
- text_glob *g = new text_glob(s, str, length,
- min_vertical, min_horizontal, max_vertical, max_horizontal,
- TRUE, FALSE, FALSE, FALSE, 0);
- *this = *g;
- delete g;
- }
- /*
- * is_a_line - returns TRUE if glob should be converted into an <hr>
- */
- int text_glob::is_a_line (void)
- {
- return is_line;
- }
- /*
- * is_a_tag - returns TRUE if glob contains a troff directive.
- */
- int text_glob::is_a_tag (void)
- {
- return is_tag;
- }
- /*
- * is_eol - returns TRUE if glob contains the tag eol
- */
- int text_glob::is_eol (void)
- {
- return is_tag && (strcmp(text_string, "devtag:.eol") == 0);
- }
- /*
- * is_eol_ce - returns TRUE if glob contains the tag eol.ce
- */
- int text_glob::is_eol_ce (void)
- {
- return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0);
- }
- /*
- * is_tl - returns TRUE if glob contains the tag .tl
- */
- int text_glob::is_tl (void)
- {
- return is_tag && (strcmp(text_string, "devtag:.tl") == 0);
- }
- /*
- * is_eo_tl - returns TRUE if glob contains the tag eo.tl
- */
- int text_glob::is_eo_tl (void)
- {
- return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0);
- }
- /*
- * is_nf - returns TRUE if glob contains the tag .fi 0
- */
- int text_glob::is_nf (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.fi",
- strlen("devtag:.fi")) == 0) &&
- (get_arg() == 0);
- }
- /*
- * is_fi - returns TRUE if glob contains the tag .fi 1
- */
- int text_glob::is_fi (void)
- {
- return( is_tag && (strncmp(text_string, "devtag:.fi",
- strlen("devtag:.fi")) == 0) &&
- (get_arg() == 1) );
- }
- /*
- * is_eo_h - returns TRUE if glob contains the tag .eo.h
- */
- int text_glob::is_eo_h (void)
- {
- return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0);
- }
- /*
- * is_ce - returns TRUE if glob contains the tag .ce
- */
- int text_glob::is_ce (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.ce",
- strlen("devtag:.ce")) == 0);
- }
- /*
- * is_in - returns TRUE if glob contains the tag .in
- */
- int text_glob::is_in (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.in ",
- strlen("devtag:.in ")) == 0);
- }
- /*
- * is_po - returns TRUE if glob contains the tag .po
- */
- int text_glob::is_po (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.po ",
- strlen("devtag:.po ")) == 0);
- }
- /*
- * is_ti - returns TRUE if glob contains the tag .ti
- */
- int text_glob::is_ti (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.ti ",
- strlen("devtag:.ti ")) == 0);
- }
- /*
- * is_ll - returns TRUE if glob contains the tag .ll
- */
- int text_glob::is_ll (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.ll ",
- strlen("devtag:.ll ")) == 0);
- }
- /*
- * is_col - returns TRUE if glob contains the tag .col
- */
- int text_glob::is_col (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.col",
- strlen("devtag:.col")) == 0);
- }
- /*
- * is_tab_ts - returns TRUE if glob contains the tag .tab_ts
- */
- int text_glob::is_tab_ts (void)
- {
- return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0);
- }
- /*
- * is_tab_te - returns TRUE if glob contains the tag .tab_te
- */
- int text_glob::is_tab_te (void)
- {
- return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0);
- }
- /*
- * is_ta - returns TRUE if glob contains the tag .ta
- */
- int text_glob::is_ta (void)
- {
- return is_tag && (strncmp(text_string, "devtag:.ta ",
- strlen("devtag:.ta ")) == 0);
- }
- /*
- * is_tab - returns TRUE if glob contains the tag tab
- */
- int text_glob::is_tab (void)
- {
- return is_tag && (strncmp(text_string, "devtag:tab ",
- strlen("devtag:tab ")) == 0);
- }
- /*
- * is_tab0 - returns TRUE if glob contains the tag tab0
- */
- int text_glob::is_tab0 (void)
- {
- return is_tag && (strncmp(text_string, "devtag:tab0",
- strlen("devtag:tab0")) == 0);
- }
- /*
- * is_auto_img - returns TRUE if the glob contains an automatically
- * generated image.
- */
- int text_glob::is_auto_img (void)
- {
- return is_img_auto;
- }
- /*
- * is_br - returns TRUE if the glob is a tag containing a .br
- * or an implied .br. Note that we do not include .nf or .fi
- * as grohtml will place a .br after these commands if they
- * should break the line.
- */
- int text_glob::is_br (void)
- {
- return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) ||
- (strncmp("devtag:.sp", text_string,
- strlen("devtag:.sp")) == 0));
- }
- int text_glob::get_arg (void)
- {
- if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
- const char *p = text_string;
- while ((*p != (char)0) && (!isspace(*p)))
- p++;
- while ((*p != (char)0) && (isspace(*p)))
- p++;
- if (*p == (char)0)
- return -1;
- return atoi(p);
- }
- return -1;
- }
- /*
- * get_tab_args - returns the tab position and alignment of the tab tag
- */
- int text_glob::get_tab_args (char *align)
- {
- if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
- const char *p = text_string;
- // firstly the alignment C|R|L
- while ((*p != (char)0) && (!isspace(*p)))
- p++;
- while ((*p != (char)0) && (isspace(*p)))
- p++;
- *align = *p;
- // now the int value
- while ((*p != (char)0) && (!isspace(*p)))
- p++;
- while ((*p != (char)0) && (isspace(*p)))
- p++;
- if (*p == (char)0)
- return -1;
- return atoi(p);
- }
- return -1;
- }
- /*
- * remember_table - saves table, t, in the text_glob.
- */
- void text_glob::remember_table (html_table *t)
- {
- if (tab != NULL)
- delete tab;
- tab = t;
- }
- /*
- * get_table - returns the stored table description.
- */
- html_table *text_glob::get_table (void)
- {
- return tab;
- }
- /*
- * the class and methods used to construct ordered double linked
- * lists. In a previous implementation we used templates via
- * #include "ordered-list.h", but this does assume that all C++
- * compilers can handle this feature. Pragmatically it is safer to
- * assume this is not the case.
- */
- struct element_list {
- element_list *right;
- element_list *left;
- text_glob *datum;
- int lineno;
- int minv, minh, maxv, maxh;
- element_list (text_glob *d,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- element_list ();
- ~element_list ();
- };
- element_list::element_list ()
- : right(0), left(0), datum(0), lineno(0), minv(-1), minh(-1), maxv(-1), maxh(-1)
- {
- }
- /*
- * element_list - create a list element assigning the datum and region parameters.
- */
- element_list::element_list (text_glob *in,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal)
- : right(0), left(0), datum(in), lineno(line_number),
- minv(min_vertical), minh(min_horizontal), maxv(max_vertical), maxh(max_horizontal)
- {
- }
- element_list::~element_list ()
- {
- if (datum != NULL)
- delete datum;
- }
- class list {
- public:
- list ();
- ~list ();
- int is_less (element_list *a, element_list *b);
- void add (text_glob *in,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- void sub_move_right (void);
- void move_right (void);
- void move_left (void);
- int is_empty (void);
- int is_equal_to_tail (void);
- int is_equal_to_head (void);
- void start_from_head (void);
- void start_from_tail (void);
- void insert (text_glob *in);
- void move_to (text_glob *in);
- text_glob *move_right_get_data (void);
- text_glob *move_left_get_data (void);
- text_glob *get_data (void);
- private:
- element_list *head;
- element_list *tail;
- element_list *ptr;
- };
- /*
- * list - construct an empty list.
- */
- list::list ()
- : head(NULL), tail(NULL), ptr(NULL)
- {
- }
- /*
- * ~list - destroy a complete list.
- */
- list::~list()
- {
- element_list *temp=head;
- do {
- temp = head;
- if (temp != NULL) {
- head = head->right;
- delete temp;
- }
- } while ((head != NULL) && (head != tail));
- }
- /*
- * is_less - returns TRUE if a is left of b if on the same line or
- * if a is higher up the page than b.
- */
- int list::is_less (element_list *a, element_list *b)
- {
- // was if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) {
- if (a->lineno < b->lineno) {
- return( TRUE );
- } else if (a->lineno > b->lineno) {
- return( FALSE );
- } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) {
- return( a->minh < b->minh );
- } else {
- return( a->maxv < b->maxv );
- }
- }
- /*
- * add - adds a datum to the list in the order specified by the
- * region position.
- */
- void list::add (text_glob *in, int line_number, int min_vertical, int min_horizontal, int max_vertical, int max_horizontal)
- {
- // create a new list element with datum and position fields initialized
- element_list *t = new element_list(in, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal);
- element_list *last;
- #if 0
- fprintf(stderr, "[%s %d,%d,%d,%d] ",
- in->text_string, min_vertical, min_horizontal, max_vertical, max_horizontal);
- fflush(stderr);
- #endif
- if (head == NULL) {
- head = t;
- tail = t;
- ptr = t;
- t->left = t;
- t->right = t;
- } else {
- last = tail;
- while ((last != head) && (is_less(t, last)))
- last = last->left;
- if (is_less(t, last)) {
- t->right = last;
- last->left->right = t;
- t->left = last->left;
- last->left = t;
- // now check for a new head
- if (last == head)
- head = t;
- } else {
- // add t beyond last
- t->right = last->right;
- t->left = last;
- last->right->left = t;
- last->right = t;
- // now check for a new tail
- if (last == tail)
- tail = t;
- }
- }
- }
- /*
- * sub_move_right - removes the element which is currently pointed to by ptr
- * from the list and moves ptr to the right.
- */
- void list::sub_move_right (void)
- {
- element_list *t=ptr->right;
- if (head == tail) {
- head = NULL;
- if (tail != NULL)
- delete tail;
-
- tail = NULL;
- ptr = NULL;
- } else {
- if (head == ptr)
- head = head->right;
- if (tail == ptr)
- tail = tail->left;
- ptr->left->right = ptr->right;
- ptr->right->left = ptr->left;
- ptr = t;
- }
- }
- /*
- * start_from_head - assigns ptr to the head.
- */
- void list::start_from_head (void)
- {
- ptr = head;
- }
- /*
- * start_from_tail - assigns ptr to the tail.
- */
- void list::start_from_tail (void)
- {
- ptr = tail;
- }
- /*
- * is_empty - returns TRUE if the list has no elements.
- */
- int list::is_empty (void)
- {
- return head == NULL;
- }
- /*
- * is_equal_to_tail - returns TRUE if the ptr equals the tail.
- */
- int list::is_equal_to_tail (void)
- {
- return ptr == tail;
- }
- /*
- * is_equal_to_head - returns TRUE if the ptr equals the head.
- */
- int list::is_equal_to_head (void)
- {
- return ptr == head;
- }
- /*
- * move_left - moves the ptr left.
- */
- void list::move_left (void)
- {
- ptr = ptr->left;
- }
- /*
- * move_right - moves the ptr right.
- */
- void list::move_right (void)
- {
- ptr = ptr->right;
- }
- /*
- * get_datum - returns the datum referenced via ptr.
- */
- text_glob* list::get_data (void)
- {
- return ptr->datum;
- }
- /*
- * move_right_get_data - returns the datum referenced via ptr and moves
- * ptr right.
- */
- text_glob* list::move_right_get_data (void)
- {
- ptr = ptr->right;
- if (ptr == head)
- return NULL;
- else
- return ptr->datum;
- }
- /*
- * move_left_get_data - returns the datum referenced via ptr and moves
- * ptr right.
- */
- text_glob* list::move_left_get_data (void)
- {
- ptr = ptr->left;
- if (ptr == tail)
- return NULL;
- else
- return ptr->datum;
- }
- /*
- * insert - inserts data after the current position.
- */
- void list::insert (text_glob *in)
- {
- if (is_empty())
- fatal("list must not be empty if we are inserting data");
- else {
- if (ptr == NULL)
- ptr = head;
-
- element_list *t = new element_list(in, ptr->lineno, ptr->minv, ptr->minh, ptr->maxv, ptr->maxh);
- if (ptr == tail)
- tail = t;
- ptr->right->left = t;
- t->right = ptr->right;
- ptr->right = t;
- t->left = ptr;
- }
- }
- /*
- * move_to - moves the current position to the point where data, in, exists.
- * This is an expensive method and should be used sparingly.
- */
- void list::move_to (text_glob *in)
- {
- ptr = head;
- while (ptr != tail && ptr->datum != in)
- ptr = ptr->right;
- }
- /*
- * page class and methods
- */
- class page {
- public:
- page (void);
- void add (style *s, const string &str,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- void add_tag (style *s, const string &str,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal);
- void add_and_encode (style *s, const string &str,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal,
- int is_tag);
- void add_line (style *s,
- int line_number,
- int x1, int y1, int x2, int y2,
- int thickness);
- void insert_tag (const string &str);
- void dump_page (void); // debugging method
- // and the data
- list glyphs; // position of glyphs and specials on page
- char_buffer buffer; // all characters for this page
- };
- page::page()
- {
- }
- /*
- * insert_tag - inserts a tag after the current position.
- */
- void page::insert_tag (const string &str)
- {
- if (str.length() > 0) {
- text_glob *g=new text_glob();
- text_glob *f=glyphs.get_data();
- g->text_glob_tag(&f->text_style, buffer.add_string(str), str.length(),
- f->minv, f->minh, f->maxv, f->maxh);
- glyphs.insert(g);
- }
- }
- /*
- * add - add html text to the list of glyphs.
- */
- void page::add (style *s, const string &str,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal)
- {
- if (str.length() > 0) {
- text_glob *g=new text_glob();
- g->text_glob_html(s, buffer.add_string(str), str.length(),
- min_vertical, min_horizontal, max_vertical, max_horizontal);
- glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal);
- }
- }
- /*
- * add_tag - adds a troff tag, for example: .tl .sp .br
- */
- void page::add_tag (style *s, const string &str,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal)
- {
- if (str.length() > 0) {
- text_glob *g;
- if (strncmp((str+'\0').contents(), "devtag:.auto-image",
- strlen("devtag:.auto-image")) == 0) {
- g = new text_glob();
- g->text_glob_auto_image(s, buffer.add_string(str), str.length(),
- min_vertical, min_horizontal, max_vertical, max_horizontal);
- } else {
- g = new text_glob();
- g->text_glob_tag(s, buffer.add_string(str), str.length(),
- min_vertical, min_horizontal, max_vertical, max_horizontal);
- }
- glyphs.add(g, line_number, min_vertical, min_horizontal, max_vertical, max_horizontal);
- }
- }
- /*
- * add_line - adds the <line> primitive providing that y1==y2
- */
- void page::add_line (style *s,
- int line_number,
- int x_1, int y_1, int x_2, int y_2,
- int thickness)
- {
- if (y_1 == y_2) {
- text_glob *g = new text_glob();
- g->text_glob_line(s,
- min(y_1, y_2), min(x_1, x_2),
- max(y_1, y_2), max(x_1, x_2),
- thickness);
- glyphs.add(g, line_number,
- min(y_1, y_2), min(x_1, x_2),
- max(y_1, y_2), max(x_1, x_2));
- }
- }
- /*
- * to_unicode - returns a unicode translation of int, ch.
- */
- static char *to_unicode (unsigned int ch)
- {
- static char buf[30];
- sprintf(buf, "&#%u;", ch);
- return buf;
- }
- /*
- * add_and_encode - adds a special string to the page, it translates the string
- * into html glyphs. The special string will have come from x X html:
- * and can contain troff character encodings which appear as
- * \(char\). A sequence of \\ represents \.
- * So for example we can write:
- * "cost = \(Po\)3.00 file = \\foo\\bar"
- * which is translated into:
- * "cost = £3.00 file = \foo\bar"
- */
- void page::add_and_encode (style *s, const string &str,
- int line_number,
- int min_vertical, int min_horizontal,
- int max_vertical, int max_horizontal,
- int is_tag)
- {
- string html_string;
- char *html_glyph;
- int i=0;
- if (s->f == NULL)
- return;
- while (i < str.length()) {
- if ((i+1<str.length()) && (str.substring(i, 2) == string("\\("))) {
- // start of escape
- i += 2; // move over \(
- int a = i;
- while ((i+1<str.length()) && (str.substring(i, 2) != string("\\)"))) {
- i++;
- }
- int n = i;
- if ((i+1<str.length()) && (str.substring(i, 2) == string("\\)")))
- i++;
- else
- n = -1;
- if (n > 0) {
- string troff_charname = str.substring(a, n-a);
- html_glyph = get_html_translation(s->f, troff_charname);
- if (html_glyph)
- html_string += html_glyph;
- else {
- int idx=s->f->name_to_index((troff_charname + '\0').contents());
-
- if (s->f->contains(idx) && (idx != 0))
- html_string += s->f->get_code(idx);
- }
- }
- } else
- html_string += str[i];
- i++;
- }
- if (html_string.length() > 0) {
- text_glob *g=new text_glob();
- if (is_tag)
- g->text_glob_tag(s, buffer.add_string(html_string),
- html_string.length(),
- min_vertical, min_horizontal,
- max_vertical, max_horizontal);
- else
- g->text_glob_special(s, buffer.add_string(html_string),
- html_string.length(),
- min_vertical, min_horizontal,
- max_vertical, max_horizontal);
- glyphs.add(g, line_number, min_vertical,
- min_horizontal, max_vertical, max_horizontal);
- }
- }
- /*
- * dump_page - dump the page contents for debugging purposes.
- */
- void page::dump_page(void)
- {
- #if defined(DEBUG_TABLES)
- text_glob *old_pos = glyphs.get_data();
- text_glob *g;
- printf("\n<!--\n");
- printf("\n\ndebugging start\n");
- glyphs.start_from_head();
- do {
- g = glyphs.get_data();
- if (g->is_tab_ts()) {
- printf("\n\n");
- if (g->get_table() != NULL)
- g->get_table()->dump_table();
- }
- printf("%s ", g->text_string);
- if (g->is_tab_te())
- printf("\n\n");
- glyphs.move_right();
- } while (! glyphs.is_equal_to_head());
- glyphs.move_to(old_pos);
- printf("\ndebugging end\n\n");
- printf("\n-->\n");
- fflush(stdout);
- #endif
- }
- /*
- * font classes and methods
- */
- class html_font : public font {
- html_font(const char *);
- 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;
- int with_h1;
- string text;
- };
- title_desc::title_desc ()
- : has_been_written(FALSE), has_been_found(FALSE), with_h1(FALSE)
- {
- }
- title_desc::~title_desc ()
- {
- }
- class header_desc {
- public:
- header_desc ();
- ~header_desc ();
- int no_of_level_one_headings; // how many .SH or .NH 1 have we found?
- int no_of_headings; // how many headings have we found?
- char_buffer headings; // all the headings used in the document
- list headers; // list of headers built from .NH and .SH
- list header_filename; // in which file is this header?
- int header_level; // current header level
- int written_header; // have we written the header yet?
- string header_buffer; // current header text
- void write_headings (FILE *f, int force);
- };
- header_desc::header_desc ()
- : no_of_level_one_headings(0), no_of_headings(0),
- header_level(2), written_header(0)
- {
- }
- header_desc::~header_desc ()
- {
- }
- /*
- * write_headings - emits a list of links for the headings in this document
- */
- void header_desc::write_headings (FILE *f, int force)
- {
- text_glob *g;
- if (auto_links || force) {
- if (! headers.is_empty()) {
- int h=1;
- headers.start_from_head();
- header_filename.start_from_head();
- do {
- g = headers.get_data();
- fputs("<a href=\"", f);
- if (multiple_files && (! header_filename.is_empty())) {
- text_glob *fn = header_filename.get_data();
- fputs(fn->text_string, f);
- }
- fputs("#", f);
- if (simple_anchors) {
- string buffer(ANCHOR_TEMPLATE);
- buffer += as_string(h);
- buffer += '\0';
- fputs(buffer.contents(), f);
- } else
- fputs(g->text_string, f);
- h++;
- fputs("\">", f);
- fputs(g->text_string, f);
- fputs("</a><br>\n", f);
- headers.move_right();
- if (multiple_files && (! header_filename.is_empty()))
- header_filename.move_right();
- } while (! headers.is_equal_to_head());
- fputs("\n", f);
- }
- }
- }
- struct assert_pos {
- assert_pos *next;
- const char *val;
- const char *id;
- };
- class assert_state {
- public:
- assert_state ();
- ~assert_state ();
- void addx (const char *c, const char *i, const char *v,
- const char *f, const char *l);
- void addy (const char *c, const char *i, const char *v,
- const char *f, const char *l);
- void build(const char *c, const char *v,
- const char *f, const char *l);
- void check_br (int br);
- void check_ce (int ce);
- void check_fi (int fi);
- void check_sp (int sp);
- void reset (void);
- private:
- int check_br_flag;
- int check_ce_flag;
- int check_fi_flag;
- int check_sp_flag;
- const char *val_br;
- const char *val_ce;
- const char *val_fi;
- const char *val_sp;
- const char *file_br;
- const char *file_ce;
- const char *file_fi;
- const char *file_sp;
- const char *line_br;
- const char *line_ce;
- const char *line_fi;
- const char *line_sp;
- assert_pos *xhead;
- assert_pos *yhead;
- void add (assert_pos **h,
- const char *c, const char *i, const char *v,
- const char *f, const char *l);
- void compare(assert_pos *t,
- const char *v, const char *f, const char *l);
- void close (const char *c);
- void set (const char *c, const char *v,
- const char *f, const char *l);
- void check_value (const char *s, int v, const char *name,
- const char *f, const char *l, int *flag);
- int check_value_error (int c, int v, const char *s,
- const char *name,
- const char *f, const char *l, int flag);
- };
- assert_state::assert_state ()
- {
- reset();
- val_br = NULL;
- val_ce = NULL;
- val_fi = NULL;
- val_sp = NULL;
- file_br = NULL;
- file_ce = NULL;
- file_fi = NULL;
- file_sp = NULL;
- line_br = NULL;
- line_ce = NULL;
- line_fi = NULL;
- line_sp = NULL;
- xhead = NULL;
- yhead = NULL;
- }
- assert_state::~assert_state ()
- {
- assert_pos *t;
- while (xhead != NULL) {
- t = xhead;
- xhead = xhead->next;
- a_delete (char *)t->val;
- a_delete (char *)t->id;
- delete t;
- }
- while (yhead != NULL) {
- t = yhead;
- yhead = yhead->next;
- a_delete (char *)t->val;
- a_delete (char *)t->id;
- delete t;
- }
- }
- void assert_state::reset (void)
- {
- check_br_flag = 0;
- check_ce_flag = 0;
- check_fi_flag = 0;
- check_sp_flag = 0;
- }
- void assert_state::add (assert_pos **h,
- const char *c, const char *i, const char *v,
- const char *f, const char *l)
- {
- assert_pos *t = *h;
- while (t != NULL) {
- if (strcmp(t->id, i) == 0)
- break;
- t = t->next;
- }
- if (t != NULL && v != NULL && (v[0] != '='))
- compare(t, v, f, l);
- else {
- if (t == NULL) {
- t = new assert_pos;
- t->next = *h;
- (*h) = t;
- }
- if (v == NULL || v[0] != '=') {
- if (f == NULL)
- f = "stdin";
- if (l == NULL)
- l = "<none>";
- if (v == NULL)
- v = "no value at all";
- fprintf(stderr, "%s:%s:error in assert format of id=%s expecting value to be prefixed with an `=' got %s\n",
- f, l, i, v);
- }
- t->id = i;
- t->val = v;
- a_delete (char *)c;
- a_delete (char *)f;
- a_delete (char *)l;
- }
- }
- void assert_state::addx (const char *c, const char *i, const char *v,
- const char *f, const char *l)
- {
- add(&xhead, c, i, v, f, l);
- }
- void assert_state::addy (const char *c, const char *i, const char *v,
- const char *f, const char *l)
- {
- add(&yhead, c, i, v, f, l);
- }
- void assert_state::compare(assert_pos *t,
- const char *v, const char *f, const char *l)
- {
- const char *s=t->val;
- while ((*v) == '=')
- v++;
- while ((*s) == '=')
- s++;
-
- if (strcmp(v, s) != 0) {
- if (f == NULL)
- f = "stdin";
- if (l == NULL)
- l = "<none>";
- fprintf(stderr, "%s:%s: grohtml assertion failed at id%s expecting %s and was given %s\n",
- f, l, t->id, s, v);
- }
- }
- void assert_state::close (const char *c)
- {
- if (strcmp(c, "sp") == 0)
- check_sp_flag = 0;
- else if (strcmp(c, "br") == 0)
- check_br_flag = 0;
- else if (strcmp(c, "fi") == 0)
- check_fi_flag = 0;
- else if (strcmp(c, "nf") == 0)
- check_fi_flag = 0;
- else if (strcmp(c, "ce") == 0)
- check_ce_flag = 0;
- else
- fprintf(stderr, "internal error: unrecognised tag in grohtml (%s)\n", c);
- }
- const char *replace_negate_str (const char *before, char *after)
- {
- if (before != NULL)
- a_delete (char *)before;
- if (strlen(after) > 0) {
- int d = atoi(after);
- if (d < 0 || d > 1) {
- fprintf(stderr, "expecting nf/fi value to be 0 or 1 not %d\n", d);
- d = 0;
- }
- if (d == 0)
- after[0] = '1';
- else
- after[0] = '0';
- after[1] = (char)0;
- }
- return after;
- }
- const char *replace_str (const char *before, const char *after)
- {
- if (before != NULL)
- a_delete (char *)before;
- return after;
- }
- void assert_state::set (const char *c, const char *v,
- const char *f, const char *l)
- {
- if (l == NULL)
- l = "<none>";
- if (f == NULL)
- f = "stdin";
- // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v);
- if (strcmp(c, "sp") == 0) {
- check_sp_flag = 1;
- val_sp = replace_str(val_sp, strsave(v));
- file_sp = replace_str(file_sp, strsave(f));
- line_sp = replace_str(line_sp, strsave(l));
- } else if (strcmp(c, "br") == 0) {
- check_br_flag = 1;
- val_br = replace_str(val_br, strsave(v));
- file_br = replace_str(file_br, strsave(f));
- line_br = replace_str(line_br, strsave(l));
- } else if (strcmp(c, "fi") == 0) {
- check_fi_flag = 1;
- val_fi = replace_str(val_fi, strsave(v));
- file_fi = replace_str(file_fi, strsave(f));
- line_fi = replace_str(line_fi, strsave(l));
- } else if (strcmp(c, "nf") == 0) {
- check_fi_flag = 1;
- val_fi = replace_negate_str(val_fi, strsave(v));
- file_fi = replace_str(file_fi, strsave(f));
- line_fi = replace_str(line_fi, strsave(l));
- } else if (strcmp(c, "ce") == 0) {
- check_ce_flag = 1;
- val_ce = replace_str(val_ce, strsave(v));
- file_ce = replace_str(file_ce, strsave(f));
- line_ce = replace_str(line_ce, strsave(l));
- }
- }
- /*
- * build - builds the troff state assertion.
- * see tmac/www.tmac for cmd examples.
- */
- void assert_state::build (const char *c, const char *v,
- const char *f, const char *l)
- {
- if (c[0] == '{')
- set(&c[1], v, f, l);
- if (c[0] == '}')
- close(&c[1]);
- }
- int assert_state::check_value_error (int c, int v, const char *s,
- const char *name,
- const char *f, const char *l, int flag)
- {
- if (! c) {
- if (f == NULL)
- f = "stdin";
- if (l == NULL)
- l = "<none>";
- fprintf(stderr, "%s:%s:grohtml (troff state) assertion failed, expected %s to be %s but found it to contain %d\n",
- f, l, name, s, v);
- return 0;
- }
- return flag;
- }
- void assert_state::check_value (const char *s, int v, const char *name,
- const char *f, const char *l, int *flag)
- {
- if (strncmp(s, "<=", 2) == 0)
- *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag);
- else if (strncmp(s, ">=", 2) == 0)
- *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag);
- else if (strncmp(s, "==", 2) == 0)
- *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag);
- else if (strncmp(s, "!=", 2) == 0)
- *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag);
- else if (strncmp(s, "<", 1) == 0)
- *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag);
- else if (strncmp(s, ">", 1) == 0)
- *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag);
- else if (strncmp(s, "=", 1) == 0)
- *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag);
- else
- *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag);
- }
- void assert_state::check_sp (int sp)
- {
- if (check_sp_flag)
- check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag);
- }
- void assert_state::check_fi (int fi)
- {
- if (check_fi_flag)
- check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag);
- }
- void assert_state::check_br (int br)
- {
- if (check_br_flag)
- check_value(val_br, br, "br", file_br, line_br, &check_br_flag);
- }
- void assert_state::check_ce (int ce)
- {
- if (check_ce_flag)
- check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag);
- }
- class html_printer : public printer {
- files file_list;
- simple_output html;
- int res;
- int space_char_index;
- int space_width;
- int no_of_printed_pages;
- int paper_length;
- string sbuf;
- int sbuf_start_hpos;
- int sbuf_vpos;
- int sbuf_end_hpos;
- int sbuf_prev_hpos;
- int sbuf_kern;
- style sbuf_style;
- int last_sbuf_length;
- int overstrike_detected;
- style output_style;
- int output_hpos;
- int output_vpos;
- int output_vpos_max;
- int output_draw_point_size;
- int line_thickness;
- int output_line_thickness;
- unsigned char output_space_code;
- char *inside_font_style;
- int page_number;
- title_desc title;
- header_desc header;
- int header_indent;
- int supress_sub_sup;
- int cutoff_heading;
- page *page_contents;
- html_text *current_paragraph;
- html_indent *indent;
- html_table *table;
- int end_center;
- int end_tempindent;
- TAG_ALIGNMENT next_tag;
- int fill_on;
- int max_linelength;
- int linelength;
- int pageoffset;
- int troff_indent;
- int device_indent;
- int temp_indent;
- int pointsize;
- int vertical_spacing;
- int line_number;
- color *background;
- int seen_indent;
- int next_indent;
- int seen_pageoffset;
- int next_pageoffset;
- int seen_linelength;
- int next_linelength;
- int seen_center;
- int next_center;
- int seen_space;
- int seen_break;
- int current_column;
- int row_space;
- assert_state as;
- void flush_sbuf ();
- void set_style (const style &);
- 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 *, c…