plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

troff2html.c (16311B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <bio.h>
      4 #include <ctype.h>
      5 
      6 enum{
      7 	Nfont = 11,
      8 	Wid = 20	/* tmac.anhtml sets page width to 20" so we can recognize .nf text */
      9 };
     10 
     11 typedef ulong Char;
     12 typedef struct Troffchar Troffchar;
     13 typedef struct Htmlchar Htmlchar;
     14 typedef struct Font Font;
     15 typedef struct HTMLfont HTMLfont;
     16 
     17 /* a Char is 32 bits. low 16 bits are the rune. higher are attributes */
     18 enum
     19 {
     20 	Italic	=	16,
     21 	Bold,
     22 	CW,
     23 	Indent1,
     24 	Indent2,
     25 	Indent3,
     26 	Heading =	25,
     27 	Anchor =	26	/* must be last */
     28 };
     29 
     30 enum	/* magic emissions */
     31 {
     32 	Estring = 0,
     33 	Epp = 1<<16
     34 };
     35 
     36 int attrorder[] = { Indent1, Indent2, Indent3, Heading, Anchor, Italic, Bold, CW };
     37 
     38 int nest[10];
     39 int nnest;
     40 
     41 struct Troffchar
     42 {
     43 	char *name;
     44 	char *value;
     45 };
     46 
     47 struct Htmlchar
     48 {
     49 	char *utf;
     50 	char *name;
     51 	int value;
     52 };
     53 
     54 #include "chars.h"
     55 
     56 struct Font{
     57 	char		*name;
     58 	HTMLfont	*htmlfont;
     59 };
     60 
     61 struct HTMLfont{
     62 	char	*name;
     63 	char	*htmlname;
     64 	int	bit;
     65 };
     66 
     67 /* R must be first; it's the default representation for fonts we don't recognize */
     68 HTMLfont htmlfonts[] =
     69 {
     70 	"R",			nil,		0,
     71 	"LuxiSans",	nil,		0,
     72 	"I",			"i",	Italic,
     73 	"LuxiSans-Oblique",	"i",	Italic,
     74 	"CW",		"tt",		CW,
     75 	"LuxiMono",	"tt",		CW,
     76 	nil,	nil
     77 };
     78 
     79 #define TABLE "<table border=0 cellpadding=0 cellspacing=0>"
     80 
     81 char*
     82 onattr[8*sizeof(ulong)] =
     83 {
     84 	0, 0, 0, 0, 0, 0, 0, 0,
     85 	0, 0, 0, 0, 0, 0, 0, 0,
     86 	"<i>",	/* italic */
     87 	"<b>",	/* bold */
     88 	"<tt><font size=+1>",	/* cw */
     89 	"<+table border=0 cellpadding=0 cellspacing=0><tr height=2><td><tr><td width=20><td>\n",	/* indent1 */
     90 	"<+table border=0 cellpadding=0 cellspacing=0><tr height=2><td><tr><td width=20><td>\n",	/* indent2 */
     91 	"<+table border=0 cellpadding=0 cellspacing=0><tr height=2><td><tr><td width=20><td>\n",	/* indent3 */
     92 	0,
     93 	0,
     94 	0,
     95 	"<p><font size=+1><b>",	/* heading 25 */
     96 	"<unused>",	/* anchor 26 */
     97 };
     98 
     99 char*
    100 offattr[8*sizeof(ulong)] =
    101 {
    102 	0, 0, 0, 0, 0, 0, 0, 0,
    103 	0, 0, 0, 0, 0, 0, 0, 0,
    104 	"</i>",	/* italic */
    105 	"</b>",	/* bold */
    106 	"</font></tt>",	/* cw */
    107 	"<-/table>",	/* indent1 */
    108 	"<-/table>",	/* indent2 */
    109 	"<-/table>",	/* indent3 */
    110 	0,
    111 	0,
    112 	0,
    113 	"</b></font>",	/* heading 25 */
    114 	"</a>",	/* anchor 26 */
    115 };
    116 
    117 Font *font[Nfont];
    118 
    119 Biobuf bout;
    120 int	debug = 0;
    121 
    122 /* troff state */
    123 int	page = 1;
    124 int	ft = 1;
    125 int	vp = 0;
    126 int	hp = 0;
    127 int	ps = 1;
    128 int	res = 720;
    129 
    130 int		didP = 0;
    131 int		atnewline = 1;
    132 int		prevlineH = 0;
    133 ulong	attr = 0;	/* or'ed into each Char */
    134 
    135 Char		*chars;
    136 int		nchars;
    137 int		nalloc;
    138 char**	anchors;	/* allocated in order */
    139 int		nanchors;
    140 
    141 char *pagename;
    142 char *section;
    143 
    144 char	*filename;
    145 int	cno;
    146 char	buf[8192];
    147 char	*title = "Plan 9 man page";
    148 
    149 void	process(Biobuf*, char*);
    150 void	mountfont(int, char*);
    151 void	switchfont(int);
    152 void	header(char*);
    153 void	flush(void);
    154 void	trailer(void);
    155 
    156 void*
    157 emalloc(ulong n)
    158 {
    159 	void *p;
    160 
    161 	p = malloc(n);
    162 	if(p == nil)
    163 		sysfatal("malloc failed: %r");
    164 	return p;
    165 }
    166 
    167 void*
    168 erealloc(void *p, ulong n)
    169 {
    170 
    171 	p = realloc(p, n);
    172 	if(p == nil)
    173 		sysfatal("realloc failed: %r");
    174 	return p;
    175 }
    176 
    177 char*
    178 estrdup(char *s)
    179 {
    180 	char *t;
    181 
    182 	t = strdup(s);
    183 	if(t == nil)
    184 		sysfatal("strdup failed: %r");
    185 	return t;
    186 }
    187 
    188 void
    189 usage(void)
    190 {
    191 	fprint(2, "usage: troff2html [-d] [-t title] [file ...]\n");
    192 	exits("usage");
    193 }
    194 
    195 int
    196 hccmp(const void *va, const void *vb)
    197 {
    198 	Htmlchar *a, *b;
    199 
    200 	a = (Htmlchar*)va;
    201 	b = (Htmlchar*)vb;
    202 	return a->value - b->value;
    203 }
    204 
    205 void
    206 main(int argc, char *argv[])
    207 {
    208 	int i;
    209 	Biobuf in, *inp;
    210 	Rune r;
    211 
    212 	for(i=0; i<nelem(htmlchars); i++){
    213 		chartorune(&r, htmlchars[i].utf);
    214 		htmlchars[i].value = r;
    215 	}
    216 	qsort(htmlchars, nelem(htmlchars), sizeof(htmlchars[0]), hccmp);
    217 
    218 	ARGBEGIN{
    219 	case 't':
    220 		title = ARGF();
    221 		if(title == nil)
    222 			usage();
    223 		break;
    224 	case 'd':
    225 		debug++;
    226 		break;
    227 	default:
    228 		usage();
    229 	}ARGEND
    230 
    231 	Binit(&bout, 1, OWRITE);
    232 	if(argc == 0){
    233 		Binit(&in, 0, OREAD);
    234 		process(&in, "<stdin>");
    235 	}else{
    236 		for(i=0; i<argc; i++){
    237 			inp = Bopen(argv[i], OREAD);
    238 			if(inp == nil)
    239 				sysfatal("can't open %s: %r", argv[i]);
    240 			process(inp, argv[i]);
    241 			Bterm(inp);
    242 		}
    243 	}
    244 	header(title);
    245 	flush();
    246 	trailer();
    247 	exits(nil);
    248 }
    249 
    250 void
    251 emitul(ulong ul, int special)
    252 {
    253 	ulong a, c;
    254 
    255 	if(nalloc == nchars){
    256 		nalloc += 10000;
    257 		chars = realloc(chars, nalloc*sizeof(chars[0]));
    258 		if(chars == nil)
    259 			sysfatal("malloc failed: %r");
    260 	}
    261 
    262 	if(!special){
    263 		a = ul&~0xFFFF;
    264 		c = ul&0xFFFF;
    265 		/*
    266 		 * Attr-specific transformations.
    267 		 */
    268 		if((a&(1<<CW)) && c=='-')
    269 			c = 0x2212;
    270 		if(!(a&(1<<CW))){
    271 			if(c == '`')
    272 				c = 0x2018;
    273 			if(c == '\'')
    274 				c = 0x2019;
    275 		}
    276 		ul = a|c;
    277 
    278 		/*
    279 		 * Turn single quotes into double quotes.
    280 		 */
    281 		if(nchars > 0){
    282 			if(c == 0x2018 && (chars[nchars-1]&0xFFFF) == 0x2018
    283 			&& a==(chars[nchars-1]&~0xFFFF)){
    284 				chars[nchars-1] = (ul&~0xFFFF) | 0x201C;
    285 				return;
    286 			}
    287 			if(c == 0x2019 && (chars[nchars-1]&0xFFFF) == 0x2019
    288 			&& a==(chars[nchars-1]&~0xFFFF)){
    289 				chars[nchars-1] = (ul&~0xFFFF) | 0x201D;
    290 				return;
    291 			}
    292 		}
    293 	}
    294 	chars[nchars++] = ul;
    295 }
    296 
    297 void
    298 emit(Rune r)
    299 {
    300 	emitul(r | attr, 0);
    301 	/*
    302 	 * Close man page references early, so that
    303 	 * .IR proof (1),
    304 	 * doesn't make the comma part of the link.
    305 	 */
    306 	if(r == ')')
    307 		attr &= ~(1<<Anchor);
    308 }
    309 
    310 void
    311 emitstr(char *s)
    312 {
    313 	emitul(Estring | attr, 0);
    314 	emitul((ulong)s, 1);
    315 }
    316 
    317 int indentlevel;
    318 int linelen;
    319 
    320 void
    321 iputrune(Biobuf *b, Rune r)
    322 {
    323 	int i;
    324 
    325 	if(linelen++ > 60 && r == ' ')
    326 		r = '\n';
    327 	if(r >= 0x80)
    328 		Bprint(b, "&#%d;", r);
    329 	else
    330 		Bputrune(b, r);
    331 	if(r == '\n'){
    332 		for(i=0; i<indentlevel; i++)
    333 			Bprint(b, "    ");
    334 		linelen = 0;
    335 	}
    336 }
    337 
    338 void
    339 iputs(Biobuf *b, char *s)
    340 {
    341 	if(s[0]=='<' && s[1]=='+'){
    342 		iputrune(b, '\n');
    343 		Bprint(b, "<%s", s+2);
    344 		indentlevel++;
    345 		iputrune(b, '\n');
    346 	}else if(s[0]=='<' && s[1]=='-'){
    347 		indentlevel--;
    348 		iputrune(b, '\n');
    349 		Bprint(b, "<%s", s+2);
    350 		iputrune(b, '\n');
    351 	}else
    352 		Bprint(b, "%s", s);
    353 }
    354 
    355 void
    356 setattr(ulong a)
    357 {
    358 	int on, off, i, j;
    359 
    360 	on = a & ~attr;
    361 	off = attr & ~a;
    362 
    363 	/* walk up the nest stack until we reach something we need to turn off. */
    364 	for(i=0; i<nnest; i++)
    365 		if(off&(1<<nest[i]))
    366 			break;
    367 
    368 	/* turn off everything above that */
    369 	for(j=nnest-1; j>=i; j--)
    370 		iputs(&bout, offattr[nest[j]]);
    371 
    372 	/* turn on everything we just turned off but didn't want to */
    373 	for(j=i; j<nnest; j++)
    374 		if(a&(1<<nest[j]))
    375 			iputs(&bout, onattr[nest[j]]);
    376 		else
    377 			nest[j] = 0;
    378 
    379 	/* shift the zeros (turned off things) up */
    380 	for(i=j=0; i<nnest; i++)
    381 		if(nest[i] != 0)
    382 			nest[j++] = nest[i];
    383 	nnest = j;
    384 
    385 	/* now turn on the new attributes */
    386 	for(i=0; i<nelem(attrorder); i++){
    387 		j = attrorder[i];
    388 		if(on&(1<<j)){
    389 			if(j == Anchor)
    390 				onattr[j] = anchors[nanchors++];
    391 			iputs(&bout, onattr[j]);
    392 			nest[nnest++] = j;
    393 		}
    394 	}
    395 	attr = a;
    396 }
    397 
    398 void
    399 flush(void)
    400 {
    401 	int i;
    402 	ulong c, a;
    403 
    404 	nanchors = 0;
    405 	for(i=0; i<nchars; i++){
    406 		c = chars[i];
    407 		if(c == Epp){
    408 			iputrune(&bout, '\n');
    409 			iputs(&bout, TABLE "<tr height=5><td></table>");
    410 			iputrune(&bout, '\n');
    411 			continue;
    412 		}
    413 		a = c & ~0xFFFF;
    414 		c &= 0xFFFF;
    415 		/*
    416 		 * If we're going to something off after a space,
    417 		 * let's just turn it off before.
    418 		 */
    419 		if(c==' ' && i<nchars-1 && (chars[i+1]&0xFFFF) >= 32)
    420 			a ^= a & ~chars[i+1];
    421 		setattr(a);
    422 		if(c == Estring){
    423 			/* next word is string to print */
    424 			iputs(&bout, (char*)chars[++i]);
    425 			continue;
    426 		}
    427 		iputrune(&bout, c & 0xFFFF);
    428 	}
    429 }
    430 
    431 void
    432 header(char *s)
    433 {
    434 	char *p;
    435 
    436 	Bprint(&bout, "<head>\n");
    437 	if(pagename && section){
    438 		char buf[512];
    439 		strecpy(buf, buf+sizeof buf, pagename);
    440 		for(p=buf; *p; p++)
    441 			*p = tolower((uchar)*p);
    442 		Bprint(&bout, "<title>%s(%s) - %s</title>\n", buf, section, s);
    443 	}else
    444 		Bprint(&bout, "<title>%s</title>\n", s);
    445 	Bprint(&bout, "<meta content=\"text/html; charset=utf-8\" http-equiv=Content-Type>\n");
    446 	Bprint(&bout, "</head>\n");
    447 	Bprint(&bout, "<body bgcolor=#ffffff>\n");
    448 	Bprint(&bout, "<table border=0 cellpadding=0 cellspacing=0 width=100%%>\n");
    449 	Bprint(&bout, "<tr height=10><td>\n");
    450 	Bprint(&bout, "<tr><td width=20><td>\n");
    451 	if(pagename && section){
    452 		Bprint(&bout, "<tr><td width=20><td><b>%s(%s)</b><td align=right><b>%s(%s)</b>\n",
    453 			pagename, section, pagename, section);
    454 	}
    455 	Bprint(&bout, "<tr><td width=20><td colspan=2>\n");
    456 }
    457 
    458 void
    459 trailer(void)
    460 {
    461 	Bprint(&bout, "<td width=20>\n");
    462 	Bprint(&bout, "<tr height=20><td>\n");
    463 	Bprint(&bout, "</table>\n");
    464 
    465 #ifdef LUCENT
    466     {
    467 	Tm *t;
    468 
    469 	t = localtime(time(nil));
    470 	Bprint(&bout, TABLE "<tr height=20><td></table>\n");
    471 	Bprint(&bout, "<font size=-1><a href=\"http:/*www.lucent.com/copyright.html\">\n"); */
    472 	Bprint(&bout, "Portions Copyright</A> &#169; %d Lucent Technologies.  All rights reserved.</font>\n", t->year+1900);
    473     }
    474 #endif
    475 	Bprint(&bout, "<!-- TRAILER -->\n");
    476 	Bprint(&bout, "</body></html>\n");
    477 }
    478 
    479 int
    480 getc(Biobuf *b)
    481 {
    482 	cno++;
    483 	return Bgetrune(b);
    484 }
    485 
    486 void
    487 ungetc(Biobuf *b)
    488 {
    489 	cno--;
    490 	Bungetrune(b);
    491 }
    492 
    493 char*
    494 getline(Biobuf *b)
    495 {
    496 	int i, c;
    497 
    498 	for(i=0; i<sizeof buf; i++){
    499 		c = getc(b);
    500 		if(c == Beof)
    501 			return nil;
    502 		buf[i] = c;
    503 		if(c == '\n'){
    504 			buf[i] = '\0';
    505 			break;
    506 		}
    507 	}
    508 	return buf;
    509 }
    510 
    511 int
    512 getnum(Biobuf *b)
    513 {
    514 	int i, c;
    515 
    516 	i = 0;
    517 	for(;;){
    518 		c = getc(b);
    519 		if(c<'0' || '9'<c){
    520 			ungetc(b);
    521 			break;
    522 		}
    523 		i = i*10 + (c-'0');
    524 	}
    525 	return i;
    526 }
    527 
    528 char*
    529 getstr(Biobuf *b)
    530 {
    531 	int i, c;
    532 
    533 	for(i=0; i<sizeof buf; i++){
    534 		/* must get bytes not runes */
    535 		cno++;
    536 		c = Bgetc(b);
    537 		if(c == Beof)
    538 			return nil;
    539 		buf[i] = c;
    540 		if(c == '\n' || c==' ' || c=='\t'){
    541 			ungetc(b);
    542 			buf[i] = '\0';
    543 			break;
    544 		}
    545 	}
    546 	return buf;
    547 }
    548 
    549 int
    550 setnum(Biobuf *b, char *name, int min, int max)
    551 {
    552 	int i;
    553 
    554 	i = getnum(b);
    555 	if(debug > 2)
    556 		fprint(2, "set %s = %d\n", name, i);
    557 	if(min<=i && i<max)
    558 		return i;
    559 	sysfatal("value of %s is %d; min %d max %d at %s:#%d", name, i, min, max, filename, cno);
    560 	return i;
    561 }
    562 
    563 void
    564 xcmd(Biobuf *b)
    565 {
    566 	char *p, *fld[16], buf[1024];
    567 
    568 	int i, nfld;
    569 
    570 	p = getline(b);
    571 	if(p == nil)
    572 		sysfatal("xcmd error: %r");
    573 	if(debug)
    574 		fprint(2, "x command '%s'\n", p);
    575 	nfld = tokenize(p, fld, nelem(fld));
    576 	if(nfld == 0)
    577 		return;
    578 	switch(fld[0][0]){
    579 	case 'f':
    580 		/* mount font */
    581 		if(nfld != 3)
    582 			break;
    583 		i = atoi(fld[1]);
    584 		if(i<0 || Nfont<=i)
    585 			sysfatal("font %d out of range at %s:#%d", i, filename, cno);
    586 		mountfont(i, fld[2]);
    587 		return;
    588 	case 'i':
    589 		/* init */
    590 		return;
    591 	case 'r':
    592 		if(nfld<2 || atoi(fld[1])!=res)
    593 			sysfatal("typesetter has unexpected resolution %s", fld[1]? fld[1] : "<unspecified>");
    594 		return;
    595 	case 's':
    596 		/* stop */
    597 		return;
    598 	case 't':
    599 		/* trailer */
    600 		return;
    601 	case 'T':
    602 		if(nfld!=2 || strcmp(fld[1], "utf")!=0)
    603 			sysfatal("output for unknown typesetter type %s", fld[1]);
    604 		return;
    605 	case 'X':
    606 		if(nfld<3 || strcmp(fld[1], "html")!=0)
    607 			break;
    608 		/* is it a man reference of the form cp(1)? */
    609 		/* X manref start/end cp (1) */
    610 		if(nfld==6 && strcmp(fld[2], "manref")==0){
    611 			/* was the right macro; is it the right form? */
    612 			if(strlen(fld[5])>=3 &&
    613 			   fld[5][0]=='('/*)*/ && (fld[5][2]==/*(*/')' || (isalpha((uchar)fld[5][2]) && fld[5][3]==/*(*/')')) &&
    614 			   '0'<=fld[5][1] && fld[5][1]<='9'){
    615 				if(strcmp(fld[3], "start") == 0){
    616 					/* set anchor attribute and remember string */
    617 					attr |= (1<<Anchor);
    618 #if 0
    619 					snprint(buf, sizeof buf,
    620 						"<a href=\"/magic/man2html/man%c/%s\">",
    621 						fld[5][1], fld[4]);
    622 #else
    623 					snprint(buf, sizeof buf,
    624 						"<a href=\"../man%c/%s.html\">", fld[5][1], fld[4]);
    625 					for(p=buf; *p; p++)
    626 						if('A' <= *p && *p <= 'Z')
    627 							*p += 'a'-'A';
    628 #endif
    629 					nanchors++;
    630 					anchors = erealloc(anchors, nanchors*sizeof(char*));
    631 					anchors[nanchors-1] = estrdup(buf);
    632 				}else if(strcmp(fld[3], "end") == 0)
    633 					attr &= ~(1<<Anchor);
    634 			}
    635 		}else if(nfld >= 4 && strcmp(fld[2], "href") == 0){
    636 			attr |= 1<<Anchor;
    637 			nanchors++;
    638 			anchors = erealloc(anchors, nanchors*sizeof(char*));
    639 			anchors[nanchors-1] = smprint("<a href=\"%s\">", fld[3]);
    640 		}else if(strcmp(fld[2], "/href") == 0){
    641 			attr &= ~(1<<Anchor);
    642 		}else if(strcmp(fld[2], "manPP") == 0){
    643 			didP = 1;
    644 			emitul(Epp, 1);
    645 		}else if(nfld>=5 && strcmp(fld[2], "manhead") == 0){
    646 			pagename = strdup(fld[3]);
    647 			section = strdup(fld[4]);
    648 		}else if(nfld<4 || strcmp(fld[2], "manref")!=0){
    649 			if(nfld>2 && strcmp(fld[2], "<P>")==0){	/* avoid triggering extra <br> */
    650 				didP = 1;
    651 				/* clear all font attributes before paragraph */
    652 				emitul(' ' | (attr & ~(0xFFFF|((1<<Italic)|(1<<Bold)|(1<<CW)))), 0);
    653 				emitstr("<P>");
    654 				/* next emittec char will turn font attributes back on */
    655 			}else if(nfld>2 && strcmp(fld[2], "<H4>")==0)
    656 				attr |= (1<<Heading);
    657 			else if(nfld>2 && strcmp(fld[2], "</H4>")==0)
    658 				attr &= ~(1<<Heading);
    659 			else if(debug)
    660 				fprint(2, "unknown in-line html %s... at %s:%#d\n",
    661 					fld[2], filename, cno);
    662 		}
    663 		return;
    664 	}
    665 	if(debug)
    666 		fprint(2, "unknown or badly formatted x command %s\n", fld[0]);
    667 }
    668 
    669 int
    670 lookup(int c, Htmlchar tab[], int ntab)
    671 {
    672 	int low, high, mid;
    673 
    674 	low = 0;
    675 	high = ntab - 1;
    676 	while(low <= high){
    677 		mid = (low+high)/2;
    678 		if(c < tab[mid].value)
    679 			high = mid - 1;
    680 		else if(c > tab[mid].value)
    681 			low = mid + 1;
    682 		else
    683 			return mid;
    684 	}
    685 	return -1;	/* no match */
    686 }
    687 
    688 void
    689 emithtmlchar(int r)
    690 {
    691 	int i;
    692 
    693 	i = lookup(r, htmlchars, nelem(htmlchars));
    694 	if(i >= 0)
    695 		emitstr(htmlchars[i].name);
    696 	else
    697 		emit(r);
    698 }
    699 
    700 char*
    701 troffchar(char *s)
    702 {
    703 	int i;
    704 
    705 	for(i=0; troffchars[i].name!=nil; i++)
    706 		if(strcmp(s, troffchars[i].name) == 0)
    707 			return troffchars[i].value;
    708 	return strdup(s);
    709 }
    710 
    711 void
    712 indent(void)
    713 {
    714 	int nind;
    715 
    716 	didP = 0;
    717 	if(atnewline){
    718 		if(hp != prevlineH){
    719 			prevlineH = hp;
    720 			/* these most peculiar numbers appear in the troff -man output */
    721 			nind = ((prevlineH-1*res)+323)/324;
    722 			attr &= ~((1<<Indent1)|(1<<Indent2)|(1<<Indent3));
    723 			if(nind >= 1)
    724 				attr |= (1<<Indent1);
    725 			if(nind >= 2)
    726 				attr |= (1<<Indent2);
    727 			if(nind >= 3)
    728 				attr |= (1<<Indent3);
    729 		}
    730 		atnewline = 0;
    731 	}
    732 }
    733 
    734 void
    735 process(Biobuf *b, char *name)
    736 {
    737 	int c, r, v, i;
    738 	char *p;
    739 
    740 	cno = 0;
    741 	prevlineH = res;
    742 	filename = name;
    743 	for(;;){
    744 		c = getc(b);
    745 		switch(c){
    746 		case Beof:
    747 			/* go to ground state */
    748 			attr = 0;
    749 			emit('\n');
    750 			return;
    751 		case '\n':
    752 			break;
    753 		case '0': case '1': case '2': case '3': case '4':
    754 		case '5': case '6': case '7': case '8': case '9':
    755 			v = c-'0';
    756 			c = getc(b);
    757 			if(c<'0' || '9'<c)
    758 				sysfatal("illegal character motion at %s:#%d", filename, cno);
    759 			v = v*10 + (c-'0');
    760 			hp += v;
    761 			/* fall through to character case */
    762 		case 'c':
    763 			indent();
    764 			r = getc(b);
    765 			emithtmlchar(r);
    766 			break;
    767 		case 'D':
    768 			/* draw line; ignore */
    769 			do
    770 				c = getc(b);
    771 			while(c!='\n' && c!= Beof);
    772 			break;
    773 		case 'f':
    774 			v = setnum(b, "font", 0, Nfont);
    775 			switchfont(v);
    776 			break;
    777 		case 'h':
    778 			v = setnum(b, "hpos", -20000, 20000);
    779 			/* generate spaces if motion is large and within a line */
    780 			if(!atnewline && v>2*72)
    781 				for(i=0; i<v; i+=72)
    782 					emitstr("&nbsp;");
    783 			hp += v;
    784 			break;
    785 		case 'n':
    786 			setnum(b, "n1", -10000, 10000);
    787 			/*Bprint(&bout, " N1=%d", v); */
    788 			getc(b);	/* space separates */
    789 			setnum(b, "n2", -10000, 10000);
    790 			atnewline = 1;
    791 			if(!didP && hp < (Wid-1)*res)	/* if line is less than 19" long, probably need a line break */
    792 				emitstr("<br>");
    793 			emit('\n');
    794 			break;
    795 		case 'p':
    796 			page = setnum(b, "ps", -10000, 10000);
    797 			break;
    798 		case 's':
    799 			ps = setnum(b, "ps", 1, 1000);
    800 			break;
    801 		case 'v':
    802 			vp += setnum(b, "vpos", -10000, 10000);
    803 			/* BUG: ignore motion */
    804 			break;
    805 		case 'x':
    806 			xcmd(b);
    807 			break;
    808 		case 'w':
    809 			emit(' ');
    810 			break;
    811 		case 'C':
    812 			indent();
    813 			p = getstr(b);
    814 			emitstr(troffchar(p));
    815 			break;
    816 		case 'H':
    817 			hp = setnum(b, "hpos", 0, 20000);
    818 			/*Bprint(&bout, " H=%d ", hp); */
    819 			break;
    820 		case 'V':
    821 			vp = setnum(b, "vpos", 0, 10000);
    822 			break;
    823 		default:
    824 			fprint(2, "dhtml: unknown directive %c(0x%.2ux) at %s:#%d\n", c, c, filename, cno);
    825 			return;
    826 		}
    827 	}
    828 }
    829 
    830 HTMLfont*
    831 htmlfont(char *name)
    832 {
    833 	int i;
    834 
    835 	for(i=0; htmlfonts[i].name!=nil; i++)
    836 		if(strcmp(name, htmlfonts[i].name) == 0)
    837 			return &htmlfonts[i];
    838 	return &htmlfonts[0];
    839 }
    840 
    841 void
    842 mountfont(int pos, char *name)
    843 {
    844 	if(debug)
    845 		fprint(2, "mount font %s on %d\n", name, pos);
    846 	if(font[pos] != nil){
    847 		free(font[pos]->name);
    848 		free(font[pos]);
    849 	}
    850 	font[pos] = emalloc(sizeof(Font));
    851 	font[pos]->name = estrdup(name);
    852 	font[pos]->htmlfont = htmlfont(name);
    853 }
    854 
    855 void
    856 switchfont(int pos)
    857 {
    858 	HTMLfont *hf;
    859 
    860 	if(debug)
    861 		fprint(2, "font change from %d (%s) to %d (%s)\n", ft, font[ft]->name, pos, font[pos]->name);
    862 	if(pos == ft)
    863 		return;
    864 	hf = font[ft]->htmlfont;
    865 	if(hf->bit != 0)
    866 		attr &= ~(1<<hf->bit);
    867 	ft = pos;
    868 	hf = font[ft]->htmlfont;
    869 	if(hf->bit != 0)
    870 		attr |= (1<<hf->bit);
    871 }