From 2808dd863a3805a478f57075aea740986ccbb9e6 Mon Sep 17 00:00:00 2001 From: Ingo Schwarze Date: Mon, 30 Jan 2017 20:24:02 +0000 Subject: Rework fill mode handling for -man -Thtml. Basically, open
 whenever printing text in no-fill mode and it is
not already open, and close it whenever printing something that cannot
be inside 
.

This fixes a crash reported by Michael 
in the French Linux chroot(2) manual and also improves rendering
for OpenBSD pages like DPMSGetTimeouts(3) and GLwDrawingArea(3).

These changes also permitted retiring struct mhtml.
---
 man_html.c | 201 ++++++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 133 insertions(+), 68 deletions(-)

diff --git a/man_html.c b/man_html.c
index 28a403ae..b4e55f64 100644
--- a/man_html.c
+++ b/man_html.c
@@ -38,13 +38,8 @@
 
 #define	MAN_ARGS	  const struct roff_meta *man, \
 			  const struct roff_node *n, \
-			  struct mhtml *mh, \
 			  struct html *h
 
-struct	mhtml {
-	struct tag	 *nofill;
-};
-
 struct	htmlman {
 	int		(*pre)(MAN_ARGS);
 	int		(*post)(MAN_ARGS);
@@ -55,6 +50,7 @@ static	void		  print_bvspace(struct html *,
 static	void		  print_man_head(MAN_ARGS);
 static	void		  print_man_nodelist(MAN_ARGS);
 static	void		  print_man_node(MAN_ARGS);
+static	int		  fillmode(struct html *, int);
 static	int		  a2width(const struct roff_node *,
 				struct roffsu *);
 static	int		  man_B_pre(MAN_ARGS);
@@ -70,7 +66,6 @@ static	int		  man_SS_pre(MAN_ARGS);
 static	int		  man_UR_pre(MAN_ARGS);
 static	int		  man_alt_pre(MAN_ARGS);
 static	int		  man_br_pre(MAN_ARGS);
-static	int		  man_fill_pre(MAN_ARGS);
 static	int		  man_ign_pre(MAN_ARGS);
 static	int		  man_in_pre(MAN_ARGS);
 static	void		  man_root_post(MAN_ARGS);
@@ -99,8 +94,8 @@ static	const struct htmlman mans[MAN_MAX] = {
 	{ man_alt_pre, NULL }, /* IR */
 	{ man_alt_pre, NULL }, /* RI */
 	{ man_br_pre, NULL }, /* sp */
-	{ man_fill_pre, NULL }, /* nf */
-	{ man_fill_pre, NULL }, /* fi */
+	{ NULL, NULL }, /* nf */
+	{ NULL, NULL }, /* fi */
 	{ NULL, NULL }, /* RE */
 	{ man_RS_pre, NULL }, /* RS */
 	{ man_ign_pre, NULL }, /* DT */
@@ -110,8 +105,8 @@ static	const struct htmlman mans[MAN_MAX] = {
 	{ man_in_pre, NULL }, /* in */
 	{ man_ign_pre, NULL }, /* ft */
 	{ man_OP_pre, NULL }, /* OP */
-	{ man_fill_pre, NULL }, /* EX */
-	{ man_fill_pre, NULL }, /* EE */
+	{ NULL, NULL }, /* EX */
+	{ NULL, NULL }, /* EE */
 	{ man_UR_pre, NULL }, /* UR */
 	{ NULL, NULL }, /* UE */
 	{ man_ign_pre, NULL }, /* ll */
@@ -144,27 +139,25 @@ print_bvspace(struct html *h, const struct roff_node *n)
 void
 html_man(void *arg, const struct roff_man *man)
 {
-	struct mhtml	 mh;
 	struct html	*h;
 	struct tag	*t;
 
-	mh.nofill = NULL;
 	h = (struct html *)arg;
 
 	if ((h->oflags & HTML_FRAGMENT) == 0) {
 		print_gen_decls(h);
 		print_otag(h, TAG_HTML, "");
 		t = print_otag(h, TAG_HEAD, "");
-		print_man_head(&man->meta, man->first, &mh, h);
+		print_man_head(&man->meta, man->first, h);
 		print_tagq(h, t);
 		print_otag(h, TAG_BODY, "");
 	}
 
-	man_root_pre(&man->meta, man->first, &mh, h);
+	man_root_pre(&man->meta, man->first, h);
 	t = print_otag(h, TAG_DIV, "c", "manual-text");
-	print_man_nodelist(&man->meta, man->first->child, &mh, h);
+	print_man_nodelist(&man->meta, man->first->child, h);
 	print_tagq(h, t);
-	man_root_post(&man->meta, man->first, &mh, h);
+	man_root_post(&man->meta, man->first, h);
 	print_tagq(h, NULL);
 }
 
@@ -185,7 +178,7 @@ print_man_nodelist(MAN_ARGS)
 {
 
 	while (n != NULL) {
-		print_man_node(man, n, mh, h);
+		print_man_node(man, n, h);
 		n = n->next;
 	}
 }
@@ -193,26 +186,95 @@ print_man_nodelist(MAN_ARGS)
 static void
 print_man_node(MAN_ARGS)
 {
-	int		 child;
+	static int	 want_fillmode = MAN_fi;
+	static int	 save_fillmode;
+
 	struct tag	*t;
+	int		 child;
 
-	child = 1;
-	t = h->tag;
-	if (t == mh->nofill)
-		t = t->next;
+	/*
+	 * Handle fill mode switch requests up front,
+	 * they would just cause trouble in the subsequent code.
+	 */
+
+	switch (n->tok) {
+	case MAN_nf:
+	case MAN_EX:
+		want_fillmode = MAN_nf;
+		return;
+	case MAN_fi:
+	case MAN_EE:
+		want_fillmode = MAN_fi;
+		if (fillmode(h, 0) == MAN_fi)
+			print_otag(h, TAG_BR, "");
+		return;
+	default:
+		break;
+	}
+
+	/* Set up fill mode for the upcoming node. */
 
 	switch (n->type) {
-	case ROFFT_TEXT:
-		if ('\0' == *n->string) {
-			print_paragraph(h);
-			return;
+	case ROFFT_BLOCK:
+		save_fillmode = 0;
+		/* Some block macros suspend or cancel .nf. */
+		switch (n->tok) {
+		case MAN_TP:  /* Tagged paragraphs		*/
+		case MAN_IP:  /* temporarily disable .nf	*/
+		case MAN_HP:  /* for the head.			*/
+			save_fillmode = want_fillmode;
+			/* FALLTHROUGH */
+		case MAN_SH:  /* Section headers		*/
+		case MAN_SS:  /* permanently cancel .nf.	*/
+			want_fillmode = MAN_fi;
+			/* FALLTHROUGH */
+		case MAN_PP:  /* These have no head.		*/
+		case MAN_LP:  /* They will simply		*/
+		case MAN_P:   /* reopen .nf in the body.	*/
+		case MAN_RS:
+		case MAN_UR:
+			fillmode(h, MAN_fi);
+			break;
+		default:
+			break;
 		}
-		if (mh->nofill == NULL &&
+		break;
+	case ROFFT_TBL:
+		fillmode(h, MAN_fi);
+		break;
+	case ROFFT_ELEM:
+		/*
+		 * Some in-line macros produce tags and/or text
+		 * in the handler, so they require fill mode to be
+		 * configured up front just like for text nodes.
+		 * For the others, keep the traditional approach
+		 * of doing the same, for now.
+		 */
+		fillmode(h, want_fillmode);
+		break;
+	case ROFFT_TEXT:
+		if (fillmode(h, want_fillmode) == MAN_fi &&
+		    want_fillmode == MAN_fi &&
 		    n->flags & NODE_LINE && *n->string == ' ')
 			print_otag(h, TAG_BR, "");
+		if (*n->string != '\0')
+			break;
+		print_paragraph(h);
+		return;
+	default:
+		break;
+	}
+
+	/* Produce output for this node. */
+
+	child = 1;
+	switch (n->type) {
+	case ROFFT_TEXT:
+		t = h->tag;
 		print_text(h, n->string);
 		break;
 	case ROFFT_EQN:
+		t = h->tag;
 		print_eqn(h, n->eqn);
 		break;
 	case ROFFT_TBL:
@@ -238,30 +300,57 @@ print_man_node(MAN_ARGS)
 		 * the "meta" table state.  This will be reopened on the
 		 * next table element.
 		 */
-		if (h->tblt) {
+		if (h->tblt)
 			print_tblclose(h);
-			t = h->tag;
-		}
+
+		t = h->tag;
 		if (mans[n->tok].pre)
-			child = (*mans[n->tok].pre)(man, n, mh, h);
+			child = (*mans[n->tok].pre)(man, n, h);
+
+		/* Some block macros resume .nf in the body. */
+		if (save_fillmode && n->type == ROFFT_BODY)
+			want_fillmode = save_fillmode;
+
 		break;
 	}
 
 	if (child && n->child)
-		print_man_nodelist(man, n->child, mh, h);
+		print_man_nodelist(man, n->child, h);
 
 	/* This will automatically close out any font scope. */
-	print_stagq(h, mh->nofill == NULL ? t : mh->nofill);
+	print_stagq(h, t);
 
-	if (n->type != ROFFT_TEXT && n->type != ROFFT_EQN &&
-	    mans[n->tok].post != NULL)
-		(*mans[n->tok].post)(man, n, mh, h);
-
-	if (mh->nofill != NULL &&
-	    (n->next == NULL || n->next->flags & NODE_LINE))
+	if (fillmode(h, 0) == MAN_nf &&
+	    n->next != NULL && n->next->flags & NODE_LINE)
 		print_endline(h);
 }
 
+/*
+ * MAN_nf switches to no-fill mode, MAN_fi to fill mode.
+ * Other arguments do not switch.
+ * The old mode is returned.
+ */
+static int
+fillmode(struct html *h, int want)
+{
+	struct tag	*pre;
+	int		 had;
+
+	for (pre = h->tag; pre != NULL; pre = pre->next)
+		if (pre->tag == TAG_PRE)
+			break;
+
+	had = pre == NULL ? MAN_fi : MAN_nf;
+
+	if (want && want != had) {
+		if (want == MAN_nf)
+			print_otag(h, TAG_PRE, "");
+		else
+			print_tagq(h, pre);
+	}
+	return had;
+}
+
 static int
 a2width(const struct roff_node *n, struct roffsu *su)
 {
@@ -347,10 +436,7 @@ man_br_pre(MAN_ARGS)
 static int
 man_SH_pre(MAN_ARGS)
 {
-	if (n->type == ROFFT_BLOCK && mh->nofill != NULL) {
-		print_tagq(h, mh->nofill);
-		mh->nofill = NULL;
-	} else if (n->type == ROFFT_HEAD)
+	if (n->type == ROFFT_HEAD)
 		print_otag(h, TAG_H1, "c", "Sh");
 	return 1;
 }
@@ -413,10 +499,7 @@ man_SM_pre(MAN_ARGS)
 static int
 man_SS_pre(MAN_ARGS)
 {
-	if (n->type == ROFFT_BLOCK && mh->nofill != NULL) {
-		print_tagq(h, mh->nofill);
-		mh->nofill = NULL;
-	} else if (n->type == ROFFT_HEAD)
+	if (n->type == ROFFT_HEAD)
 		print_otag(h, TAG_H2, "c", "Ss");
 	return 1;
 }
@@ -453,7 +536,7 @@ man_IP_pre(MAN_ARGS)
 	/* For IP, only print the first header element. */
 
 	if (MAN_IP == n->tok && n->child)
-		print_man_node(man, n->child, mh, h);
+		print_man_node(man, n->child, h);
 
 	/* For TP, only print next-line header elements. */
 
@@ -462,7 +545,7 @@ man_IP_pre(MAN_ARGS)
 		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
 			nn = nn->next;
 		while (NULL != nn) {
-			print_man_node(man, nn, mh, h);
+			print_man_node(man, nn, h);
 			nn = nn->next;
 		}
 	}
@@ -535,24 +618,6 @@ man_I_pre(MAN_ARGS)
 	return 1;
 }
 
-static int
-man_fill_pre(MAN_ARGS)
-{
-	if (MAN_fi == n->tok || MAN_EE == n->tok) {
-		if (mh->nofill != NULL) {
-			print_tagq(h, mh->nofill);
-			mh->nofill = NULL;
-		} else
-			print_otag(h, TAG_BR, "");
-	} else {
-		if (mh->nofill == NULL)
-			mh->nofill = print_otag(h, TAG_PRE, "");
-		else
-			print_otag(h, TAG_BR, "");
-	}
-	return 0;
-}
-
 static int
 man_in_pre(MAN_ARGS)
 {
@@ -599,7 +664,7 @@ man_UR_pre(MAN_ARGS)
 	if (n->next->child != NULL)
 		n = n->next;
 
-	print_man_nodelist(man, n->child, mh, h);
+	print_man_nodelist(man, n->child, h);
 
 	return 0;
 }
-- 
cgit