// -*- C++ -*- /* Copyright (C) 2000-2020 Free Software Foundation, Inc. * * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cpp * * html-text.cpp * * provide a troff like state machine interface which * generates html text. */ /* This file is part of groff. groff is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. groff is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "driver.h" #include "stringclass.h" #include "cset.h" #if !defined(TRUE) # define TRUE (1==1) #endif #if !defined(FALSE) # define FALSE (1==0) #endif #include "html-text.h" html_text::html_text (simple_output *op, html_dialect d) : stackptr(NULL), lastptr(NULL), out(op), dialect(d), space_emitted(TRUE), current_indentation(-1), pageoffset(-1), linelength(-1), blank_para(TRUE), start_space(FALSE) { } html_text::~html_text () { flush_text(); } #if defined(DEBUGGING) static int debugStack = FALSE; /* * turnDebug - flip the debugStack boolean and return the new value. */ static int turnDebug (void) { debugStack = 1-debugStack; return debugStack; } /* * dump_stack_element - display an element of the html stack, p. */ void html_text::dump_stack_element (tag_definition *p) { fprintf(stderr, " | "); switch (p->type) { case P_TAG: if (p->indent == NULL) { fprintf(stderr, "

", (char *)p->arg1); break; } else { fprintf(stderr, "

", (char *)p->arg1); break; } case I_TAG: fprintf(stderr, ""); break; case B_TAG: fprintf(stderr, ""); break; case SUB_TAG: fprintf(stderr, ""); break; case SUP_TAG: fprintf(stderr, ""); break; case TT_TAG: fprintf(stderr, ""); break; case PRE_TAG: if (p->indent == NULL) { fprintf(stderr, "

"); break;
                   } else {
                      fprintf(stderr, "
"); break;
		   }
  case SMALL_TAG:  fprintf(stderr, ""); break;
  case BIG_TAG:    fprintf(stderr, ""); break;
  case BREAK_TAG:  fprintf(stderr, ""); break;
  case COLOR_TAG:  {
    if (p->col.is_default())
      fprintf(stderr, "");
    else {
      unsigned int r, g, b;
      
      p->col.get_rgb(&r, &g, &b);
      fprintf(stderr, "", r/0x101, g/0x101, b/0x101);
    }
    break;
  }
  default: fprintf(stderr, "unknown tag");
  }
  if (p->text_emitted)
    fprintf(stderr, "[t] ");
}

/*
 *  dump_stack - debugging function only.
 */

void html_text::dump_stack (void)
{
  if (debugStack) {
    tag_definition *p = stackptr;

    while (p != NULL) {
      dump_stack_element(p);
      p = p->next;
    }
  }
  fprintf(stderr, "\n");
  fflush(stderr);
}
#else
void html_text::dump_stack (void) {}
#endif


/*
 *  end_tag - shuts down the tag.
 */

void html_text::end_tag (tag_definition *t)
{
  switch (t->type) {

  case I_TAG:      out->put_string(""); break;
  case B_TAG:      out->put_string(""); break;
  case P_TAG:      if (t->indent == NULL) {
                     out->put_string("

"); } else { delete t->indent; t->indent = NULL; out->put_string("

"); } out->enable_newlines(FALSE); blank_para = TRUE; break; case SUB_TAG: out->put_string(""); break; case SUP_TAG: out->put_string(""); break; case TT_TAG: out->put_string("
"); break; case PRE_TAG: out->put_string("
"); out->enable_newlines(TRUE); blank_para = TRUE; if (t->indent != NULL) delete t->indent; t->indent = NULL; break; case SMALL_TAG: if (! is_in_pre ()) out->put_string(""); break; case BIG_TAG: if (! is_in_pre ()) out->put_string(""); break; case COLOR_TAG: if (! is_in_pre ()) out->put_string(""); break; default: error("unrecognised tag"); } } /* * issue_tag - writes out an html tag with argument. * space == 0 if no space is requested * space == 1 if a space is requested * space == 2 if tag should not have a space style */ void html_text::issue_tag (const char *tagname, const char *arg, int space) { if ((arg == 0) || (strlen(arg) == 0)) out->put_string(tagname); else { out->put_string(tagname); out->put_string(" "); out->put_string(arg); } if (space == TRUE) { out->put_string(" style=\"margin-top: "); out->put_string(STYLE_VERTICAL_SPACE); out->put_string("\""); } #if 0 if (space == TRUE || space == FALSE) out->put_string(" valign=\"top\""); #endif out->put_string(">"); } /* * issue_color_begin - writes out an html color tag. */ void html_text::issue_color_begin (color *c) { char buf[(INT_HEXDIGITS * 3) + 1]; unsigned int r, g, b; out->put_string("is_default()) sprintf(buf, "000000"); else { c->get_rgb(&r, &g, &b); // we have to scale 0..0xFFFF to 0..0xFF sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101); } out->put_string(buf); out->put_string("\">"); } /* * start_tag - starts a tag. */ void html_text::start_tag (tag_definition *t) { switch (t->type) { case I_TAG: issue_tag("arg1); break; case B_TAG: issue_tag("arg1); break; case P_TAG: if (t->indent != NULL) { out->nl(); #if defined(DEBUGGING) out->simple_comment("INDENTATION"); #endif out->put_string("\nindent->begin(start_space); issue_tag("", (char *)t->arg1); } else { out->nl(); issue_tag("\narg1, start_space); } out->enable_newlines(TRUE); break; case SUB_TAG: issue_tag("arg1); break; case SUP_TAG: issue_tag("arg1); break; case TT_TAG: issue_tag("arg1); break; case PRE_TAG: out->enable_newlines(TRUE); out->nl(); out->put_string("indent == NULL) issue_tag("", (char *)t->arg1, start_space); else { t->indent->begin(start_space); issue_tag("", (char *)t->arg1); } out->enable_newlines(FALSE); break; case SMALL_TAG: if (! is_in_pre ()) issue_tag("arg1); break; case BIG_TAG: if (! is_in_pre ()) issue_tag("arg1); break; case BREAK_TAG: break; case COLOR_TAG: if (! is_in_pre ()) issue_color_begin(&t->col); break; default: error("unrecognised tag"); } } /* * flush_text - flushes html tags which are outstanding on the html stack. */ void html_text::flush_text (void) { int notext=TRUE; tag_definition *p=stackptr; while (stackptr != 0) { notext = (notext && (! stackptr->text_emitted)); if (! notext) { end_tag(stackptr); } p = stackptr; stackptr = stackptr->next; delete p; } lastptr = NULL; } /* * is_present - returns TRUE if tag is already present on the stack. */ int html_text::is_present (HTML_TAG t) { tag_definition *p=stackptr; while (p != NULL) { if (t == p->type) return TRUE; p = p->next; } return FALSE; } /* * uses_indent - returns TRUE if the current paragraph is using a * html table to effect an indent. */ int html_text::uses_indent (void) { tag_definition *p = stackptr; while (p != NULL) { if (p->indent != NULL) return TRUE; p = p->next; } return FALSE; } extern void stop(); /* * do_push - places, tag_definition, p, onto the stack */ void html_text::do_push (tag_definition *p) { HTML_TAG t = p->type; #if defined(DEBUGGING) if (t == PRE_TAG) stop(); debugStack = TRUE; fprintf(stderr, "\nentering do_push ("); dump_stack_element(p); fprintf(stderr, ")\n"); dump_stack(); fprintf(stderr, ")\n"); fflush(stderr); #endif /* * if t is a P_TAG or PRE_TAG make sure it goes on the end of the stack. */ if (((t == P_TAG) || (t == PRE_TAG)) && (lastptr != NULL)) { /* * store, p, at the end */ lastptr->next = p; lastptr = p; p->next = NULL; } else { p->next = stackptr; if (stackptr == NULL) lastptr = p; stackptr = p; } #if defined(DEBUGGING) dump_stack(); fprintf(stderr, "exiting do_push\n"); #endif } /* * push_para - adds a new entry onto the html paragraph stack. */ void html_text::push_para (HTML_TAG t, void *arg, html_indent *in) { tag_definition *p= new tag_definition; p->type = t; p->arg1 = arg; p->text_emitted = FALSE; p->indent = in; if (t == PRE_TAG && is_present(PRE_TAG)) fatal("cannot have multiple PRE_TAGs"); do_push(p); } void html_text::push_para (HTML_TAG t) { push_para(t, (void *)"", NULL); } void html_text::push_para (color *c) { tag_definition *p = new tag_definition; p->type = COLOR_TAG; p->arg1 = NULL; p->col = *c; p->text_emitted = FALSE; p->indent = NULL; do_push(p); } /* * do_italic - changes to italic */ void html_text::do_italic (void) { if (! is_present(I_TAG)) push_para(I_TAG); } /* * do_bold - changes to bold. */ void html_text::do_bold (void) { if (! is_present(B_TAG)) push_para(B_TAG); } /* * do_tt - changes to teletype. */ void html_text::do_tt (void) { if ((! is_present(TT_TAG)) && (! is_present(PRE_TAG))) push_para(TT_TAG); } /* * do_pre - changes to preformated text. */ void html_text::do_pre (void) { done_tt(); if (is_present(P_TAG)) { html_indent *i = remove_indent(P_TAG); int space = retrieve_para_space(); (void)done_para(); if (! is_present(PRE_TAG)) push_para(PRE_TAG, NULL, i); start_space = space; } else if (! is_present(PRE_TAG)) push_para(PRE_TAG, NULL, NULL); dump_stack(); } /* * is_in_pre - returns TRUE if we are currently within a preformatted *
 block.
 */

int html_text::is_in_pre (void)
{
  return is_present(PRE_TAG);
}

/*
 *  do_color - initiates a new color tag.
 */

void html_text::do_color (color *c)
{
  shutdown(COLOR_TAG);   // shutdown a previous color tag, if present
  push_para(c);
}

/*
 *  done_color - shutdown an outstanding color tag, if it exists.
 */

void html_text::done_color (void)
{
  shutdown(COLOR_TAG);
}

/*
 *  shutdown - shuts down an html tag.
 */

char *html_text::shutdown (HTML_TAG t)
{
  char *arg=NULL;

  if (is_present(t)) {
    tag_definition *p    =stackptr;
    tag_definition *temp =NULL;
    int notext           =TRUE;
    
    dump_stack();
    while ((stackptr != NULL) && (stackptr->type != t)) {
      notext = (notext && (! stackptr->text_emitted));
      if (! notext) {
	end_tag(stackptr);
      }

      /*
       *  pop tag
       */
      p        = stackptr;
      stackptr = stackptr->next;
      if (stackptr == NULL)
	lastptr = NULL;
    
      /*
       *  push tag onto temp stack
       */
      p->next = temp;
      temp    = p;
    }

    /*
     *  and examine stackptr
     */
    if ((stackptr != NULL) && (stackptr->type == t)) {
      if (stackptr->text_emitted) {
	end_tag(stackptr);
      }
      if (t == P_TAG) {
	arg = (char *)stackptr->arg1;
      }
      p        = stackptr;
      stackptr = stackptr->next;
      if (stackptr == NULL)
	lastptr = NULL;
      if (p->indent != NULL)
	delete p->indent;
      delete p;
    }

    /*
     *  and restore unaffected tags
     */
    while (temp != NULL) {
      if (temp->type == COLOR_TAG)
	push_para(&temp->col);
      else
	push_para(temp->type, temp->arg1, temp->indent);
      p    = temp;
      temp = temp->next;
      delete p;
    }
  }
  return arg;
}

/*
 *  done_bold - shuts downs a bold tag.
 */

void html_text::done_bold (void)
{
  shutdown(B_TAG);
}

/*
 *  done_italic - shuts downs an italic tag.
 */

void html_text::done_italic (void)
{
  shutdown(I_TAG);
}

/*
 *  done_sup - shuts downs a sup tag.
 */

void html_text::done_sup (void)
{
  shutdown(SUP_TAG);
}

/*
 *  done_sub - shuts downs a sub tag.
 */

void html_text::done_sub (void)
{
  shutdown(SUB_TAG);
}

/*
 *  done_tt - shuts downs a tt tag.
 */

void html_text::done_tt (void)
{
  shutdown(TT_TAG);
}

/*
 *  done_pre - shuts downs a pre tag.
 */

void html_text::done_pre (void)
{
  shutdown(PRE_TAG);
}

/*
 *  done_small - shuts downs a small tag.
 */

void html_text::done_small (void)
{
  shutdown(SMALL_TAG);
}

/*
 *  done_big - shuts downs a big tag.
 */

void html_text::done_big (void)
{
  shutdown(BIG_TAG);
}

/*
 *  check_emit_text - ensures that all previous tags have been emitted (in order)
 *                    before the text is written.
 */

void html_text::check_emit_text (tag_definition *t)
{
  if ((t != NULL) && (! t->text_emitted)) {
    check_emit_text(t->next);
    t->text_emitted = TRUE;
    start_tag(t);
  }
}

/*
 *  do_emittext - tells the class that text was written during the current tag.
 */

void html_text::do_emittext (const char *s, int length)
{
  if ((! is_present(P_TAG)) && (! is_present(PRE_TAG)))
    do_para("", FALSE);

  if (is_present(BREAK_TAG)) {
    int text = remove_break();
    check_emit_text(stackptr);
    if (text) {
      if (is_present(PRE_TAG))
	out->nl();
      else if (dialect == xhtml)
	out->put_string("
").nl(); else out->put_string("
").nl(); } } else check_emit_text(stackptr); out->put_string(s, length); space_emitted = FALSE; blank_para = FALSE; } /* * do_para - starts a new paragraph */ void html_text::do_para (const char *arg, html_indent *in, int space) { if (! is_present(P_TAG)) { if (is_present(PRE_TAG)) { html_indent *i = remove_indent(PRE_TAG); done_pre(); if ((arg == NULL || (strcmp(arg, "") == 0)) && (i == in || in == NULL)) in = i; else delete i; } remove_sub_sup(); push_para(P_TAG, (void *)arg, in); start_space = space; } } void html_text::do_para (const char *arg, int space) { do_para(arg, NULL, space); } void html_text::do_para (simple_output *op, const char *arg1, int indentation_value, int page_offset, int line_length, int space) { html_indent *ind; if (indentation_value == 0) ind = NULL; else ind = new html_indent(op, indentation_value, page_offset, line_length); do_para(arg1, ind, space); } /* * done_para - shuts down a paragraph tag. */ char *html_text::done_para (void) { char *result; space_emitted = TRUE; result = shutdown(P_TAG); start_space = FALSE; return result; } /* * remove_indent - returns the indent associated with, tag. * The indent associated with tag is set to NULL. */ html_indent *html_text::remove_indent (HTML_TAG tag) { tag_definition *p=stackptr; while (p != NULL) { if (tag == p->type) { html_indent *i = p->indent; p->indent = NULL; return i; } p = p->next; } return NULL; } /* * remove_para_space - removes the leading space to a paragraph * (effectively this trims off a leading '.sp' tag). */ void html_text::remove_para_space (void) { start_space = FALSE; } /* * do_space - issues an end of paragraph */ void html_text::do_space (void) { if (is_in_pre()) { do_emittext("", 0); out->force_nl(); space_emitted = TRUE; } else { html_indent *i = remove_indent(P_TAG); do_para(done_para(), i, TRUE); space_emitted = TRUE; } } /* * do_break - issue a break tag. */ void html_text::do_break (void) { if (! is_present(PRE_TAG)) if (emitted_text()) if (! is_present(BREAK_TAG)) push_para(BREAK_TAG); space_emitted = TRUE; } /* * do_newline - issue a newline providing that we are inside a
 tag.
 */

void html_text::do_newline (void)
{
  if (is_present(PRE_TAG)) {
    do_emittext("\n", 1);
    space_emitted = TRUE;
  }
}

/*
 *  emitted_text - returns FALSE if white space has just been written.
 */

int html_text::emitted_text (void)
{
  return !space_emitted;
}

/*
 *  ever_emitted_text - returns TRUE if we have ever emitted text in this
 *                      paragraph.
 */

int html_text::ever_emitted_text (void)
{
  return !blank_para;
}

/*
 *  starts_with_space - returns TRUE if we started this paragraph with a .sp
 */

int html_text::starts_with_space (void)
{
  return start_space;
}

/*
 *  retrieve_para_space - returns TRUE, if the paragraph starts with
 *                        a space and text has not yet been emitted.
 *                        If TRUE is returned, then the, start_space,
 *                        variable is set to FALSE.
 */

int html_text::retrieve_para_space (void)
{
  if (start_space && blank_para) {
    start_space = FALSE;
    return TRUE;
  }
  else
    return FALSE;
}

/*
 *  emit_space - writes a space providing that text was written beforehand.
 */

void html_text::emit_space (void)
{
  if (is_present(PRE_TAG))
    do_emittext(" ", 1);
  else
    out->space_or_newline();

  space_emitted = TRUE;
}

/*
 *  remove_def - removes a definition, t, from the stack.
 */

void html_text::remove_def (tag_definition *t)
{
  tag_definition *p = stackptr;
  tag_definition *l = 0;
    
  while ((p != 0) && (p != t)) {
    l = p;
    p = p->next;
  }
  if ((p != 0) && (p == t)) {
    if (p == stackptr) {
      stackptr = stackptr->next;
      if (stackptr == NULL)
	lastptr = NULL;
    } else if (l == 0) {
      error("stack list pointers are wrong");
    } else {
      l->next = p->next;
      if (l->next == NULL)
	lastptr = l;
    }
    delete p;
  }
}

/*
 *  remove_tag - removes a tag from the stack.
 */

void html_text::remove_tag (HTML_TAG tag)
{
  tag_definition *p = stackptr;
    
  while ((p != 0) && (p->type != tag)) {
    p = p->next;
  }
  if ((p != 0) && (p->type == tag))
    remove_def(p);
}

/*
 *  remove_sub_sup - removes a sub or sup tag, should either exist
 *                   on the stack.
 */

void html_text::remove_sub_sup (void)
{
  if (is_present(SUB_TAG)) {
    remove_tag(SUB_TAG);
  }
  if (is_present(SUP_TAG)) {
    remove_tag(SUP_TAG);
  }
  if (is_present(PRE_TAG)) {
    remove_tag(PRE_TAG);
  }
}

/*
 *  remove_break - break tags are not balanced thus remove it once it has been emitted.
 *                 It returns TRUE if text was emitted before the 
was issued. */ int html_text::remove_break (void) { tag_definition *p = stackptr; tag_definition *l = 0; tag_definition *q = 0; while ((p != 0) && (p->type != BREAK_TAG)) { l = p; p = p->next; } if ((p != 0) && (p->type == BREAK_TAG)) { if (p == stackptr) { stackptr = stackptr->next; if (stackptr == NULL) lastptr = NULL; q = stackptr; } else if (l == 0) error("stack list pointers are wrong"); else { l->next = p->next; q = p->next; if (l->next == NULL) lastptr = l; } delete p; } /* * now determine whether text was issued before
*/ while (q != 0) { if (q->text_emitted) return TRUE; else q = q->next; } return FALSE; } /* * remove_para_align - removes a paragraph which has a text * argument. If the paragraph has no text * argument then it is left alone. */ void html_text::remove_para_align (void) { if (is_present(P_TAG)) { tag_definition *p=stackptr; while (p != NULL) { if (p->type == P_TAG && p->arg1 != NULL) { html_indent *i = remove_indent(P_TAG); int space = retrieve_para_space(); done_para(); do_para("", i, space); return; } p = p->next; } } } /* * get_alignment - returns the alignment for the paragraph. * If no alignment was given then we return "". */ char *html_text::get_alignment (void) { if (is_present(P_TAG)) { tag_definition *p=stackptr; while (p != NULL) { if (p->type == P_TAG && p->arg1 != NULL) return (char *)p->arg1; p = p->next; } } return (char *)""; } /* * do_small - potentially inserts a tag into the html stream. * However we check for a tag, if present then we terminate it. * Otherwise a tag is inserted. */ void html_text::do_small (void) { if (is_present(BIG_TAG)) done_big(); else push_para(SMALL_TAG); } /* * do_big - is the mirror image of do_small. */ void html_text::do_big (void) { if (is_present(SMALL_TAG)) done_small(); else push_para(BIG_TAG); } /* * do_sup - save a superscript tag on the stack of tags. */ void html_text::do_sup (void) { push_para(SUP_TAG); } /* * do_sub - save a subscript tag on the stack of tags. */ void html_text::do_sub (void) { push_para(SUB_TAG); }