troff2html.c 14 KB

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