]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/mothra/rdhtml.c
mothra: fix alt display resizing, filter control characters in panel entries, use...
[plan9front.git] / sys / src / cmd / mothra / rdhtml.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 #include <panel.h>
6 #include "mothra.h"
7 #include "html.h"
8 #include "rtext.h"
9
10 typedef struct Fontdata Fontdata;
11 struct Fontdata{
12         char *name;
13         Font *font;
14         int space;
15 }fontlist[4][4]={
16         "lucidasans/unicode.7", 0, 0,
17         "lucidasans/unicode.8", 0, 0,
18         "lucidasans/unicode.10", 0, 0,
19         "lucidasans/unicode.13", 0, 0,
20
21         "lucidasans/italicunicode.7", 0, 0,
22         "lucidasans/italicunicode.8", 0, 0,
23         "lucidasans/italicunicode.10", 0, 0,
24         "lucidasans/italicunicode.13", 0, 0,
25
26         "lucidasans/boldunicode.7", 0, 0,
27         "lucidasans/boldunicode.8", 0, 0,
28         "lucidasans/boldunicode.10", 0, 0,
29         "lucidasans/boldunicode.13", 0, 0,
30
31         "lucidasans/typeunicode.7", 0, 0,
32         "pelm/unicode.8", 0, 0,
33         "lucidasans/typeunicode.12", 0, 0,
34         "lucidasans/typeunicode.16", 0, 0,
35 };
36 Fontdata *pl_whichfont(int f, int s){
37         char name[NNAME];
38
39         assert(f >= 0 && f < 4);
40         assert(s >= 0 && s < 4);
41
42         if(fontlist[f][s].font==0){
43                 snprint(name, sizeof(name), "/lib/font/bit/%s.font", fontlist[f][s].name);
44                 fontlist[f][s].font=openfont(display, name);
45                 if(fontlist[f][s].font==0) fontlist[f][s].font=font;
46                 fontlist[f][s].space=stringwidth(fontlist[f][s].font, "0");
47         }
48         return &fontlist[f][s];
49         
50 }
51 void getfonts(void){
52         int f, s;
53         for(f=0;f!=4;f++)
54                 for(s=0;s!=4;s++)
55                         pl_whichfont(f, s);
56 }
57 void pl_pushstate(Hglob *g, int t){
58         ++g->state;
59         if(g->state==&g->stack[NSTACK]){
60                 htmlerror(g->name, g->lineno, "stack overflow at <%s>", tag[t].name);
61                 --g->state;
62         }
63         g->state[0]=g->state[-1];
64         g->state->tag=t;
65 }
66 void pl_linespace(Hglob *g){
67         plrtbitmap(&g->dst->text, 1000000, 0, linespace, 0, 0);
68         g->para=0;
69         g->linebrk=0;
70 }
71 enum{
72         HORIZ,
73         VERT,
74 };
75 int strtolength(Hglob *g, int dir, char *str)
76 {
77         double f;
78
79         f = atof(str);
80         if(cistrstr(str, "px"))
81                 return floor(f);
82         if(cistrstr(str, "%"))
83                 return floor(f*((dir==HORIZ) ? Dx(g->dst->text->r) : Dy(g->dst->text->r))/100);
84         if(cistrstr(str, "em")){
85                 Point z;
86                 z = stringsize(g->dst->text->font, "M");
87                 return floor(f*((dir==HORIZ) ? z.x : z.y));
88         }
89         return floor(f);
90 }
91
92 void pl_htmloutput(Hglob *g, int nsp, char *s, Field *field){
93         Fontdata *f;
94         int space, indent;
95         Action *ap;
96         if(g->state->tag==Tag_title
97 /*      || g->state->tag==Tag_textarea */
98         || g->state->tag==Tag_select){
99                 if(s){
100                         if(g->tp!=g->text && g->tp!=g->etext && g->tp[-1]!=' ')
101                                 *g->tp++=' ';
102                         while(g->tp!=g->etext && *s) *g->tp++=*s++;
103                         if(g->state->tag==Tag_title) g->dst->changed=1;
104                         *g->tp='\0';
105                 }
106                 return;
107         }
108         f=pl_whichfont(g->state->font, g->state->size);
109         space=f->space;
110         indent=g->state->margin;
111         if(g->para){
112                 space=1000000;
113                 indent+=g->state->indent;
114         }
115         else if(g->linebrk)
116                 space=1000000;
117         else if(nsp<=0)
118                 space=0;
119         if(g->state->image[0]==0 && g->state->link[0]==0 && g->state->name[0]==0 && field==0)
120                 ap=0;
121         else{
122                 ap=mallocz(sizeof(Action), 1);
123                 if(ap!=0){
124                         if(g->state->image[0])
125                                 ap->image = strdup(g->state->image);
126                         if(g->state->link[0])
127                                 ap->link = strdup(g->state->link);
128                         if(g->state->name[0])
129                                 ap->name = strdup(g->state->name);
130                         ap->ismap=g->state->ismap;
131                         ap->width=g->state->width;
132                         ap->height=g->state->height;
133                         ap->field=field;
134                 }
135         }
136         if(space<0) space=0;
137         if(indent<0) indent=0;
138         if(g->state->pre && s[0]=='\t'){
139                 space=0;
140                 while(s[0]=='\t'){
141                         space++;
142                         s++;
143                 }
144                 space=PL_TAB|space;
145                 if(g->linebrk){
146                         indent=space;
147                         space=1000000;
148                 }
149         }
150         plrtstr(&g->dst->text, space, indent, f->font, strdup(s),
151                 g->state->link[0] || g->state->image[0], ap);
152         g->para=0;
153         g->linebrk=0;
154         g->dst->changed=1;
155 }
156
157 /*
158  * Buffered read, no translation
159  * Save in cache.
160  */
161 int pl_bread(Hglob *g){
162         int n, c;
163         char err[1024];
164         if(g->hbufp==g->ehbuf){
165                 n=read(g->hfd, g->hbuf, NHBUF);
166                 if(n<=0){
167                         if(n<0){
168                                 snprint(err, sizeof(err), "%r reading %s", g->name);
169                                 pl_htmloutput(g, 1, err, 0);
170                         }
171                         g->heof=1;
172                         return EOF;
173                 }
174                 g->hbufp=g->hbuf;
175                 g->ehbuf=g->hbuf+n;
176         }
177         c=*g->hbufp++&255;
178         if(c=='\n') g->lineno++;
179         return c;
180 }
181 /*
182  * Read a character, translating \r\n, \n\r, \r and \n into \n
183  */
184 int pl_readc(Hglob *g){
185         int c;
186         static int peek=-1;
187         if(peek!=-1){
188                 c=peek;
189                 peek=-1;
190         }
191         else
192                 c=pl_bread(g);
193         if(c=='\r'){
194                 c=pl_bread(g);
195                 if(c!='\n') peek=c;
196                 return '\n';
197         }
198         if(c=='\n'){
199                 c=pl_bread(g);
200                 if(c!='\r') peek=c;
201                 return '\n';
202         }
203         return c;
204 }
205 void pl_putback(Hglob *g, int c){
206         if(g->npeekc==NPEEKC) htmlerror(g->name, g->lineno, "too much putback!");
207         else if(c!=EOF) g->peekc[g->npeekc++]=c;
208 }
209 int pl_nextc(Hglob *g){
210         int c;
211         int n;
212         Rune r;
213         char crune[UTFmax+1];
214         if(g->heof) return EOF;
215         if(g->npeekc!=0) return g->peekc[--g->npeekc];
216         c=pl_readc(g);
217         if(c=='<'){
218                 c=pl_readc(g);
219                 if(c=='/'){
220                         c=pl_readc(g);
221                         pl_putback(g, c);
222                         pl_putback(g, '/');
223                         if('a'<=c && c<='z' || 'A'<=c && c<='Z') return STAG;
224                         return '<';
225                 }
226                 pl_putback(g, c);
227                 if(c=='!' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || c=='?') return STAG;
228                 return '<';
229         }
230         if(c=='>') return ETAG;
231         if(c==EOF) return c;
232         for (n=1; n<=sizeof(crune); n++){
233                 crune[n-1]=c;
234                 if(fullrune(crune, n)){
235                         chartorune(&r, crune);
236                         return r;
237                 }
238                 c=pl_readc(g);
239                 if(c==EOF)
240                         return EOF;
241         }
242         return c;
243 }
244 int entchar(int c){
245         return c=='#' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9';
246 }
247 /*
248  * remove entity references, in place.
249  * Potential bug:
250  *      This doesn't work if removing an entity reference can lengthen the string!
251  *      Fortunately, this doesn't happen.
252  */
253 void pl_rmentities(Hglob *g, char *s){
254         char *t, *u, c, svc;
255         Entity *ep;
256         Rune r;
257         t=s;
258         do{
259                 c=*s++;
260                 if(c=='&'
261                 && ((*s=='#' && strchr("0123456789Xx", s[1]))
262                   || 'a'<=*s && *s<='z'
263                   || 'A'<=*s && *s<='Z')){
264                         u=s;
265                         while(entchar(*s)) s++;
266                         svc=*s;
267                         *s = 0;
268                         if(svc==';') s++;
269                         if(strcmp(u, "lt") == 0)
270                                 *t++='<';
271                         else if(strcmp(u, "gt") == 0)
272                                 *t++='>';
273                         else if(strcmp(u, "quot") == 0)
274                                 *t++='"';
275                         else if(strcmp(u, "apos") == 0)
276                                 *t++='\'';
277                         else if(strcmp(u, "amp") == 0)
278                                 *t++='&';
279                         else {
280                                 if(svc==';') s--;
281                                 *s=svc;
282                                 *t++='&';
283                                 while(u<s)
284                                         *t++=*u++;
285                         }
286                 }       
287                 else *t++=c;
288         }while(c);
289 }
290 /*
291  * Skip over white space
292  */
293 char *pl_white(char *s){
294         while(*s==' ' || *s=='\t' || *s=='\n' || *s=='\r') s++;
295         return s;
296 }
297 /*
298  * Skip over HTML word
299  */
300 char *pl_word(char *s){
301         if ('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z') {
302                 s++;
303                 while('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z' || '0'<=*s && *s<='9' || *s=='-' || *s=='.') s++;
304         }
305         return s;
306 }
307 /*
308  * Skip to matching quote
309  */
310 char *pl_quote(char *s){
311         char q;
312         q=*s++;
313         while(*s!=q && *s!='\0') s++;
314         return s;
315 }
316 void pl_dnl(char *s){
317         char *t;
318         for(t=s;*s;s++) if(*s!='\r' && *s!='\n') *t++=*s;
319         *t='\0';
320 }
321 void pl_tagparse(Hglob *g, char *str){
322         char *s, *t, *name, c;
323         Pair *ap;
324         Tag *tagp;
325         g->tag=Tag_end;
326         ap=g->attr;
327         if(str[0]=='!'){        /* test should be strncmp(str, "!--", 3)==0 */
328                 g->tag=Tag_comment;
329                 ap->name=0;
330                 return;
331         }
332         if(str[0]=='/') str++;
333         name=str;
334         s=pl_word(str);
335         if(*s!=' ' && *s!='\n' && *s!='\t' && *s!='\0'){
336                 htmlerror(g->name, g->lineno, "bad tag name in %s", str);
337                 ap->name=0;
338                 return;
339         }
340         if(*s!='\0') *s++='\0';
341         for(t=name;t!=s;t++) if('A'<=*t && *t<='Z') *t+='a'-'A';
342         /*
343          * Binary search would be faster here
344          */
345         for(tagp=tag;tagp->name;tagp++) if(strcmp(name, tagp->name)==0) break;
346         g->tag=tagp-tag;
347         if(g->tag==Tag_end) htmlerror(g->name, g->lineno, "no tag %s", name);
348         for(;;){
349                 s=pl_white(s);
350                 if(*s=='\0'){
351                         ap->name=0;
352                         return;
353                 }
354                 ap->name=s;
355                 s=pl_word(s);
356                 t=pl_white(s);
357                 c=*t;
358                 *s='\0';
359                 for(s=ap->name;*s;s++) if('A'<=*s && *s<='Z') *s+='a'-'A';
360                 if(c=='='){
361                         s=pl_white(t+1);
362                         if(*s=='\'' || *s=='"'){
363                                 ap->value=s+1;
364                                 s=pl_quote(s);
365                                 if(*s=='\0'){
366                                         htmlerror(g->name, g->lineno,
367                                                 "No terminating quote in rhs of attribute %s",
368                                                 ap->name);
369                                         ap->name=0;
370                                         return;
371                                 }
372                                 *s++='\0';
373                                 pl_dnl(ap->value);
374                         }
375                         else{
376                                 /* read up to white space or > */
377                                 ap->value=s;
378                                 while(*s!=' ' && *s!='\t' && *s!='\n' && *s!='\0') s++;
379                                 if(*s!='\0') *s++='\0';
380                         }
381                         pl_rmentities(g, ap->value);
382                 }
383                 else{
384                         if(c!='\0') s++;
385                         ap->value="";
386                 }
387                 if(ap==&g->attr[NATTR-1])
388                         htmlerror(g->name, g->lineno, "too many attributes!");
389                 else ap++;
390         }
391 }
392 int pl_getcomment(Hglob *g){
393         int c;
394         if((c=pl_nextc(g))=='-' && (c=pl_nextc(g))=='-'){
395                 /* <!-- eats everything until --> or EOF */
396                 for(;;){
397                         while((c=pl_nextc(g))!='-' && c!=EOF)
398                                 ;
399                         if(c==EOF)
400                                 break;
401                         if((c=pl_nextc(g))=='-'){
402                                 while((c=pl_nextc(g))=='-')
403                                         ;
404                                 if(c==ETAG || c==EOF)
405                                         break;
406                         }
407                 }
408         } else {
409                 /* <! eats everything until > or EOF */
410                 while(c!=ETAG && c!=EOF)
411                         c=pl_nextc(g);
412         }
413         if(c==EOF)
414                 htmlerror(g->name, g->lineno, "EOF in comment");
415         g->tag=Tag_comment;
416         g->attr->name=0;
417         g->token[0]='\0';
418         return TAG;
419 }
420 int lrunetochar(char *p, int v)
421 {
422         Rune r;
423
424         r=v;
425         return runetochar(p, &r);
426 }
427
428 /*
429  * Read a start or end tag -- the caller has read the initial <
430  */
431 int pl_gettag(Hglob *g){
432         char *tokp;
433         int c;
434         tokp=g->token;
435         if((c=pl_nextc(g))=='!' || c=='?')
436                 return pl_getcomment(g);
437         pl_putback(g, c);
438         while((c=pl_nextc(g))!=ETAG && c!=EOF)
439                 if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
440         *tokp='\0';
441         if(c==EOF) htmlerror(g->name, g->lineno, "EOF in tag");
442         pl_tagparse(g, g->token);
443         if(g->token[0]!='/') return TAG;
444         if(g->attr[0].name!=0)
445                 htmlerror(g->name, g->lineno, "end tag should not have attributes");
446         return ENDTAG;
447 }
448 /*
449  * The next token is a tag, an end tag or a sequence of
450  * non-white characters.
451  * If inside <pre>, newlines are converted to <br> and spaces are preserved.
452  * Otherwise, spaces and newlines are noted and discarded.
453  */
454 int pl_gettoken(Hglob *g){
455         char *tokp;
456         int c;
457         if(g->state->pre) switch(c=pl_nextc(g)){
458         case STAG: return pl_gettag(g);
459         case EOF: return EOF;
460         case '\n':
461                 pl_tagparse(g, "br");
462                 return TAG;
463         default:
464                 tokp=g->token;
465                 while(c=='\t'){
466                         if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
467                         c=pl_nextc(g);
468                 }
469                 while(c!='\t' && c!='\n' && c!=STAG && c!=EOF){
470                         if(c==ETAG) c='>';
471                         if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
472                         c=pl_nextc(g);
473                 }
474                 *tokp='\0';
475                 pl_rmentities(g, g->token);
476                 pl_putback(g, c);
477                 g->nsp=0;
478                 g->spacc=0;
479                 return TEXT;
480         }
481         while((c=pl_nextc(g))==' ' || c=='\t' || c=='\n')
482                 if(g->spacc!=-1)
483                         g->spacc++;
484         switch(c){
485         case STAG: return pl_gettag(g);
486         case EOF: return EOF;
487         default:
488                 tokp=g->token;
489                 do{
490                         if(c==ETAG) c='>';
491                         if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
492                         c=pl_nextc(g);
493                 }while(c!=' ' && c!='\t' && c!='\n' && c!=STAG && c!=EOF);
494                 *tokp='\0';
495                 pl_rmentities(g, g->token);
496                 pl_putback(g, c);
497                 g->nsp=g->spacc;
498                 g->spacc=0;
499                 return TEXT;
500         }
501 }
502 char *pl_getattr(Pair *attr, char *name){
503         for(;attr->name;attr++)
504                 if(strcmp(attr->name, name)==0)
505                         return attr->value;
506         return 0;
507 }
508 int pl_hasattr(Pair *attr, char *name){
509         for(;attr->name;attr++)
510                 if(strcmp(attr->name, name)==0)
511                         return 1;
512         return 0;
513 }
514 void plaintext(Hglob *g){
515         char line[NLINE];
516         char *lp, *elp;
517         int c;
518         g->state->font=CWIDTH;
519         g->state->size=NORMAL;
520         elp=&line[NLINE-UTFmax-1];
521         lp=line;
522         for(;;){
523                 c=pl_readc(g);
524                 if(c==EOF) break;
525                 if(c=='\n' || lp>=elp){
526                         *lp='\0';
527                         g->linebrk=1;
528                         pl_htmloutput(g, 0, line, 0);
529                         lp=line;
530                 }
531                 if(c=='\t'){
532                         do *lp++=' '; while(lp<elp && utfnlen(line, lp-line)%8!=0);
533                 }
534                 else if(c!='\n')
535                         lp += lrunetochar(lp, c);
536         }
537         if(lp!=line){
538                 *lp='\0';
539                 g->linebrk=1;
540                 pl_htmloutput(g, 0, line, 0);
541         }
542 }
543 void plrdplain(char *name, int fd, Www *dst){
544         Hglob g;
545         g.state=g.stack;
546         g.state->tag=Tag_html;
547         g.state->font=CWIDTH;
548         g.state->size=NORMAL;
549         g.state->pre=0;
550         g.state->image[0]=0;
551         g.state->link[0]=0;
552         g.state->name[0]=0;
553         g.state->margin=0;
554         g.state->indent=20;
555         g.state->ismap=0;
556         g.state->table=0;
557         g.dst=dst;
558         g.hfd=fd;
559         g.name=name;
560         g.ehbuf=g.hbufp=g.hbuf;
561         g.npeekc=0;
562         g.heof=0;
563         g.lineno=1;
564         g.linebrk=1;
565         g.para=0;
566         g.text=dst->title;
567         g.tp=g.text;
568         g.etext=g.text+NTITLE-1;
569         g.spacc=0;
570         g.form=0;
571         strncpy(g.text, name, NTITLE);
572         plaintext(&g);
573         dst->finished=1;
574 }
575 void plrdhtml(char *name, int fd, Www *dst){
576         Stack *sp;
577         char buf[20];
578         char *str;
579         Hglob g;
580         int t;
581         int tagerr;
582
583         g.state=g.stack;
584         g.state->tag=Tag_html;
585         g.state->font=ROMAN;
586         g.state->size=NORMAL;
587         g.state->pre=0;
588         g.state->image[0]=0;
589         g.state->link[0]=0;
590         g.state->name[0]=0;
591         g.state->margin=0;
592         g.state->indent=25;
593         g.state->ismap=0;
594         g.state->width=0;
595         g.state->height=0;
596         g.state->table=0;
597         g.dst=dst;
598         g.hfd=fd;
599         g.name=name;
600         g.ehbuf=g.hbufp=g.hbuf;
601         g.npeekc=0;
602         g.heof=0;
603         g.lineno=1;
604         g.linebrk=1;
605         g.para=0;
606         g.text=dst->title;
607         g.tp=g.text;
608         g.etext=g.text+NTITLE-1;
609         dst->title[0]='\0';
610         g.spacc=0;
611         g.form=0;
612
613         for(;;) switch(pl_gettoken(&g)){
614         case TAG:
615                 switch(tag[g.tag].action){
616                 case OPTEND:
617                         for(sp=g.state;sp!=g.stack && sp->tag!=g.tag;--sp);
618                         if(sp->tag!=g.tag)
619                                 pl_pushstate(&g, g.tag);
620                         else
621                                 for(;g.state!=sp;--g.state)
622                                         if(tag[g.state->tag].action!=OPTEND)
623                                                 htmlerror(g.name, g.lineno,
624                                                         "end tag </%s> missing",
625                                                         tag[g.state->tag].name);
626                         break;
627                 case END:
628                         pl_pushstate(&g, g.tag);
629                         break;
630                 }
631                 if(str=pl_getattr(g.attr, "id")){
632                         char swap[NNAME];
633
634                         strncpy(swap, g.state->name, sizeof(swap));
635                         strncpy(g.state->name, str, sizeof(g.state->name));
636                         pl_htmloutput(&g, 0, "", 0);
637                         strncpy(g.state->name, swap, sizeof(g.state->name));
638                 }
639                 switch(g.tag){
640                 default:
641                         htmlerror(g.name, g.lineno,
642                                 "unimplemented tag <%s>", tag[g.tag].name);
643                         break;
644                 case Tag_end:   /* unrecognized start tag */
645                         break;
646                 case Tag_meta:
647                         break;
648                 case Tag_img:
649                         if(str=pl_getattr(g.attr, "src"))
650                                 strncpy(g.state->image, str, sizeof(g.state->image));
651                         g.state->ismap=pl_hasattr(g.attr, "ismap");
652                         if(str=pl_getattr(g.attr, "width"))
653                                 g.state->width = strtolength(&g, HORIZ, str);
654                         if(str=pl_getattr(g.attr, "height"))
655                                 g.state->height = strtolength(&g, VERT, str);
656                         str=pl_getattr(g.attr, "alt");
657                         if(str==0){
658                                 if(g.state->image[0])
659                                         str=g.state->image;
660                                 else
661                                         str="[[image]]";
662                         }
663                         pl_htmloutput(&g, 0, str, 0);
664                         g.state->image[0]=0;
665                         g.state->ismap=0;
666                         g.state->width=0;
667                         g.state->height=0;
668                         break;
669                 case Tag_plaintext:
670                         g.spacc=0;
671                         plaintext(&g);
672                         break;
673                 case Tag_comment:
674                 case Tag_html:
675                 case Tag_link:
676                 case Tag_nextid:
677                         break;
678                 case Tag_table:
679                         g.state->table++;
680                         break;
681                 case Tag_tr:
682                         if(g.state->table==1){
683                                 g.spacc=0;
684                                 g.linebrk=1;
685                         } else
686                                 g.spacc++;
687                         break;
688                 case Tag_td:
689                         g.spacc++;
690                         break;
691                 case Tag_a:
692                         if(str=pl_getattr(g.attr, "href"))
693                                 strncpy(g.state->link, str, sizeof(g.state->link));
694                         if(str=pl_getattr(g.attr, "name")){
695                                 strncpy(g.state->name, str, sizeof(g.state->name));
696                                 pl_htmloutput(&g, 0, "", 0);
697                         }
698                         break;
699                 case Tag_frame:
700                         pl_htmloutput(&g, 0, "FRAME: ", 0);
701                         if(str=pl_getattr(g.attr, "src"))
702                                 strncpy(g.state->link, str, sizeof(g.state->link));
703                         if(str=pl_getattr(g.attr, "name"))
704                                 strncpy(g.state->name, str, sizeof(g.state->name));
705                         else
706                                 str = g.state->link;
707                         pl_htmloutput(&g, 0, str, 0);
708                         g.state->link[0]=0;
709                         g.state->name[0] =0;
710                         g.spacc=0;
711                         g.linebrk=1;
712                         break;
713                 case Tag_address:
714                         g.spacc=0;
715                         g.linebrk=1;
716                         g.state->font=ROMAN;
717                         g.state->size=NORMAL;
718                         g.state->margin=300;
719                         g.state->indent=50;
720                         break;
721                 case Tag_b:
722                 case Tag_strong:
723                         g.state->font=BOLD;
724                         break;
725                 case Tag_blockquot:
726                         g.spacc=0;
727                         g.linebrk=1;
728                         g.state->margin+=50;
729                         g.state->indent=20;
730                         break;
731                 case Tag_body:
732                         break;
733                 case Tag_head:
734                         g.state->font=ROMAN;
735                         g.state->size=NORMAL;
736                         g.state->margin=0;
737                         g.state->indent=20;
738                         g.spacc=0;
739                         break;
740                 case Tag_br:
741                         g.spacc=0;
742                         g.linebrk=1;
743                         break;
744                 case Tag_center:
745                         /* more to come */
746                         break;
747                 case Tag_cite:
748                         g.state->font=ITALIC;
749                         g.state->size=NORMAL;
750                         break;
751                 case Tag_code:
752                         g.state->font=CWIDTH;
753                         g.state->size=NORMAL;
754                         break;
755                 case Tag_dd:
756                         g.linebrk=1;
757                         g.state->indent=0;
758                         g.state->font=ROMAN;
759                         g.spacc=0;
760                         break;
761                 case Tag_dfn:
762                         htmlerror(g.name, g.lineno, "<dfn> deprecated");
763                         g.state->font=BOLD;
764                         g.state->size=NORMAL;
765                         break;
766                 case Tag_dl:
767                         g.state->font=BOLD;
768                         g.state->size=NORMAL;
769                         g.state->margin+=40;
770                         g.spacc=0;
771                         break;
772                 case Tag_dt:
773                         g.para=1;
774                         g.state->indent=-40;
775                         g.state->font=BOLD;
776                         g.spacc=0;
777                         break;
778                 case Tag_font:
779                         /* more to come */
780                         break;
781                 case Tag_u:
782                         htmlerror(g.name, g.lineno, "<u> deprecated");
783                 case Tag_em:
784                 case Tag_i:
785                 case Tag_var:
786                         g.state->font=ITALIC;
787                         break;
788                 case Tag_h1:
789                         g.linebrk=1;
790                         g.state->font=BOLD;
791                         g.state->size=ENORMOUS;
792                         g.state->margin+=100;
793                         g.spacc=0;
794                         break;
795                 case Tag_h2:
796                         pl_linespace(&g);
797                         g.state->font=BOLD;
798                         g.state->size=ENORMOUS;
799                         g.spacc=0;
800                         break;
801                 case Tag_h3:
802                         g.linebrk=1;
803                         pl_linespace(&g);
804                         g.state->font=ITALIC;
805                         g.state->size=ENORMOUS;
806                         g.state->margin+=20;
807                         g.spacc=0;
808                         break;
809                 case Tag_h4:
810                         pl_linespace(&g);
811                         g.state->font=BOLD;
812                         g.state->size=LARGE;
813                         g.state->margin+=10;
814                         g.spacc=0;
815                         break;
816                 case Tag_h5:
817                         pl_linespace(&g);
818                         g.state->font=ITALIC;
819                         g.state->size=LARGE;
820                         g.state->margin+=10;
821                         g.spacc=0;
822                         break;
823                 case Tag_h6:
824                         pl_linespace(&g);
825                         g.state->font=BOLD;
826                         g.state->size=LARGE;
827                         g.spacc=0;
828                         break;
829                 case Tag_hr:
830                         g.spacc=0;
831                         plrtbitmap(&g.dst->text, 1000000, g.state->margin, hrule, 0, 0);
832                         break;
833                 case Tag_key:
834                         htmlerror(g.name, g.lineno, "<key> deprecated");
835                 case Tag_kbd:
836                         g.state->font=CWIDTH;
837                         break;
838                 case Tag_dir:
839                 case Tag_menu:
840                 case Tag_ol:
841                 case Tag_ul:
842                         g.state->number=0;
843                         g.linebrk=1;
844                         g.state->margin+=25;
845                         g.state->indent=-25;
846                         g.spacc=0;
847                         break;
848                 case Tag_li:
849                         g.spacc=0;
850                         switch(g.state->tag){
851                         default:
852                                 htmlerror(g.name, g.lineno, "can't have <li> in <%s>",
853                                         tag[g.state->tag].name);
854                         case Tag_dir:   /* supposed to be multi-columns, can't do! */
855                         case Tag_menu:
856                                 g.linebrk=1;
857                                 break;
858                         case Tag_ol:
859                                 g.para=1;
860                                 snprint(buf, sizeof(buf), "%2d  ", ++g.state->number);
861                                 pl_htmloutput(&g, 0, buf, 0);
862                                 break;
863                         case Tag_ul:
864                                 g.para=0;
865                                 g.linebrk=0;
866                                 g.spacc=-1;
867                                 plrtbitmap(&g.dst->text, 100000,
868                                         g.state->margin+g.state->indent, bullet, 0, 0);
869                                 break;
870                         }
871                         break;
872                 case Tag_p:
873                         pl_linespace(&g);
874                         g.linebrk=1;
875                         g.spacc=0;
876                         break;
877                 case Tag_listing:
878                 case Tag_xmp:
879                         htmlerror(g.name, g.lineno, "<%s> deprecated", tag[g.tag].name);
880                 case Tag_pre:
881                 case Tag_samp:
882                         g.state->indent=0;
883                         g.state->pre=1;
884                         g.state->font=CWIDTH;
885                         g.state->size=NORMAL;
886                         pl_linespace(&g);
887                         break;
888                 case Tag_tt:
889                         g.state->font=CWIDTH;
890                         g.state->size=NORMAL;
891                         break;
892                 case Tag_title:
893                         g.text=dst->title+strlen(dst->title);
894                         g.tp=g.text;
895                         g.etext=dst->title+NTITLE-1;
896                         break;
897                 case Tag_form:
898                 case Tag_input:
899                 case Tag_button:
900                 case Tag_select:
901                 case Tag_option:
902                 case Tag_textarea:
903                 case Tag_isindex:
904                         rdform(&g);
905                         break;
906                 case Tag_script:
907                 case Tag_style:
908                         /*
909                          * ignore the content of these tags, eat tokens until we
910                          * reach a matching endtag.
911                          */
912                         t = g.tag;
913                         for(;;){
914                                 switch(pl_gettoken(&g)){
915                                 default:
916                                         continue;
917                                 case ENDTAG:
918                                         if(g.tag != t)
919                                                 continue;
920                                 case EOF:
921                                         break;
922                                 }
923                                 break;
924                         }
925                         break;
926                 }
927                 break;
928
929         case ENDTAG:
930                 /*
931                  * If the end tag doesn't match the top, we try to uncover a match
932                  * on the stack.
933                  */
934                 if(g.state->tag!=g.tag){
935                         tagerr=0;
936                         for(sp=g.state;sp!=g.stack;--sp){
937                                 if(sp->tag==g.tag)
938                                         break;
939                                 if(tag[g.state->tag].action!=OPTEND) tagerr++;
940                         }
941                         if(sp==g.stack){
942                                 if(tagerr)
943                                         htmlerror(g.name, g.lineno,
944                                                 "end tag mismatch <%s>...</%s>, ignored",
945                                                 tag[g.state->tag].name, tag[g.tag].name);
946                         }
947                         else{
948                                 if(tagerr)
949                                         htmlerror(g.name, g.lineno,
950                                                 "end tag mismatch <%s>...</%s>, "
951                                                 "intervening tags popped",
952                                                 tag[g.state->tag].name, tag[g.tag].name);
953                                 g.state=sp-1;
954                         }
955                 }
956                 else if(g.state==g.stack)
957                         htmlerror(g.name, g.lineno, "end tag </%s> at stack bottom",
958                                 tag[g.tag].name);
959                 else
960                         --g.state;
961                 switch(g.tag){
962                 case Tag_select:
963                 case Tag_form:
964                 case Tag_textarea:
965                         endform(&g);
966                         break;
967                 case Tag_h1:
968                 case Tag_h2:
969                 case Tag_h3:
970                 case Tag_h4:
971                         pl_linespace(&g);
972                         break;
973                 case Tag_address:
974                 case Tag_blockquot:
975                 case Tag_body:
976                 case Tag_dir:
977                 case Tag_dl:
978                 case Tag_dt:
979                 case Tag_h5:
980                 case Tag_h6:
981                 case Tag_listing:
982                 case Tag_menu:
983                 case Tag_ol:
984                 case Tag_samp:
985                 case Tag_title:
986                 case Tag_ul:
987                 case Tag_xmp:
988                         g.linebrk=1;
989                         break;
990                 case Tag_table:
991                         if(g.state->table==0)
992                                 g.linebrk=1;
993                         break;
994                 case Tag_pre:
995                         pl_linespace(&g);
996                         break;
997                 }
998                 break;
999         case TEXT:
1000                 pl_htmloutput(&g, g.nsp, g.token, 0);
1001                 break;
1002         case EOF:
1003                 for(;g.state!=g.stack;--g.state)
1004                         if(tag[g.state->tag].action!=OPTEND)
1005                                 htmlerror(g.name, g.lineno,
1006                                         "missing </%s> at EOF", tag[g.state->tag].name);
1007                 *g.tp='\0';
1008                 dst->changed=1;
1009                 getpix(dst->text, dst);
1010                 dst->finished=1;
1011                 return;
1012         }
1013 }