troff2html.c 14 KB

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