ref: eea1eebb1304de23bca3b3e9759702c3d391bdd1
parent: 19c9042e451e61f1d71f79eea86209e9369d1944
author: qwx <qwx@sciops.net>
date: Sun Aug 13 23:30:07 EDT 2023
add mothra: theme, font
--- /dev/null
+++ b/sys/src/cmd/mothra/libpanel/draw.c
@@ -1,0 +1,283 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+#define PWID 1 /* width of label border */
+#define BWID 1 /* width of button relief */
+#define FWID 1 /* width of frame relief */
+#define SPACE 2 /* space inside relief of button or frame */
+#define CKSIZE 3 /* size of check mark */
+#define CKSPACE 2 /* space around check mark */
+#define CKWID 1 /* width of frame around check mark */
+#define CKINSET 1 /* space around check mark frame */
+#define CKBORDER 2 /* space around X inside frame */
+static Image *pl_light, *pl_dark, *pl_scrl, *pl_tick, *pl_hilit;
+Image *pl_blue, *pl_white, *pl_black, *pl_txt;
+int pl_drawinit(void){
+ enum{
+ Cwhite,
+ Clight,
+ Cdark,
+ Cblack,
+ Cblue,
+ Ctxt,
+ Cscrl,
+ Ncols,
+ };
+ Theme th[Ncols] = {
+ [Cwhite] { "rioback", DWhite },
+ [Clight] { "back", DWhite },
+ [Cdark] { "menubar", 0x777777FF },
+ [Cblack] { "menuhigh", DBlack },
+ [Cblue] { "hold", 0x0000FFFF },
+ [Ctxt] { "text", DBlack },
+ [Cscrl] { "border", 0x999999FF },
+ };
+ readtheme(th, nelem(th), nil);
+ pl_white=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cwhite].c);
+ pl_light=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Clight].c);
+ pl_dark=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cdark].c);
+ pl_black=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cblack].c);
+ pl_blue=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cblue].c);
+ pl_txt=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Ctxt].c);
+ pl_scrl=allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cscrl].c);
+ pl_hilit=allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80);
+ if((pl_tick = allocimage(display, Rect(0, 0, TICKW, font->height), screen->chan, 0x00ff00, DNofill)) != nil){
+ draw(pl_tick, pl_tick->r, pl_white, nil, ZP);
+ draw(pl_tick, Rect(TICKW/2, 0, TICKW/2+1, font->height), pl_black, nil, ZP);
+ draw(pl_tick, Rect(0, 0, TICKW, TICKW), pl_black, nil, ZP);
+ draw(pl_tick, Rect(0, font->height-TICKW, TICKW, font->height), pl_black, nil, ZP);
+ }
+ if(pl_white==0 || pl_light==0 || pl_black==0 || pl_dark==0 || pl_scrl==0 || pl_blue==0 || pl_tick==0 || pl_txt==0) sysfatal("allocimage: %r");
+ return 1;
+}
+
+Rectangle pl_boxoutline(Image *b, Rectangle r, int style, int fill){
+ int doborder;
+
+ doborder = (style & BORDER) != 0;
+ switch(style & ~BORDER){
+ case SUP:
+ case TUP:
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, BWID+SPACE, pl_white, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, BWID);
+ break;
+ case UP:
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, BWID+SPACE, pl_white, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, BWID);
+ break;
+ case DOWN:
+ case DOWN1:
+ case DOWN2:
+ case DOWN3:
+ if(fill) draw(b, r, pl_dark, 0, ZP);
+ else border(b, r, BWID+SPACE, pl_dark, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, BWID);
+ break;
+ case PASSIVE:
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, PWID+SPACE, pl_white, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, PWID);
+ break;
+ case FRAME:
+ border(b, r, FWID, pl_black, ZP);
+ r=insetrect(r, FWID);
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, SPACE, pl_white, ZP);
+ break;
+ }
+ switch(style){
+ case SUP: return insetrect(r, SPACE-SPACE);
+ default: return insetrect(r, SPACE);
+ }
+}
+Rectangle pl_outline(Image *b, Rectangle r, int style){
+ return pl_boxoutline(b, r, style, 0);
+}
+Rectangle pl_box(Image *b, Rectangle r, int style){
+ return pl_boxoutline(b, r, style, 1);
+}
+Point pl_boxsize(Point interior, int state){
+ switch(state){
+ case UP:
+ case DOWN:
+ case DOWN1:
+ case DOWN2:
+ case DOWN3:
+ return addpt(interior, Pt(2*(BWID+SPACE), 2*(BWID+SPACE)));
+ case PASSIVE:
+ return addpt(interior, Pt(2*(PWID+SPACE), 2*(PWID+SPACE)));
+ case FRAME:
+ return addpt(interior, Pt(2*FWID+2*SPACE, 2*FWID+2*SPACE));
+ }
+ return Pt(0, 0);
+}
+void pl_interior(int state, Point *ul, Point *size){
+ switch(state){
+ case UP:
+ case DOWN:
+ case DOWN1:
+ case DOWN2:
+ case DOWN3:
+ *ul=addpt(*ul, Pt(BWID+SPACE, BWID+SPACE));
+ *size=subpt(*size, Pt(2*(BWID+SPACE), 2*(BWID+SPACE)));
+ break;
+ case PASSIVE:
+ *ul=addpt(*ul, Pt(PWID+SPACE, PWID+SPACE));
+ *size=subpt(*size, Pt(2*(PWID+SPACE), 2*(PWID+SPACE)));
+ break;
+ case FRAME:
+ *ul=addpt(*ul, Pt(FWID+SPACE, FWID+SPACE));
+ *size=subpt(*size, Pt(2*FWID+2*SPACE, 2*FWID+2*SPACE));
+ }
+}
+
+void pl_drawicon(Image *b, Rectangle r, int stick, int flags, Icon *s){
+ Rectangle save;
+ Point ul, offs;
+ ul=r.min;
+ offs=subpt(subpt(r.max, r.min), pl_iconsize(flags, s));
+ switch(stick){
+ case PLACENW: break;
+ case PLACEN: ul.x+=offs.x/2; break;
+ case PLACENE: ul.x+=offs.x; break;
+ case PLACEW: ul.y+=offs.y/2; break;
+ case PLACECEN: ul.x+=offs.x/2; ul.y+=offs.y/2; break;
+ case PLACEE: ul.x+=offs.x; break;
+ case PLACESW: ul.y+=offs.y; break;
+ case PLACES: ul.x+=offs.x/2; ul.y+=offs.y; break;
+ case PLACESE: ul.x+=offs.x; ul.y+=offs.y; break;
+ }
+ save=b->clipr;
+ if(!rectclip(&r, save))
+ return;
+ replclipr(b, b->repl, r);
+ if(flags&BITMAP) draw(b, Rpt(ul, addpt(ul, pl_iconsize(flags, s))), s, 0, ZP);
+ else string(b, ul, pl_black, ZP, font, s);
+ replclipr(b, b->repl, save);
+}
+/*
+ * Place a check mark at the left end of r. Return the unused space.
+ * Caller must guarantee that r.max.x-r.min.x>=r.max.y-r.min.y!
+ */
+Rectangle pl_radio(Image *b, Rectangle r, int val){
+ Rectangle remainder;
+ remainder=r;
+ r.max.x=r.min.x+r.max.y-r.min.y;
+ remainder.min.x=r.max.x;
+ r=insetrect(r, CKINSET);
+ border(b, r, CKWID, pl_white, ZP);
+ r=insetrect(r, CKWID);
+ draw(b, r, pl_light, 0, ZP);
+ if(val) draw(b, insetrect(r, CKSPACE), pl_black, 0, ZP);
+ return remainder;
+}
+Rectangle pl_check(Image *b, Rectangle r, int val){
+ Rectangle remainder;
+ remainder=r;
+ r.max.x=r.min.x+r.max.y-r.min.y;
+ remainder.min.x=r.max.x;
+ r=insetrect(r, CKINSET);
+ border(b, r, CKWID, pl_white, ZP);
+ r=insetrect(r, CKWID);
+ draw(b, r, pl_light, 0, ZP);
+ r=insetrect(r, CKBORDER);
+ if(val){
+ line(b, Pt(r.min.x, r.min.y+1), Pt(r.max.x-1, r.max.y ), Endsquare, Endsquare, 0, pl_black, ZP);
+ line(b, Pt(r.min.x, r.min.y ), Pt(r.max.x, r.max.y ), Endsquare, Endsquare, 0, pl_black, ZP);
+ line(b, Pt(r.min.x+1, r.min.y ), Pt(r.max.x, r.max.y-1), Endsquare, Endsquare, 0, pl_black, ZP);
+ line(b, Pt(r.min.x , r.max.y-2), Pt(r.max.x-1, r.min.y-1), Endsquare, Endsquare, 0, pl_black, ZP);
+ line(b, Pt(r.min.x, r.max.y-1), Pt(r.max.x, r.min.y-1), Endsquare, Endsquare, 0, pl_black, ZP);
+ line(b, Pt(r.min.x+1, r.max.y-1), Pt(r.max.x, r.min.y ), Endsquare, Endsquare, 0, pl_black, ZP);
+ }
+ return remainder;
+}
+int pl_ckwid(void){
+ return 2*(CKINSET+CKSPACE+CKWID)+CKSIZE;
+}
+void pl_sliderupd(Image *b, Rectangle r1, int dir, int lo, int hi){
+ Rectangle r2, r3;
+ r2=r1;
+ r3=r1;
+ if(lo<0) lo=0;
+ if(hi<=lo) hi=lo+1;
+ switch(dir){
+ case HORIZ:
+ r1.max.x=r1.min.x+lo;
+ r2.min.x=r1.max.x;
+ r2.max.x=r1.min.x+hi;
+ if(r2.max.x>r3.max.x) r2.max.x=r3.max.x;
+ r3.min.x=r2.max.x;
+ break;
+ case VERT:
+ r1.max.y=r1.min.y+lo;
+ r2.min.y=r1.max.y;
+ r2.max.y=r1.min.y+hi;
+ if(r2.max.y>r3.max.y) r2.max.y=r3.max.y;
+ r3.min.y=r2.max.y;
+ break;
+ }
+ draw(b, r1, pl_light, 0, ZP);
+ draw(b, r2, pl_dark, 0, ZP);
+ draw(b, r3, pl_light, 0, ZP);
+}
+void pl_scrollupd(Image *b, Rectangle r, int lo, int hi)
+{
+ Rectangle sr;
+ if(lo<0) lo=0;
+ if(hi<=lo) hi=lo+1;
+ sr=r;
+ sr.min.y+=lo;
+ sr.max.x-=1;
+ sr.max.y=sr.min.y+hi;
+ if(sr.max.y>r.max.y) sr.max.y=r.max.y;
+ draw(b, r, pl_scrl, 0, ZP);
+ draw(b, sr, pl_light, 0, ZP);
+}
+void pl_draw1(Panel *p, Image *b);
+void pl_drawall(Panel *p, Image *b){
+ if(p->flags&INVIS || p->flags&IGNORE) return;
+ p->b=b;
+ p->draw(p);
+ for(p=p->child;p;p=p->next) pl_draw1(p, b);
+}
+void pl_draw1(Panel *p, Image *b){
+ if(b!=0)
+ pl_drawall(p, b);
+}
+void pldraw(Panel *p, Image *b){
+ pl_draw1(p, b);
+}
+void pl_invis(Panel *p, int v){
+ for(;p;p=p->next){
+ if(v) p->flags|=INVIS; else p->flags&=~INVIS;
+ pl_invis(p->child, v);
+ }
+}
+Point pl_iconsize(int flags, Icon *p){
+ if(flags&BITMAP) return subpt(((Image *)p)->r.max, ((Image *)p)->r.min);
+ return stringsize(font, (char *)p);
+}
+void pl_highlight(Image *b, Rectangle r){
+ draw(b, r, pl_dark, pl_hilit, ZP);
+}
+void pl_drawtick(Image *b, Rectangle r){
+ draw(b, r, pl_tick, nil, ZP);
+}
+void pl_clr(Image *b, Rectangle r){
+ draw(b, r, pl_white, 0, ZP);
+}
+void pl_fill(Image *b, Rectangle r){
+ draw(b, r, pl_light, 0, ZP);
+}
+void pl_cpy(Image *b, Point dst, Rectangle src){
+ draw(b, Rpt(dst, addpt(dst, subpt(src.max, src.min))), b, 0, src.min);
+}
--- /dev/null
+++ b/sys/src/cmd/mothra/libpanel/message.c
@@ -1,0 +1,104 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+typedef struct Message Message;
+struct Message{
+ char *text;
+ Point minsize;
+};
+void pl_textmsg(Image *b, Rectangle r, Font *f, char *s){
+ char *start, *end; /* of line */
+ Point where;
+ int lwid, c, wid;
+ where=r.min;
+ wid=r.max.x-r.min.x;
+ do{
+ start=s;
+ lwid=0;
+ end=s;
+ do{
+ for(;*s!=' ' && *s!='\0';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s);
+ if(lwid>wid) break;
+ end=s;
+ for(;*s==' ';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s);
+ }while(*s!='\0');
+ if(end==start) /* can't even fit one word on line! */
+ end=s;
+ c=*end;
+ *end='\0';
+ string(b, where, pl_txt, ZP, f, start);
+ *end=c;
+ where.y+=font->height;
+ s=end;
+ while(*s==' ') s=pl_nextrune(s);
+ }while(*s!='\0');
+}
+Point pl_foldsize(Font *f, char *s, int wid){
+ char *start, *end; /* of line */
+ Point size;
+ int lwid, ewid;
+ size=Pt(0,0);
+ do{
+ start=s;
+ lwid=0;
+ end=s;
+ ewid=lwid;
+ do{
+ for(;*s!=' ' && *s!='\0';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s);
+ if(lwid>wid) break;
+ end=s;
+ ewid=lwid;
+ for(;*s==' ';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s);
+ }while(*s!='\0');
+ if(end==start){ /* can't even fit one word on line! */
+ ewid=lwid;
+ end=s;
+ }
+ if(ewid>size.x) size.x=ewid;
+ size.y+=font->height;
+ s=end;
+ while(*s==' ') s=pl_nextrune(s);
+ }while(*s!='\0');
+ return size;
+}
+void pl_drawmessage(Panel *p){
+ pl_textmsg(p->b, pl_box(p->b, p->r, PASSIVE), font, ((Message *)p->data)->text);
+}
+int pl_hitmessage(Panel *g, Mouse *m){
+ USED(g, m);
+ return 0;
+}
+void pl_typemessage(Panel *g, Rune c){
+ USED(g, c);
+}
+Point pl_getsizemessage(Panel *p, Point children){
+ Message *mp;
+ USED(children);
+ mp=p->data;
+ return pl_boxsize(pl_foldsize(font, mp->text, mp->minsize.x), PASSIVE);
+}
+void pl_childspacemessage(Panel *p, Point *ul, Point *size){
+ USED(p, ul, size);
+}
+void plinitmessage(Panel *v, int flags, int wid, char *msg){
+ Message *mp;
+ mp=v->data;
+ v->flags=flags|LEAF;
+ v->draw=pl_drawmessage;
+ v->hit=pl_hitmessage;
+ v->type=pl_typemessage;
+ v->getsize=pl_getsizemessage;
+ v->childspace=pl_childspacemessage;
+ mp->text=msg;
+ mp->minsize=Pt(wid, font->height);
+ v->kind="message";
+}
+Panel *plmessage(Panel *parent, int flags, int wid, char *msg){
+ Panel *v;
+ v=pl_newpanel(parent, sizeof(Message));
+ plinitmessage(v, flags, wid, msg);
+ return v;
+}
--- /dev/null
+++ b/sys/src/cmd/mothra/libpanel/pldefs.h
@@ -1,0 +1,113 @@
+/*
+ * Definitions for internal use only
+ */
+/*
+ * Variable-font text routines
+ * These could make a separate library.
+ */
+Point pl_rtfmt(Rtext *, int);
+void pl_rtdraw(Image *, Rectangle, Rtext *, Point);
+void pl_rtredraw(Image *, Rectangle, Rtext *, Point, Point, int);
+Rtext *pl_rthit(Rtext *, Point, Point, Point);
+#define HITME 0x08000 /* tells ptinpanel not to look at children */
+#define LEAF 0x10000 /* newpanel will refuse to attach children */
+#define INVIS 0x20000 /* don't draw this */
+#define REMOUSE 0x40000 /* send next mouse event here, even if not inside */
+#define TICKW 3 /* tick width */
+/*
+ * States, also styles
+ */
+enum{
+ SUP, // scrollbar
+ TUP, // textview
+ UP, // deprecated
+ DOWN1,
+ DOWN2,
+ DOWN3,
+ DOWN,
+ PASSIVE,
+ FRAME,
+ BORDER = 1<<8,
+};
+/*
+ * Scroll flags
+ */
+enum{
+ SCROLLUP,
+ SCROLLDOWN,
+ SCROLLABSY,
+ SCROLLLEFT,
+ SCROLLRIGHT,
+ SCROLLABSX,
+};
+
+extern Image *pl_blue, *pl_white, *pl_black, *pl_txt;
+
+/*
+ * Scrollbar, slider orientations
+ */
+enum{
+ HORIZ,
+ VERT
+};
+Panel *pl_newpanel(Panel *, int); /* make a new Panel, given parent & data size */
+void *pl_emalloc(int); /* allocate some space, exit on error */
+void *pl_erealloc(void*,int); /* reallocate some space, exit on error */
+void pl_print(Panel *); /* print a Panel tree */
+Panel *pl_ptinpanel(Point, Panel *); /* highest-priority subpanel containing point */
+/*
+ * Drawing primitives
+ */
+int pl_drawinit(void);
+Rectangle pl_box(Image *, Rectangle, int);
+Rectangle pl_outline(Image *, Rectangle, int);
+Point pl_boxsize(Point, int);
+void pl_interior(int, Point *, Point *);
+void pl_drawicon(Image *, Rectangle, int, int, Icon *);
+Rectangle pl_check(Image *, Rectangle, int);
+Rectangle pl_radio(Image *, Rectangle, int);
+int pl_ckwid(void);
+void pl_sliderupd(Image *, Rectangle, int, int, int);
+void pl_scrollupd(Image *, Rectangle, int, int);
+void pl_invis(Panel *, int);
+Point pl_iconsize(int, Icon *);
+void pl_highlight(Image *, Rectangle);
+void pl_drawtick(Image *, Rectangle);
+void pl_clr(Image *, Rectangle);
+void pl_fill(Image *, Rectangle);
+void pl_cpy(Image *, Point, Rectangle);
+
+/*
+ * Rune mangling functions
+ */
+int pl_idchar(int);
+int pl_rune1st(int);
+char *pl_nextrune(char *);
+int pl_runewidth(Font *, char *);
+/*
+ * Fixed-font Text-window routines
+ * These could be separated out into a separate library.
+ */
+typedef struct Textwin Textwin;
+struct Textwin{
+ Rune *text, *etext, *eslack; /* text, with some slack off the end */
+ int top, bot; /* range of runes visible on screen */
+ int sel0, sel1; /* selection */
+ Point *loc, *eloc; /* ul corners of visible runes (+1 more at end!) */
+ Image *b; /* bitmap the text is drawn in */
+ Rectangle r; /* rectangle the text is drawn in */
+ Font *font; /* font text is drawn in */
+ int hgt; /* same as font->height */
+ int tabstop; /* tab settings are every tabstop pixels */
+ int mintab; /* the minimum size of a tab */
+};
+Textwin *twnew(Image *, Font *, Rune *, int);
+void twfree(Textwin *);
+void twhilite(Textwin *, int, int, int);
+void twselect(Textwin *, Mouse *);
+void twreplace(Textwin *, int, int, Rune *, int);
+void twscroll(Textwin *, int);
+int twpt2rune(Textwin *, Point);
+void twreshape(Textwin *, Rectangle);
+void twmove(Textwin *, Point);
+void plemove(Panel *, Point);
--- /dev/null
+++ b/sys/src/cmd/mothra/libpanel/rtext.c
@@ -1,0 +1,375 @@
+/*
+ * Rich text with images.
+ * Should there be an offset field, to do subscripts & kerning?
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+#include "rtext.h"
+
+#define LEAD 4 /* extra space between lines */
+#define BORD 2 /* extra border for images */
+
+Rtext *pl_rtnew(Rtext **t, int space, int indent, int voff, Image *b, Panel *p, Font *f, char *s, int flags, void *user){
+ Rtext *new;
+ new=pl_emalloc(sizeof(Rtext));
+ new->flags=flags;
+ new->user=user;
+ new->space=space;
+ new->indent=indent;
+ new->voff=voff;
+ new->b=b;
+ new->p=p;
+ new->font=f;
+ new->text=s;
+ new->next=0;
+ new->nextline=0;
+ new->r=Rect(0,0,0,0);
+ if(*t)
+ (*t)->last->next=new;
+ else
+ *t=new;
+ (*t)->last=new;
+ return new;
+}
+Rtext *plrtpanel(Rtext **t, int space, int indent, int voff, Panel *p, void *user){
+ return pl_rtnew(t, space, indent, voff, 0, p, 0, 0, 1, user);
+}
+Rtext *plrtstr(Rtext **t, int space, int indent, int voff, Font *f, char *s, int flags, void *user){
+ return pl_rtnew(t, space, indent, voff, 0, 0, f, s, flags, user);
+}
+Rtext *plrtbitmap(Rtext **t, int space, int indent, int voff, Image *b, int flags, void *user){
+ return pl_rtnew(t, space, indent, voff, b, 0, 0, 0, flags, user);
+}
+void plrtfree(Rtext *t){
+ Rtext *next;
+ while(t){
+ next=t->next;
+ free(t);
+ t=next;
+ }
+}
+int pl_tabmin, pl_tabsize;
+void pltabsize(int min, int size){
+ pl_tabmin=min;
+ pl_tabsize=size;
+}
+int pl_space(int space, int pos, int indent){
+ if(space>=0) return space;
+ switch(PL_OP(space)){
+ default:
+ return 0;
+ case PL_TAB:
+ return ((pos-indent+pl_tabmin)/pl_tabsize+PL_ARG(space))*pl_tabsize+indent-pos;
+ }
+}
+/*
+ * initialize rectangles & nextlines of text starting at t,
+ * galley width is wid. Returns the total width/height of the text
+ */
+Point pl_rtfmt(Rtext *t, int wid){
+ Rtext *tp, *eline;
+ int ascent, descent, x, space, a, d, w, topy, indent, maxwid;
+ Point p;
+
+ p=Pt(0,0);
+ eline=t;
+ maxwid=0;
+ while(t){
+ ascent=0;
+ descent=0;
+ indent=space=pl_space(t->indent, 0, 0);
+ x=0;
+ tp=t;
+ for(;;){
+ if(tp->b){
+ a=tp->b->r.max.y-tp->b->r.min.y+BORD;
+ d=BORD;
+ w=tp->b->repl?wid-x:tp->b->r.max.x-tp->b->r.min.x+BORD*2;
+ }
+ else if(tp->p){
+ /* what if plpack fails? */
+ plpack(tp->p, Rect(0,0,wid,wid));
+ plmove(tp->p, subpt(Pt(0,0), tp->p->r.min));
+ a=tp->p->r.max.y-tp->p->r.min.y;
+ d=0;
+ w=tp->p->r.max.x-tp->p->r.min.x;
+ }
+ else{
+ a=tp->font->ascent;
+ d=tp->font->height-a;
+ w=tp->wid=stringwidth(tp->font, tp->text);
+ }
+ a-=tp->voff,d+=tp->voff;
+ if(x+w+space>wid) break;
+ if(a>ascent) ascent=a;
+ if(d>descent) descent=d;
+ x+=w+space;
+ tp=tp->next;
+ if(tp==0){
+ eline=0;
+ break;
+ }
+ space=pl_space(tp->space, x, indent);
+ if(space) eline=tp;
+ }
+ if(eline==t){ /* No progress! Force fit the first block! */
+ if(tp==t){
+ if(a>ascent) ascent=a;
+ if(d>descent) descent=d;
+ eline=tp->next;
+ }else
+ eline=tp;
+ }
+ topy=p.y;
+ p.y+=ascent;
+ p.x=indent=pl_space(t->indent, 0, 0);
+ for(;;){
+ t->topy=topy;
+ t->r.min.x=p.x;
+ p.y+=t->voff;
+ if(t->b){
+ t->r.max.y=p.y+BORD;
+ t->r.min.y=p.y-(t->b->r.max.y-t->b->r.min.y)-BORD;
+ p.x+=t->b->repl?wid-p.x:(t->b->r.max.x-t->b->r.min.x)+BORD*2;
+ }
+ else if(t->p){
+ t->r.max.y=p.y;
+ t->r.min.y=p.y-t->p->r.max.y;
+ p.x+=t->p->r.max.x;
+ }
+ else{
+ t->r.min.y=p.y-t->font->ascent;
+ t->r.max.y=t->r.min.y+t->font->height;
+ p.x+=t->wid;
+ }
+ p.y-=t->voff;
+ t->r.max.x=p.x;
+ t->nextline=eline;
+ t=t->next;
+ if(t==eline) break;
+ p.x+=pl_space(t->space, p.x, indent);
+ }
+ if(p.x>maxwid) maxwid=p.x;
+ p.y+=descent+LEAD;
+ }
+ return Pt(maxwid, p.y);
+}
+
+/*
+ * If we draw the text in a backup bitmap and copy it onto the screen,
+ * the bitmap pointers in all the subpanels point to the wrong bitmap.
+ * This code fixes them.
+ */
+void pl_stuffbitmap(Panel *p, Image *b){
+ p->b=b;
+ for(p=p->child;p;p=p->next)
+ pl_stuffbitmap(p, b);
+}
+
+void pl_rtdraw(Image *b, Rectangle r, Rtext *t, Point offs){
+ static Image *backup;
+ Point lp, sp;
+ Rectangle dr;
+ Image *bb;
+
+ bb = b;
+ if(backup==0 || backup->chan!=b->chan || rectinrect(r, backup->r)==0){
+ freeimage(backup);
+ backup=allocimage(display, bb->r, bb->chan, 0, DNofill);
+ }
+ if(backup)
+ b=backup;
+ pl_clr(b, r);
+ lp=ZP;
+ sp=ZP;
+ offs=subpt(r.min, offs);
+ for(;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){
+ dr=rectaddpt(t->r, offs);
+ if(dr.max.y>r.min.y
+ && dr.min.y<r.max.y
+ && dr.max.x>r.min.x
+ && dr.min.x<r.max.x){
+ if(t->b){
+ if(t->flags&PL_HOT) border(b, dr, 1, pl_blue, ZP);
+ draw(b, insetrect(dr, BORD), t->b, 0, t->b->r.min);
+ if(t->flags&PL_STR) {
+ line(b, Pt(dr.min.x, dr.min.y), Pt(dr.max.x, dr.max.y),
+ Endsquare, Endsquare, 0,
+ pl_txt, ZP);
+ line(b, Pt(dr.min.x, dr.max.y), Pt(dr.max.x, dr.min.y),
+ Endsquare, Endsquare, 0,
+ pl_txt, ZP);
+ }
+ if(t->flags&PL_SEL)
+ pl_highlight(b, dr);
+ }
+ else if(t->p){
+ plmove(t->p, subpt(dr.min, t->p->r.min));
+ pldraw(t->p, b);
+ if(b!=bb)
+ pl_stuffbitmap(t->p, bb);
+ }
+ else{
+ if(t->flags&PL_HOT)
+ string(b, dr.min, pl_blue, ZP, t->font, t->text);
+ else
+ string(b, dr.min, pl_txt, ZP, t->font, t->text);
+ if(t->flags&PL_SEL)
+ pl_highlight(b, dr);
+ if(t->flags&PL_STR){
+ int y = dr.max.y - t->font->height/2;
+ if(sp.y != y)
+ sp = Pt(dr.min.x, y);
+ line(b, sp, Pt(dr.max.x, y),
+ Endsquare, Endsquare, 0,
+ pl_txt, ZP);
+ sp = Pt(dr.max.x, y);
+ } else
+ sp = ZP;
+ if(t->flags&PL_HOT){
+ int y = dr.max.y - 1;
+ if(lp.y != y)
+ lp = Pt(dr.min.x, y);
+ line(b, lp, Pt(dr.max.x, y),
+ Endsquare, Endsquare, 0,
+ pl_blue, ZP);
+ lp = Pt(dr.max.x, y);
+ } else
+ lp = ZP;
+ continue;
+ }
+ lp = ZP;
+ sp = ZP;
+ }
+ }
+ if(b!=bb)
+ draw(bb, r, b, 0, r.min);
+}
+/*
+ * Reposition text already drawn in the window.
+ * We just move the pixels and update the positions of any
+ * enclosed panels
+ */
+void pl_reposition(Rtext *t, Image *b, Point p, Rectangle r){
+ Point offs;
+ pl_cpy(b, p, r);
+ offs=subpt(p, r.min);
+ for(;t;t=t->next)
+ if(!eqrect(t->r, Rect(0,0,0,0)) && !t->b && t->p)
+ plmove(t->p, offs);
+}
+/*
+ * Rectangle r of Image b contains an image of Rtext t, offset by oldoffs.
+ * Redraw the text to have offset yoffs.
+ */
+void pl_rtredraw(Image *b, Rectangle r, Rtext *t, Point offs, Point oldoffs, int dir){
+ int d, size;
+
+ if(dir==VERT){
+ d=oldoffs.y-offs.y;
+ size=r.max.y-r.min.y;
+ if(d>=size || -d>=size) /* move more than screenful */
+ pl_rtdraw(b, r, t, offs);
+ else if(d<0){ /* down */
+ pl_reposition(t, b, r.min,
+ Rect(r.min.x, r.min.y-d, r.max.x, r.max.y));
+ pl_rtdraw(b, Rect(r.min.x, r.max.y+d, r.max.x, r.max.y),
+ t, Pt(offs.x, offs.y+size+d));
+ }
+ else if(d>0){ /* up */
+ pl_reposition(t, b, Pt(r.min.x, r.min.y+d),
+ Rect(r.min.x, r.min.y, r.max.x, r.max.y-d));
+ pl_rtdraw(b, Rect(r.min.x, r.min.y, r.max.x, r.min.y+d),
+ t, offs);
+ }
+ }else{ /* dir==HORIZ */
+ d=oldoffs.x-offs.x;
+ size=r.max.x-r.min.x;
+ if(d>=size || -d>=size) /* move more than screenful */
+ pl_rtdraw(b, r, t, offs);
+ else if(d<0){ /* right */
+ pl_reposition(t, b, r.min,
+ Rect(r.min.x-d, r.min.y, r.max.x, r.max.y));
+ pl_rtdraw(b, Rect(r.max.x+d, r.min.y, r.max.x, r.max.y),
+ t, Pt(offs.x+size+d, offs.y));
+ }
+ else if(d>0){ /* left */
+ pl_reposition(t, b, Pt(r.min.x+d, r.min.y),
+ Rect(r.min.x, r.min.y, r.max.x-d, r.max.y));
+ pl_rtdraw(b, Rect(r.min.x, r.min.y, r.min.x+d, r.max.y),
+ t, offs);
+ }
+ }
+}
+Rtext *pl_rthit(Rtext *t, Point offs, Point p, Point ul){
+ Rectangle r;
+ Point lp;
+ if(t==0) return 0;
+ p.x+=offs.x-ul.x;
+ p.y+=offs.y-ul.y;
+ while(t->nextline && t->nextline->topy<=p.y) t=t->nextline;
+ lp=ZP;
+ for(;t!=0;t=t->next){
+ if(t->topy>p.y) return 0;
+ r = t->r;
+ if((t->flags&PL_HOT) != 0 && t->b == nil && t->p == nil){
+ if(lp.y == r.max.y && lp.x < r.min.x)
+ r.min.x=lp.x;
+ lp=r.max;
+ } else
+ lp=ZP;
+ if(ptinrect(p, r)) return t;
+ }
+ return 0;
+}
+
+void plrtseltext(Rtext *t, Rtext *s, Rtext *e){
+ while(t){
+ t->flags &= ~PL_SEL;
+ t = t->next;
+ }
+ if(s==0 || e==0)
+ return;
+ for(t=s; t!=0 && t!=e; t=t->next)
+ ;
+ if(t==e){
+ for(t=s; t!=e; t=t->next)
+ t->flags |= PL_SEL;
+ }else{
+ for(t=e; t!=s; t=t->next)
+ t->flags |= PL_SEL;
+ }
+ t->flags |= PL_SEL;
+}
+
+char *plrtsnarftext(Rtext *w){
+ char *b, *p, *e, *t;
+ int n;
+
+ b=p=e=0;
+ for(; w; w = w->next){
+ if((w->flags&PL_SEL)==0 || w->text==0)
+ continue;
+ n = strlen(w->text)+64;
+ if(p+n >= e){
+ n = (p+n+64)-b;
+ t = pl_erealloc(b, n);
+ p = t+(p-b);
+ e = t+n;
+ b = t;
+ }
+ if(w->space == 0)
+ p += sprint(p, "%s", w->text);
+ else if(w->space > 0)
+ p += sprint(p, " %s", w->text);
+ else if(PL_OP(w->space) == PL_TAB)
+ p += sprint(p, "\t%s", w->text);
+ if(w->nextline == w->next)
+ p += sprint(p, "\n");
+ }
+ return b;
+}
--- /dev/null
+++ b/sys/src/cmd/mothra/libpanel/textwin.c
@@ -1,0 +1,474 @@
+/*
+ * Text windows
+ * void twhilite(Textwin *t, int sel0, int sel1, int on)
+ * hilite (on=1) or unhilite (on=0) a range of characters
+ * void twselect(Textwin *t, Mouse *m)
+ * set t->sel0, t->sel1 from mouse input.
+ * Also hilites selection.
+ * Caller should first unhilite previous selection.
+ * void twreplace(Textwin *t, int r0, int r1, Rune *ins, int nins)
+ * Replace the given range of characters with the given insertion.
+ * Caller should unhilite selection while this is called.
+ * void twscroll(Textwin *t, int top)
+ * Character with index top moves to the top line of the screen.
+ * int twpt2rune(Textwin *t, Point p)
+ * which character is displayed at point p?
+ * void twreshape(Textwin *t, Rectangle r)
+ * save r and redraw the text
+ * Textwin *twnew(Bitmap *b, Font *f, Rune *text, int ntext)
+ * create a new text window
+ * void twfree(Textwin *t)
+ * get rid of a surplus Textwin
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+
+#define SLACK 100
+
+/*
+ * Is text at point a before or after that at point b?
+ */
+int tw_before(Textwin *t, Point a, Point b){
+ return a.y<b.y || a.y<b.y+t->hgt && a.x<b.x;
+}
+/*
+ * Return the character index indicated by point p, or -1
+ * if its off-screen. The screen must be up-to-date.
+ *
+ * Linear search should be binary search.
+ */
+int twpt2rune(Textwin *t, Point p){
+ Point *el, *lp;
+ el=t->loc+(t->bot-t->top);
+ for(lp=t->loc;lp!=el;lp++)
+ if(tw_before(t, p, *lp)){
+ if(lp==t->loc) return t->top;
+ return lp-t->loc+t->top-1;
+ }
+ return t->bot;
+}
+/*
+ * Return ul corner of the character with the given index
+ */
+Point tw_rune2pt(Textwin *t, int i){
+ if(i<t->top) return t->r.min;
+ if(i>t->bot) return t->r.max;
+ return t->loc[i-t->top];
+}
+/*
+ * Store p at t->loc[l], extending t->loc if necessary
+ */
+void tw_storeloc(Textwin *t, int l, Point p){
+ int nloc;
+ if(l>=t->eloc-t->loc){
+ nloc=l+SLACK;
+ t->loc=pl_erealloc(t->loc, nloc*sizeof(Point));
+ t->eloc=t->loc+nloc;
+ }
+ t->loc[l]=p;
+}
+/*
+ * Set the locations at which the given runes should appear.
+ * Returns the index of the first rune not set, which might not
+ * be last because we reached the bottom of the window.
+ *
+ * N.B. this zaps the loc of r[last], so that value should be saved first,
+ * if it's important.
+ */
+int tw_setloc(Textwin *t, int first, int last, Point ul){
+ Rune *r, *er;
+ int x, dt, lp;
+ char buf[UTFmax+1];
+ er=t->text+last;
+ for(r=t->text+first,lp=first-t->top;r!=er && ul.y+t->hgt<=t->r.max.y;r++,lp++){
+ tw_storeloc(t, lp, ul);
+ switch(*r){
+ case '\n':
+ ul.x=t->r.min.x;
+ ul.y+=t->hgt;
+ break;
+ case '\t':
+ x=ul.x-t->r.min.x+t->mintab+t->tabstop;
+ x-=x%t->tabstop;
+ ul.x=x+t->r.min.x;
+ if(ul.x>t->r.max.x){
+ ul.x=t->r.min.x;
+ ul.y+=t->hgt;
+ tw_storeloc(t, lp, ul);
+ if(ul.y+t->hgt>t->r.max.y) return r-t->text;
+ ul.x+=+t->tabstop;
+ }
+ break;
+ default:
+ buf[runetochar(buf, r)]='\0';
+ dt=stringwidth(t->font, buf);
+ ul.x+=dt;
+ if(ul.x>t->r.max.x){
+ ul.x=t->r.min.x;
+ ul.y+=t->hgt;
+ tw_storeloc(t, lp, ul);
+ if(ul.y+t->hgt>t->r.max.y) return r-t->text;
+ ul.x+=dt;
+ }
+ break;
+ }
+ }
+ tw_storeloc(t, lp, ul);
+ return r-t->text;
+}
+/*
+ * Draw the given runes at their locations.
+ * Bug -- saving up multiple characters would
+ * reduce the number of calls to string,
+ * and probably make this a lot faster.
+ */
+void tw_draw(Textwin *t, int first, int last){
+ Rune *r, *er;
+ Point *lp, ul, ur;
+ char buf[UTFmax+1];
+ if(first<t->top) first=t->top;
+ if(last>t->bot) last=t->bot;
+ if(last<=first) return;
+ er=t->text+last;
+ for(r=t->text+first,lp=t->loc+(first-t->top);r!=er;r++,lp++){
+ if(lp->y+t->hgt>t->r.max.y){
+ fprint(2, "chr %C, index %zd of %d, loc %d %d, off bottom\n",
+ *r, lp-t->loc, t->bot-t->top, lp->x, lp->y);
+ return;
+ }
+ switch(*r){
+ case '\n':
+ ur=*lp;
+ break;
+ case '\t':
+ ur=*lp;
+ if(lp[1].y!=lp[0].y)
+ ul=Pt(t->r.min.x, lp[1].y);
+ else
+ ul=*lp;
+ pl_clr(t->b, Rpt(ul, Pt(lp[1].x, ul.y+t->hgt)));
+ break;
+ default:
+ buf[runetochar(buf, r)]='\0';
+ /***/ pl_clr(t->b, Rpt(*lp, addpt(*lp, stringsize(t->font, buf))));
+ ur=string(t->b, *lp, pl_txt, ZP, t->font, buf);
+ break;
+ }
+ if(lp[1].y!=lp[0].y)
+ /***/ pl_clr(t->b, Rpt(ur, Pt(t->r.max.x, ur.y+t->hgt)));
+ }
+}
+/*
+ * Hilight the characters with tops between ul and ur
+ */
+void tw_hilitep(Textwin *t, Point ul, Point ur){
+ Point swap;
+ int y;
+ if(tw_before(t, ur, ul)){ swap=ul; ul=ur; ur=swap;}
+ y=ul.y+t->hgt;
+ if(y>t->r.max.y) y=t->r.max.y;
+ if(ul.y==ur.y)
+ pl_highlight(t->b, Rpt(ul, Pt(ur.x, y)));
+ else{
+ pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, y)));
+ ul=Pt(t->r.min.x, y);
+ pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, ur.y)));
+ ul=Pt(t->r.min.x, ur.y);
+ y=ur.y+t->hgt;
+ if(y>t->r.max.y) y=t->r.max.y;
+ pl_highlight(t->b, Rpt(ul, Pt(ur.x, y)));
+ }
+}
+/*
+ * Hilite/unhilite the given range of characters
+ */
+void twhilite(Textwin *t, int sel0, int sel1, int on){
+ Point ul, ur;
+ int swap, y;
+ if(sel1<sel0){ swap=sel0; sel0=sel1; sel1=swap; }
+ if(sel1<t->top || t->bot<sel0) return;
+ if(sel0<t->top) sel0=t->top;
+ if(sel1>t->bot) sel1=t->bot;
+ if(!on){
+ if(sel1==sel0){
+ ul=t->loc[sel0-t->top];
+ y=ul.y+t->hgt;
+ if(y>t->r.max.y) y=t->r.max.y;
+ pl_clr(t->b, Rpt(ul, Pt(ul.x+1, y)));
+ }else
+ tw_draw(t, sel0, sel1);
+ return;
+ }
+ ul=t->loc[sel0-t->top];
+ if(sel1==sel0)
+ ur=addpt(ul, Pt(1, 0));
+ else
+ ur=t->loc[sel1-t->top];
+ tw_hilitep(t, ul, ur);
+}
+/*
+ * Set t->sel[01] from mouse input.
+ * Also hilites the selection.
+ * Caller should unhilite the previous
+ * selection before calling this.
+ */
+void twselect(Textwin *t, Mouse *m){
+ int sel0, sel1, newsel;
+ Point p0, p1, newp;
+ sel0=sel1=twpt2rune(t, m->xy);
+ p0=tw_rune2pt(t, sel0);
+ p1=addpt(p0, Pt(1, 0));
+ twhilite(t, sel0, sel1, 1);
+ for(;;){
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ *m=emouse();
+ if((m->buttons&7)!=1) break;
+ newsel=twpt2rune(t, m->xy);
+ newp=tw_rune2pt(t, newsel);
+ if(eqpt(newp, p0)) newp=addpt(newp, Pt(1, 0));
+ if(!eqpt(newp, p1)){
+ if((sel0<=sel1 && sel1<newsel) || (newsel<sel1 && sel1<sel0))
+ tw_hilitep(t, p1, newp);
+ else if((sel0<=newsel && newsel<sel1) || (sel1<newsel && newsel<=sel0)){
+ twhilite(t, sel1, newsel, 0);
+ if(newsel==sel0)
+ tw_hilitep(t, p0, newp);
+ }else if((newsel<sel0 && sel0<=sel1) || (sel1<sel0 && sel0<=newsel)){
+ twhilite(t, sel0, sel1, 0);
+ tw_hilitep(t, p0, newp);
+ }
+ sel1=newsel;
+ p1=newp;
+ }
+ }
+ if(sel0<=sel1){
+ t->sel0=sel0;
+ t->sel1=sel1;
+ }
+ else{
+ t->sel0=sel1;
+ t->sel1=sel0;
+ }
+}
+/*
+ * Clear the area following the last displayed character
+ */
+void tw_clrend(Textwin *t){
+ Point ul;
+ int y;
+ ul=t->loc[t->bot-t->top];
+ y=ul.y+t->hgt;
+ if(y>t->r.max.y) y=t->r.max.y;
+ pl_clr(t->b, Rpt(ul, Pt(t->r.max.x, y)));
+ ul=Pt(t->r.min.x, y);
+ pl_clr(t->b, Rpt(ul, t->r.max));
+}
+/*
+ * Move part of a line of text, truncating the source or padding
+ * the destination on the right if necessary.
+ */
+void tw_moverect(Textwin *t, Point uld, Point urd, Point uls, Point urs){
+ int sw, dw, d;
+ if(urs.y!=uls.y) urs=Pt(t->r.max.x, uls.y);
+ if(urd.y!=uld.y) urd=Pt(t->r.max.x, uld.y);
+ sw=uls.x-urs.x;
+ dw=uld.x-urd.x;
+ if(dw>sw){
+ d=dw-sw;
+ pl_clr(t->b, Rect(urd.x-d, urd.y, urd.x, urd.y+t->hgt));
+ dw=sw;
+ }
+ pl_cpy(t->b, uld, Rpt(uls, Pt(uls.x+dw, uls.y+t->hgt)));
+}
+/*
+ * Move a block of characters up or to the left:
+ * Identify contiguous runs of characters whose width doesn't change, and
+ * move them in one bitblt per run.
+ * If we get to a point where source and destination are x-aligned,
+ * they will remain x-aligned for the rest of the block.
+ * Then, if they are y-aligned, they're already in the right place.
+ * Otherwise, we can move them in three bitblts; one if all the
+ * remaining characters are on one line.
+ */
+void tw_moveup(Textwin *t, Point *dp, Point *sp, Point *esp){
+ Point uld, uls; /* upper left of destination/source */
+ int y;
+ while(sp!=esp && sp->x!=dp->x){
+ uld=*dp;
+ uls=*sp;
+ while(sp!=esp && sp->y==uls.y && dp->y==uld.y && sp->x-uls.x==dp->x-uld.x){
+ sp++;
+ dp++;
+ }
+ tw_moverect(t, uld, *dp, uls, *sp);
+ }
+ if(sp==esp || esp->y==dp->y) return;
+ if(esp->y==sp->y){ /* one line only */
+ pl_cpy(t->b, *dp, Rpt(*sp, Pt(esp->x, sp->y+t->hgt)));
+ return;
+ }
+ y=sp->y+t->hgt;
+ pl_cpy(t->b, *dp, Rpt(*sp, Pt(t->r.max.x, y)));
+ pl_cpy(t->b, Pt(t->r.min.x, dp->y+t->hgt),
+ Rect(t->r.min.x, y, t->r.max.x, esp->y));
+ y=dp->y+esp->y-sp->y;
+ pl_cpy(t->b, Pt(t->r.min.x, y),
+ Rect(t->r.min.x, esp->y, esp->x, esp->y+t->hgt));
+}
+/*
+ * Same as above, but moving down and in reverse order, so as not to overwrite stuff
+ * not moved yet.
+ */
+void tw_movedn(Textwin *t, Point *dp, Point *bsp, Point *esp){
+ Point *sp, urs, urd;
+ int dy;
+ dp+=esp-bsp;
+ sp=esp;
+ dy=dp->y-sp->y;
+ while(sp!=bsp && dp[-1].x==sp[-1].x){
+ --dp;
+ --sp;
+ }
+ if(dy!=0){
+ if(sp->y==esp->y)
+ pl_cpy(t->b, *dp, Rect(sp->x, sp->y, esp->x, esp->y+t->hgt));
+ else{
+ pl_cpy(t->b, Pt(t->r.min.x, sp->x+dy),
+ Rect(t->r.min.x, sp->y, esp->x, esp->y+t->hgt));
+ pl_cpy(t->b, Pt(t->r.min.x, dp->y+t->hgt),
+ Rect(t->r.min.x, sp->y+t->hgt, t->r.max.x, esp->y));
+ pl_cpy(t->b, *dp,
+ Rect(sp->x, sp->y, t->r.max.x, sp->y+t->hgt));
+ }
+ }
+ while(sp!=bsp){
+ urd=*dp;
+ urs=*sp;
+ while(sp!=bsp && sp[-1].y==sp[0].y && dp[-1].y==dp[0].y
+ && sp[-1].x-sp[0].x==dp[-1].x-dp[0].x){
+ --sp;
+ --dp;
+ }
+ tw_moverect(t, *dp, urd, *sp, urs);
+ }
+}
+/*
+ * Move the given range of characters, already drawn on
+ * the given textwin, to the given location.
+ * Start and end must both index characters that are initially on-screen.
+ */
+void tw_relocate(Textwin *t, int first, int last, Point dst){
+ Point *srcloc;
+ int nbyte;
+ if(first<t->top || last<first || t->bot<last) return;
+ nbyte=(last-first+1)*sizeof(Point);
+ srcloc=pl_emalloc(nbyte);
+ memmove(srcloc, &t->loc[first-t->top], nbyte);
+ tw_setloc(t, first, last, dst);
+ if(tw_before(t, dst, srcloc[0]))
+ tw_moveup(t, t->loc+first-t->top, srcloc, srcloc+(last-first));
+ else
+ tw_movedn(t, t->loc+first-t->top, srcloc, srcloc+(last-first));
+}
+/*
+ * Replace the runes with indices from r0 to r1-1 with the text
+ * pointed to by text, and with length ntext.
+ * Open up a hole in t->text, t->loc.
+ * Insert new text, calculate their locs (save the extra loc that's overwritten first)
+ * (swap saved & overwritten locs)
+ * move tail.
+ * calc locs and draw new text after tail, if necessary.
+ * draw new text, if necessary
+ */
+void twreplace(Textwin *t, int r0, int r1, Rune *ins, int nins){
+ int olen, nlen, tlen, dtop;
+ olen=t->etext-t->text;
+ nlen=olen+nins-(r1-r0);
+ tlen=t->eslack-t->text;
+ if(nlen>tlen){
+ tlen=nlen+SLACK;
+ t->text=pl_erealloc(t->text, tlen*sizeof(Rune));
+ t->eslack=t->text+tlen;
+ }
+ if(olen!=nlen)
+ memmove(t->text+r0+nins, t->text+r1, (olen-r1)*sizeof(Rune));
+ if(nins!=0) /* ins can be 0 if nins==0 */
+ memmove(t->text+r0, ins, nins*sizeof(Rune));
+ t->etext=t->text+nlen;
+ if(r0>t->bot) /* insertion is completely below visible text */
+ return;
+ if(r1<t->top){ /* insertion is completely above visible text */
+ dtop=nlen-olen;
+ t->top+=dtop;
+ t->bot+=dtop;
+ return;
+ }
+ if(1 || t->bot<=r0+nins){ /* no useful text on screen below r0 */
+ if(r0<=t->top) /* no useful text above, either */
+ t->top=r0;
+ t->bot=tw_setloc(t, r0, nlen, t->loc[r0-t->top]);
+ tw_draw(t, r0, t->bot);
+ tw_clrend(t);
+ return;
+ }
+ /*
+ * code for case where there is useful text below is missing (see `1 ||' above)
+ */
+}
+/*
+ * This works but is stupid.
+ */
+void twscroll(Textwin *t, int top){
+ while(top!=0 && t->text[top-1]!='\n') --top;
+ t->top=top;
+ t->bot=tw_setloc(t, top, t->etext-t->text, t->r.min);
+ tw_draw(t, t->top, t->bot);
+ tw_clrend(t);
+}
+void twreshape(Textwin *t, Rectangle r){
+ t->r=r;
+ t->bot=tw_setloc(t, t->top, t->etext-t->text, t->r.min);
+ tw_draw(t, t->top, t->bot);
+ tw_clrend(t);
+}
+Textwin *twnew(Image *b, Font *f, Rune *text, int ntext){
+ Textwin *t;
+ t=pl_emalloc(sizeof(Textwin));
+ t->text=pl_emalloc((ntext+SLACK)*sizeof(Rune));
+ t->loc=pl_emalloc(SLACK*sizeof(Point));
+ t->eloc=t->loc+SLACK;
+ t->etext=t->text+ntext;
+ t->eslack=t->etext+SLACK;
+ if(ntext) memmove(t->text, text, ntext*sizeof(Rune));
+ t->top=0;
+ t->bot=0;
+ t->sel0=0;
+ t->sel1=0;
+ t->b=b;
+ t->font=f;
+ t->hgt=f->height;
+ t->mintab=stringwidth(f, "0");
+ t->tabstop=8*t->mintab;
+ return t;
+}
+void twfree(Textwin *t){
+ free(t->loc);
+ free(t->text);
+ free(t);
+}
+/*
+ * Correct the character locations in a textwin after the panel is moved.
+ * This horrid hack would not be necessary if loc values were relative
+ * to the panel, rather than absolute.
+ */
+void twmove(Textwin *t, Point d){
+ Point *lp;
+ t->r = rectaddpt(t->r, d);
+ for(lp=t->loc; lp<t->eloc; lp++)
+ *lp = addpt(*lp, d);
+}
--- /dev/null
+++ b/sys/src/cmd/mothra/mothra.c
@@ -1,0 +1,1282 @@
+/*
+ * Trivial web browser
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+#include <plumb.h>
+#include <cursor.h>
+#include <panel.h>
+#include <regexp.h>
+#include "mothra.h"
+#include "rtext.h"
+#include "pldefs.h"
+int debug=0;
+int verbose=0; /* -v flag causes html errors to be written to file-descriptor 2 */
+int killimgs=0; /* should mothra kill images? */
+int defdisplay=1; /* is the default (initial) display visible? */
+int visxbar=0; /* horizontal scrollbar visible? */
+int topxbar=0; /* horizontal scrollbar at top? */
+Panel *root; /* the whole display */
+Panel *alt; /* the alternate display */
+Panel *alttext; /* the alternate text window */
+Panel *cmd; /* command entry */
+Panel *cururl; /* label giving the url of the visible text */
+Panel *list; /* list of previously acquired www pages */
+Panel *msg; /* message display */
+Panel *menu3; /* button 3 menu */
+char mothra[] = "mothra!";
+Cursor patientcurs={
+ 0, 0,
+ 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xe0,
+ 0x07, 0xe0, 0x07, 0xe0, 0x03, 0xc0, 0x0F, 0xF0,
+ 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8,
+ 0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x3F, 0xFC,
+
+ 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x04, 0x20,
+ 0x04, 0x20, 0x06, 0x60, 0x02, 0x40, 0x0C, 0x30,
+ 0x10, 0x08, 0x14, 0x08, 0x14, 0x28, 0x12, 0x28,
+ 0x0A, 0x50, 0x16, 0x68, 0x20, 0x04, 0x3F, 0xFC,
+};
+Cursor confirmcursor={
+ 0, 0,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+
+ 0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F,
+ 0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C,
+ 0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
+ 0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90,
+};
+Cursor readingcurs={
+ -10, -3,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0,
+ 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x1F, 0xF0,
+ 0x3F, 0xF0, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFB, 0xFF, 0xF3, 0xFF, 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0,
+ 0x07, 0xE0, 0x01, 0xE0, 0x03, 0xE0, 0x07, 0x60,
+ 0x0E, 0x60, 0x1C, 0x00, 0x38, 0x00, 0x71, 0xB6,
+ 0x61, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+Cursor mothcurs={
+ {-7, -7},
+ {0x00, 0x00, 0x60, 0x06, 0xf8, 0x1f, 0xfc, 0x3f,
+ 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe,
+ 0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8,
+ 0x1f, 0xf8, 0x0e, 0x70, 0x0c, 0x30, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x58, 0x1a,
+ 0x5c, 0x3a, 0x64, 0x26, 0x27, 0xe4, 0x37, 0xec,
+ 0x37, 0xec, 0x17, 0xe8, 0x1b, 0xd8, 0x0e, 0x70,
+ 0x0c, 0x30, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Www *current=0;
+Url *selection=0;
+int mothmode;
+int kickpipe[2];
+
+void docmd(Panel *, char *);
+void doprev(Panel *, int, int);
+char *urlstr(Url *);
+void setcurrent(int, char *);
+char *genwww(Panel *, int);
+void updtext(Www *);
+void dolink(Panel *, int, Rtext *);
+void hit3(int, int);
+void mothon(Www *, int);
+void killpix(Www *w);
+char *buttons[]={
+ "alt display",
+ "moth mode",
+ "snarf",
+ "paste",
+ "plumb",
+ "search",
+ "save hit",
+ "hit list",
+ "exit",
+ 0
+};
+
+int wwwtop=0;
+Www *www(int index){
+ static Www a[NWWW];
+ return &a[index % NWWW];
+}
+int nwww(void){
+ return wwwtop<NWWW ? wwwtop : NWWW;
+}
+
+int subpanel(Panel *obj, Panel *subj){
+ if(obj==0) return 0;
+ if(obj==subj) return 1;
+ for(obj=obj->child;obj;obj=obj->next)
+ if(subpanel(obj, subj)) return 1;
+ return 0;
+}
+/*
+ * Make sure that the keyboard focus is on-screen, by adjusting it to
+ * be the cmd entry if necessary.
+ */
+int adjkb(void){
+ Rtext *t;
+ int yoffs;
+ if(current){
+ yoffs=text->r.min.y-plgetpostextview(text);
+ for(t=current->text;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){
+ if(t->r.max.y+yoffs>=text->r.min.y
+ && t->r.min.y+yoffs<text->r.max.y
+ && t->b==0
+ && subpanel(t->p, plkbfocus))
+ return 1;
+ }
+ }
+ plgrabkb(cmd);
+ return 0;
+}
+
+void scrollpanel(Panel *p, int dy, int whence)
+{
+ Scroll s;
+
+ s = plgetscroll(p);
+ switch(whence){
+ case 0:
+ s.pos.y = dy;
+ break;
+ case 1:
+ s.pos.y += dy;
+ break;
+ case 2:
+ s.pos.y = s.size.y+dy;
+ break;
+ }
+ if(s.pos.y > s.size.y)
+ s.pos.y = s.size.y;
+ if(s.pos.y < 0)
+ s.pos.y = 0;
+ plsetscroll(p, s);
+}
+
+void sidescroll(int dx, int whence)
+{
+ Scroll s;
+
+ s = plgetscroll(text);
+ switch(whence){
+ case 0:
+ s.pos.x = dx;
+ break;
+ case 1:
+ s.pos.x += dx;
+ break;
+ case 2:
+ s.pos.x = s.size.x+dx;
+ break;
+ }
+ if(s.pos.x > s.size.x - text->size.x + 5)
+ s.pos.x = s.size.x - text->size.x + 5;
+ if(s.pos.x < 0)
+ s.pos.x = 0;
+ plsetscroll(text, s);
+}
+
+void mkpanels(void){
+ Panel *p, *xbar, *ybar, *swap;
+ int xflags;
+
+ if(topxbar)
+ xflags=PACKN|USERFL;
+ else
+ xflags=PACKS|USERFL;
+ if(!visxbar)
+ xflags|=IGNORE;
+ menu3=plmenu(0, 0, buttons, PACKN|FILLX, hit3);
+ root=plpopup(root, EXPAND, 0, 0, menu3);
+ p=plgroup(root, PACKN|FILLX);
+ msg=pllabel(p, PACKN|FILLX, mothra);
+ plplacelabel(msg, PLACEW);
+ pllabel(p, PACKW, "Go:");
+ cmd=plentry(p, PACKN|FILLX, 0, "", docmd);
+ p=plgroup(root, PACKN|FILLX);
+ ybar=plscrollbar(p, PACKW);
+ list=pllist(p, PACKN|FILLX, genwww, 8, doprev);
+ plscroll(list, 0, ybar);
+ p=plgroup(root, PACKN|FILLX);
+ pllabel(p, PACKW, "Url:");
+ cururl=pllabel(p, PACKE|EXPAND, "---");
+ plplacelabel(cururl, PLACEW);
+ p=plgroup(root, PACKN|EXPAND);
+ ybar=plscrollbar(p, PACKW|USERFL);
+ xbar=plscrollbar(p, xflags);
+ text=pltextview(p, PACKE|EXPAND, Pt(0, 0), 0, dolink);
+ plscroll(text, xbar, ybar);
+ plgrabkb(cmd);
+ alt=plpopup(0, PACKE|EXPAND, 0, 0, menu3);
+ ybar=plscrollbar(alt, PACKW|USERFL);
+ xbar=plscrollbar(alt, xflags);
+ alttext=pltextview(alt, PACKE|EXPAND, Pt(0, 0), 0, dolink);
+ plscroll(alttext, xbar, ybar);
+ if(!defdisplay){
+ swap=root;
+ root=alt;
+ alt=swap;
+ swap=text;
+ text=alttext;
+ alttext=swap;
+ }
+}
+int cohort = -1;
+void killcohort(void){
+ int i;
+ for(i=0;i!=3;i++){ /* It's a long way to the kitchen */
+ postnote(PNGROUP, cohort, "kill\n");
+ sleep(1);
+ }
+}
+void catch(void*, char*){
+ noted(NCONT);
+}
+void dienow(void*, char*){
+ noted(NDFLT);
+}
+
+char* mkhome(void){
+ static char *home; /* where to put files */
+ char *henv, *tmp;
+ int f;
+
+ if(home == nil){
+ henv=getenv("home");
+ if(henv){
+ tmp = smprint("%s/lib", henv);
+ f=create(tmp, OREAD, DMDIR|0777);
+ if(f!=-1) close(f);
+ free(tmp);
+
+ home = smprint("%s/lib/mothra", henv);
+ f=create(home, OREAD, DMDIR|0777);
+ if(f!=-1) close(f);
+ free(henv);
+ }
+ else
+ home = strdup("/tmp");
+ }
+ return home;
+}
+
+void donecurs(void){
+ if(current && current->alldone==0)
+ esetcursor(&readingcurs);
+ else if(mothmode)
+ esetcursor(&mothcurs);
+ else
+ esetcursor(0);
+}
+
+void drawlock(int dolock){
+ static int ref = 0;
+ if(dolock){
+ if(ref++ == 0)
+ lockdisplay(display);
+ } else {
+ if(--ref == 0)
+ unlockdisplay(display);
+ }
+}
+
+void scrollto(char *tag);
+void search(void);
+
+extern char *mtpt; /* url */
+
+void main(int argc, char *argv[]){
+ Event e;
+ enum { Eplumb = 128, Ekick = 256 };
+ Plumbmsg *pm;
+ char *url;
+ int i;
+
+ quotefmtinstall();
+ fmtinstall('U', Ufmt);
+
+ ARGBEGIN{
+ case 'd': debug=1; break;
+ case 'v': verbose=1; break;
+ case 'k': killimgs=1; break;
+ case 'm':
+ if(mtpt = ARGF())
+ break;
+ case 'a': defdisplay=0; break;
+ default: goto Usage;
+ }ARGEND
+
+ /*
+ * so that we can stop all subprocesses with a note,
+ * and to isolate rendezvous from other processes
+ */
+ if(cohort=rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
+ atexit(killcohort);
+ notify(catch);
+ waitpid();
+ exits(0);
+ }
+ cohort = getpid();
+ atexit(killcohort);
+
+ switch(argc){
+ default:
+ Usage:
+ fprint(2, "usage: %s [-dvak] [-m mtpt] [url]\n", argv0);
+ exits("usage");
+ case 0:
+ url=getenv("url");
+ break;
+ case 1: url=argv[0]; break;
+ }
+ if(initdraw(0, 0, mothra) < 0)
+ sysfatal("initdraw: %r");
+ display->locking = 1;
+ chrwidth=stringwidth(font, "0");
+ pltabsize(chrwidth, 8*chrwidth);
+ einit(Emouse|Ekeyboard);
+ eplumb(Eplumb, "web");
+ if(pipe(kickpipe) < 0)
+ sysfatal("pipe: %r");
+ estart(Ekick, kickpipe[0], 256);
+ plinit();
+ if(debug) notify(dienow);
+ getfonts();
+ hrule=allocimage(display, Rect(0, 0, 1, 5), screen->chan, 1, DWhite);
+ if(hrule==0)
+ sysfatal("can't allocimage!");
+ draw(hrule, Rect(0,1,1,3), pl_txt, 0, ZP);
+ linespace=display->black;
+ bullet=allocimage(display, Rect(0,0,25, 8), screen->chan, 0, DBlack);
+ fillellipse(bullet, Pt(4,4), 3, 3, pl_txt, ZP);
+ mkpanels();
+ unlockdisplay(display);
+ eresized(0);
+ drawlock(1);
+
+ if(url && url[0])
+ geturl(url, -1, 1, 0);
+
+ mouse.buttons=0;
+ for(;;){
+ if(mouse.buttons==0 && current){
+ if(current->finished){
+ updtext(current);
+ if(current->url->tag[0])
+ scrollto(current->url->tag);
+ current->finished=0;
+ current->changed=0;
+ current->alldone=1;
+ message(mothra);
+ donecurs();
+ }
+ }
+
+ drawlock(0);
+ i=event(&e);
+ drawlock(1);
+
+ switch(i){
+ case Ekick:
+ if(mouse.buttons==0 && current && current->changed){
+ if(!current->finished)
+ updtext(current);
+ current->changed=0;
+ }
+ break;
+ case Ekeyboard:
+ switch(e.kbdc){
+ default:
+Plkey:
+ adjkb();
+ plkeyboard(e.kbdc);
+ break;
+ case Khome:
+ scrollpanel(text, 0, 0);
+ break;
+ case Kup:
+ scrollpanel(text, -text->size.y/4, 1);
+ break;
+ case Kpgup:
+ scrollpanel(text, -text->size.y/2, 1);
+ break;
+ case Kdown:
+ scrollpanel(text, text->size.y/4, 1);
+ break;
+ case Kpgdown:
+ scrollpanel(text, text->size.y/2, 1);
+ break;
+ case Kend:
+ scrollpanel(text, -text->size.y, 2);
+ break;
+ case Kack:
+ search();
+ break;
+ case Kright:
+ if(plkbfocus)
+ goto Plkey;
+ sidescroll(text->size.x/4, 1);
+ break;
+ case Kleft:
+ if(plkbfocus)
+ goto Plkey;
+ sidescroll(-text->size.x/4, 1);
+ break;
+ }
+ break;
+ case Emouse:
+ mouse=e.mouse;
+ if(mouse.buttons & (8|16) && ptinrect(mouse.xy, list->r) && defdisplay){
+ if(mouse.buttons & 8)
+ scrollpanel(list, list->r.min.y - mouse.xy.y, 1);
+ else
+ scrollpanel(list, mouse.xy.y - list->r.min.y, 1);
+ break;
+ }
+ if(mouse.buttons & (8|16) && ptinrect(mouse.xy, text->r)){
+ if(mouse.buttons & 8)
+ scrollpanel(text, text->r.min.y - mouse.xy.y, 1);
+ else
+ scrollpanel(text, mouse.xy.y - text->r.min.y, 1);
+ break;
+ }
+ plmouse(root, &mouse);
+ if(mouse.buttons == 1 && root->lastmouse == root)
+ plgrabkb(nil);
+ break;
+ case Eplumb:
+ pm=e.v;
+ if(pm->ndata > 0)
+ geturl(pm->data, -1, 1, 0);
+ plumbfree(pm);
+ break;
+ }
+ }
+}
+int confirm(int b){
+ Mouse down, up;
+ esetcursor(&confirmcursor);
+ do down=emouse(); while(!down.buttons);
+ do up=emouse(); while(up.buttons);
+ donecurs();
+ return down.buttons==(1<<(b-1));
+}
+void message(char *s, ...){
+ static char buf[1024];
+ char *out;
+ va_list args;
+ va_start(args, s);
+ out = buf + vsnprint(buf, sizeof(buf), s, args);
+ va_end(args);
+ *out='\0';
+ plinitlabel(msg, PACKN|FILLX, buf);
+ if(defdisplay) pldraw(msg, screen);
+}
+void htmlerror(char *name, int line, char *m, ...){
+ static char buf[1024];
+ char *out;
+ va_list args;
+ if(verbose){
+ va_start(args, m);
+ out=buf+snprint(buf, sizeof(buf), "%s: line %d: ", name, line);
+ out+=vsnprint(out, sizeof(buf)-(out-buf)-1, m, args);
+ va_end(args);
+ *out='\0';
+ fprint(2, "%s\n", buf);
+ }
+}
+void eresized(int new){
+ Rectangle r;
+
+ drawlock(1);
+ if(new && getwindow(display, Refnone) == -1) {
+ fprint(2, "getwindow: %r\n");
+ exits("getwindow");
+ }
+ r=screen->r;
+ plpack(root, r);
+ plpack(alt, r);
+ pldraw(cmd, screen); /* put cmd box on screen for alt display */
+ pldraw(root, screen);
+ flushimage(display, 1);
+ drawlock(0);
+}
+void *emalloc(int n){
+ void *v;
+ v=malloc(n);
+ if(v==0)
+ sysfatal("out of memory");
+ memset(v, 0, n);
+ setmalloctag(v, getcallerpc(&n));
+ return v;
+}
+void nstrcpy(char *to, char *from, int len){
+ strncpy(to, from, len);
+ to[len-1] = 0;
+}
+
+char *genwww(Panel *, int index){
+ static char buf[1024];
+ Www *w;
+ int i;
+
+ if(index >= nwww())
+ return 0;
+ i = wwwtop-index-1;
+ w = www(i);
+ if(!w->url)
+ return 0;
+ if(w->title[0]!='\0'){
+ w->gottitle=1;
+ snprint(buf, sizeof(buf), "%2d %s", i+1, w->title);
+ } else
+ snprint(buf, sizeof(buf), "%2d %s", i+1, urlstr(w->url));
+ return buf;
+}
+
+void scrollto(char *tag){
+ Rtext *tp;
+ Action *ap;
+ if(current == nil || text == nil)
+ return;
+ if(tag && tag[0]){
+ for(tp=current->text;tp;tp=tp->next){
+ ap=tp->user;
+ if(ap && ap->name && strcmp(ap->name, tag)==0){
+ current->yoffs=tp->topy;
+ break;
+ }
+ }
+ }
+ plsetpostextview(text, current->yoffs);
+}
+
+/*
+ * selected text should be a url.
+ */
+void setcurrent(int index, char *tag){
+ Www *new;
+ int i;
+ new=www(index);
+ if(new==current && (tag==0 || tag[0]==0)) return;
+ if(current)
+ current->yoffs=plgetpostextview(text);
+ current=new;
+ plinitlabel(cururl, PACKE|EXPAND, current->url->fullname);
+ if(defdisplay) pldraw(cururl, screen);
+ plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
+ scrollto(tag);
+ if((i = open("/dev/label", OWRITE)) >= 0){
+ fprint(i, "%s %s", mothra, current->url->fullname);
+ close(i);
+ }
+ donecurs();
+}
+char *arg(char *s){
+ do ++s; while(*s==' ' || *s=='\t');
+ return s;
+}
+void save(int ifd, char *name){
+ char buf[NNAME+64];
+ int ofd;
+ if(ifd < 0){
+ message("save: %s: %r", name);
+ return;
+ }
+ ofd=create(name, OWRITE, 0666);
+ if(ofd < 0){
+ message("save: %s: %r", name);
+ return;
+ }
+ switch(rfork(RFNOTEG|RFNAMEG|RFFDG|RFMEM|RFPROC|RFNOWAIT)){
+ case -1:
+ message("Can't fork: %r");
+ break;
+ case 0:
+ dup(ifd, 0);
+ close(ifd);
+ dup(ofd, 1);
+ close(ofd);
+
+ snprint(buf, sizeof(buf),
+ "{tput -p || cat} |[2] {aux/statusmsg -k %q >/dev/null || cat >/dev/null}", name);
+ execl("/bin/rc", "rc", "-c", buf, nil);
+ exits("exec");
+ }
+ close(ifd);
+ close(ofd);
+ donecurs();
+}
+void screendump(char *name, int full){
+ Image *b;
+ int fd;
+ fd=create(name, OWRITE, 0666);
+ if(fd==-1){
+ message("can't create %s", name);
+ return;
+ }
+ if(full){
+ writeimage(fd, screen, 0);
+ } else {
+ if((b=allocimage(display, text->r, screen->chan, 0, DNofill)) == nil){
+ message("can't allocate image");
+ close(fd);
+ return;
+ }
+ draw(b, b->r, screen, 0, b->r.min);
+ writeimage(fd, b, 0);
+ freeimage(b);
+ }
+ close(fd);
+}
+
+/*
+ * convert a url into a local file name.
+ */
+char *urltofile(Url *url){
+ char *name, *slash;
+ if(url == nil)
+ return nil;
+ name = urlstr(url);
+ if(name == nil || name[0] == 0)
+ name = "/";
+ if(slash = strrchr(name, '/'))
+ name = slash+1;
+ if(name[0] == 0)
+ name = "index";
+ return name;
+}
+
+/*
+ * user typed a command.
+ */
+void docmd(Panel *p, char *s){
+ char buf[NNAME];
+ int c;
+
+ USED(p);
+ while(*s==' ' || *s=='\t') s++;
+ /*
+ * Non-command does a get on the url
+ */
+ if(s[0]!='\0' && s[1]!='\0' && s[1]!=' ')
+ geturl(s, -1, 0, 0);
+ else switch(c = s[0]){
+ default:
+ message("Unknown command %s", s);
+ break;
+ case 'a':
+ s = arg(s);
+ if(*s=='\0' && selection)
+ hit3(3, 0);
+ break;
+ case 'd':
+ s = arg(s);
+ if(*s){
+ s = smprint("https://lite.duckduckgo.com/lite/?q=%U&kd=-1", s);
+ if(s != nil)
+ geturl(s, -1, 0, 0);
+ free(s);
+ }else
+ message("Usage: d text");
+ break;
+ case 'g':
+ s = arg(s);
+ if(*s=='\0'){
+ case 'r':
+ if(selection)
+ s = urlstr(selection);
+ else
+ message("no url selected");
+ }
+ geturl(s, -1, 0, 0);
+ break;
+ case 'j':
+ s = arg(s);
+ if(*s)
+ doprev(nil, 1, wwwtop-atoi(s));
+ else
+ message("Usage: j index");
+ break;
+ case 'm':
+ mothon(current, !mothmode);
+ break;
+ case 'k':
+ killimgs = !killimgs;
+ if (killimgs)
+ killpix(current);
+ break;
+ case 'w':
+ case 'W':
+ s = arg(s);
+ if(s==0 || *s=='\0'){
+ snprint(buf, sizeof(buf), "dump.bit");
+ if(eenter("Screendump to", buf, sizeof(buf), &mouse) <= 0)
+ break;
+ s = buf;
+ }
+ screendump(s, c == 'W');
+ break;
+ case 's':
+ s = arg(s);
+ if(!selection){
+ message("no url selected");
+ break;
+ }
+ if(s==0 || *s=='\0'){
+ snprint(buf, sizeof(buf), "%s", urltofile(selection));
+ if(eenter("Save to", buf, sizeof(buf), &mouse) <= 0)
+ break;
+ s = buf;
+ }
+ save(urlget(selection, -1), s);
+ break;
+ case 'q':
+ exits(0);
+ }
+ plinitentry(cmd, EXPAND, 0, "", docmd);
+ pldraw(root, screen);
+}
+
+void regerror(char *msg)
+{
+ werrstr("regerror: %s", msg);
+}
+
+void search(void){
+ static char last[256];
+ char buf[256];
+ Reprog *re;
+ Rtext *tp;
+
+ for(;;){
+ if(current == nil || current->text == nil || text == nil)
+ return;
+ strncpy(buf, last, sizeof(buf)-1);
+ if(eenter("Search for", buf, sizeof(buf), &mouse) <= 0)
+ return;
+ strncpy(last, buf, sizeof(buf)-1);
+ re = regcompnl(buf);
+ if(re == nil){
+ message("%r");
+ continue;
+ }
+ for(tp=current->text;tp;tp=tp->next)
+ if(tp->flags & PL_SEL)
+ break;
+ if(tp == nil)
+ tp = current->text;
+ else {
+ tp->flags &= ~PL_SEL;
+ tp = tp->next;
+ }
+ while(tp != nil){
+ tp->flags &= ~PL_SEL;
+ if(tp->text && *tp->text)
+ if(regexec(re, tp->text, nil, 0)){
+ tp->flags |= PL_SEL;
+ plsetpostextview(text, tp->topy);
+ break;
+ }
+ tp = tp->next;
+ }
+ free(re);
+ updtext(current);
+ }
+}
+
+void hiturl(int buttons, char *url, int map){
+ switch(buttons){
+ case 1: geturl(url, -1, 0, map); break;
+ case 2: urlresolve(selurl(url)); break;
+ case 4: message("Button 3 hit on url can't happen!"); break;
+ }
+}
+
+/*
+ * user selected from the list of available pages
+ */
+void doprev(Panel *p, int buttons, int index){
+ int i;
+ USED(p);
+ if(index < 0 || index >= nwww())
+ return;
+ i = wwwtop-index-1;
+ switch(buttons){
+ case 1: setcurrent(i, 0); /* no break ... */
+ case 2: selurl(www(i)->url->fullname); break;
+ case 4: message("Button 3 hit on page can't happen!"); break;
+ }
+}
+
+/*
+ * Follow an html link
+ */
+void dolink(Panel *p, int buttons, Rtext *word){
+ Action *a;
+
+ a=word->user;
+ if(a == nil || (a->link == nil && a->image == nil))
+ return;
+ if(mothmode)
+ hiturl(buttons, a->image ? a->image : a->link, 0);
+ else if(a->link){
+ if(a->ismap){
+ char mapurl[NNAME];
+ Point coord;
+ int yoffs;
+
+ yoffs=plgetpostextview(p);
+ coord=subpt(subpt(mouse.xy, word->r.min), p->r.min);
+ snprint(mapurl, sizeof(mapurl), "%s?%d,%d", a->link, coord.x, coord.y+yoffs);
+ hiturl(buttons, mapurl, 1);
+ } else
+ hiturl(buttons, a->link, 0);
+ }
+}
+
+void filter(int fd, char *cmd){
+ switch(rfork(RFFDG|RFPROC|RFMEM|RFREND|RFNOWAIT|RFNOTEG)){
+ case -1:
+ message("Can't fork!");
+ break;
+ case 0:
+ dupfds(fd, 1, 2, -1);
+ execl("/bin/rc", "rc", "-c", cmd, nil);
+ _exits(0);
+ }
+ close(fd);
+}
+void gettext(Www *w, int fd, int type){
+ switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ message("Can't fork, please wait");
+ break;
+ case 0:
+ if(type==HTML)
+ plrdhtml(w->url->fullname, fd, w, killimgs);
+ else
+ plrdplain(w->url->fullname, fd, w);
+ _exits(0);
+ }
+ close(fd);
+}
+
+void freetext(Rtext *t){
+ Rtext *tt;
+ Action *a;
+
+ tt = t;
+ for(; t!=0; t = t->next){
+ t->b=0;
+ free(t->text);
+ t->text=0;
+ if(a = t->user){
+ t->user=0;
+ free(a->image);
+ free(a->link);
+ free(a->name);
+ free(a);
+ }
+ }
+ plrtfree(tt);
+}
+
+void
+dupfds(int fd, ...)
+{
+ int mfd, n, i;
+ va_list arg;
+ Dir *dir;
+
+ va_start(arg, fd);
+ for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++)
+ if(fd != mfd)
+ if(dup(fd, mfd) < 0)
+ sysfatal("dup: %r");
+ va_end(arg);
+ if((fd = open("/fd", OREAD)) < 0)
+ sysfatal("open: %r");
+ n = dirreadall(fd, &dir);
+ for(i=0; i<n; i++){
+ if(strstr(dir[i].name, "ctl"))
+ continue;
+ fd = atoi(dir[i].name);
+ if(fd >= mfd)
+ close(fd);
+ }
+ free(dir);
+}
+
+int pipeline(int fd, char *fmt, ...)
+{
+ char buf[80], *argv[4];
+ va_list arg;
+ int pfd[2];
+
+ va_start(arg, fmt);
+ vsnprint(buf, sizeof buf, fmt, arg);
+ va_end(arg);
+
+ if(pipe(pfd) < 0){
+ Err:
+ close(fd);
+ werrstr("pipeline for %s failed: %r", buf);
+ return -1;
+ }
+ switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
+ case -1:
+ close(pfd[0]);
+ close(pfd[1]);
+ goto Err;
+ case 0:
+ dupfds(fd, pfd[1], 2, -1);
+ argv[0] = "rc";
+ argv[1] = "-c";
+ argv[2] = buf;
+ argv[3] = nil;
+ exec("/bin/rc", argv);
+ _exits(0);
+ }
+ close(fd);
+ close(pfd[1]);
+ return pfd[0];
+}
+
+char*
+urlstr(Url *url){
+ if(url->fullname[0])
+ return url->fullname;
+ return url->reltext;
+}
+
+Url *copyurl(Url *u){
+ Url *v;
+ v=emalloc(sizeof(Url));
+ *v=*u;
+ v->reltext = strdup(u->reltext);
+ v->basename = strdup(u->basename);
+ return v;
+}
+
+void freeurl(Url *u){
+ free(u->reltext);
+ free(u->basename);
+ free(u);
+}
+
+void seturl(Url *url, char *urlname, char *base){
+ url->reltext = strdup(urlname);
+ url->basename = strdup(base);
+ url->fullname[0] = 0;
+ url->tag[0] = 0;
+ url->map = 0;
+}
+
+Url* selurl(char *urlname){
+ Url *last;
+
+ last=selection;
+ selection=emalloc(sizeof(Url));
+ seturl(selection, urlname, current ? current->url->fullname : "");
+ if(last) freeurl(last);
+ message("selected: %s", urlstr(selection));
+ plgrabkb(cmd); /* for snarf */
+ return selection;
+}
+
+/*
+ * get the file at the given url
+ */
+void geturl(char *urlname, int post, int plumb, int map){
+ int i, fd, typ;
+ char cmd[NNAME];
+ ulong n;
+ Www *w;
+
+ if(*urlname == '#' && post < 0){
+ scrollto(urlname+1);
+ return;
+ }
+
+ selurl(urlname);
+ selection->map=map;
+
+ message("getting %s", urlstr(selection));
+ esetcursor(&patientcurs);
+ for(;;){
+ if((fd=urlget(selection, post)) < 0){
+ message("%r");
+ break;
+ }
+ message("getting %s", selection->fullname);
+ if(mothmode && !plumb)
+ typ = -1;
+ else if((typ = mimetotype(selection->contenttype)) < 0)
+ typ = snooptype(fd);
+
+ switch(typ){
+ default:
+ if(plumb){
+ message("unknown file type");
+ close(fd);
+ break;
+ }
+ snprint(cmd, sizeof(cmd), "%s", urltofile(selection));
+ if(eenter("Save to", cmd, sizeof(cmd), &mouse) <= 0){
+ close(fd);
+ break;
+ }
+ save(fd, cmd);
+ break;
+ case HTML:
+ fd = pipeline(fd, "exec uhtml");
+ case PLAIN:
+ n=0;
+ for(i=wwwtop-1; i>=0 && i!=(wwwtop-NWWW-1); i--){
+ w = www(i);
+ n += countpix(w->pix);
+ if(n >= NPIXMB*1024*1024)
+ killpix(w);
+ }
+ w = www(i = wwwtop++);
+ if(i >= NWWW){
+ /* wait for the reader to finish the document */
+ while(!w->finished && !w->alldone){
+ drawlock(0);
+ sleep(10);
+ drawlock(1);
+ }
+ freetext(w->text);
+ freeform(w->form);
+ freepix(w->pix);
+ freeurl(w->url);
+ memset(w, 0, sizeof(*w));
+ }
+ if(selection->map)
+ w->url=copyurl(current->url);
+ else
+ w->url=copyurl(selection);
+ w->finished = 0;
+ w->alldone = 0;
+ gettext(w, fd, typ);
+ if(rfork(RFPROC|RFMEM|RFNOWAIT) == 0){
+ for(;;){
+ sleep(1000);
+ if(w->finished || w->alldone)
+ break;
+ if(w->changed)
+ write(kickpipe[1], "C", 1);
+ }
+ _exits(0);
+ }
+ plinitlist(list, PACKN|FILLX, genwww, 8, doprev);
+ if(defdisplay) pldraw(list, screen);
+ setcurrent(i, selection->tag);
+ break;
+ case GIF:
+ case JPEG:
+ case PNG:
+ case BMP:
+ case PAGE:
+ filter(fd, "exec page -w");
+ break;
+ }
+ break;
+ }
+ donecurs();
+}
+void updtext(Www *w){
+ Rtext *t;
+ Action *a;
+ if(defdisplay && w->gottitle==0 && w->title[0]!='\0')
+ pldraw(list, screen);
+ for(t=w->text;t;t=t->next){
+ a=t->user;
+ if(a){
+ if(a->field)
+ mkfieldpanel(t);
+ a->field=0;
+ }
+ }
+ if(w != current)
+ return;
+ w->yoffs=plgetpostextview(text);
+ plinittextview(text, PACKE|EXPAND, Pt(0, 0), w->text, dolink);
+ plsetpostextview(text, w->yoffs);
+ pldraw(text, screen);
+}
+
+void finish(Www *w){
+ w->finished = 1;
+ write(kickpipe[1], "F", 1);
+}
+
+void
+mothon(Www *w, int on)
+{
+ Rtext *t, *x;
+ Action *a, *ap;
+
+ if(w==0 || mothmode==on)
+ return;
+ if(mothmode = on)
+ message("moth mode!");
+ else
+ message(mothra);
+ /*
+ * insert or remove artificial links to the href for
+ * images that are also links
+ */
+ for(t=w->text;t;t=t->next){
+ a=t->user;
+ if(a == nil || a->image == nil)
+ continue;
+ if(a->link == nil){
+ if(on)
+ t->flags |= PL_HOT;
+ else
+ t->flags &= ~PL_HOT;
+ continue;
+ }
+ x = t->next;
+ if(on){
+ t->next = nil;
+ ap=emalloc(sizeof(Action));
+ ap->link = strdup(a->link);
+ plrtstr(&t->next, 0, 0, 0, t->font, strdup("->"), PL_HOT, ap);
+ t->next->next = x;
+ } else {
+ if(x) {
+ t->next = x->next;
+ x->next = nil;
+ freetext(x);
+ }
+ }
+ }
+ updtext(w);
+ donecurs();
+}
+
+void killpix(Www *w){
+ Rtext *t;
+
+ if(w==0 || !w->finished && !w->alldone)
+ return;
+ for(t=w->text; t; t=t->next)
+ if(t->b && t->user)
+ t->b=0;
+ freepix(w->pix);
+ w->pix=0;
+ updtext(w);
+}
+void snarf(Panel *p){
+ if(p==0 || p==cmd){
+ if(selection){
+ plputsnarf(urlstr(selection));
+ plsnarf(text);
+ }else
+ message("no url selected");
+ }else
+ plsnarf(p);
+}
+void paste(Panel *p){
+ if(p==0) p=cmd;
+ plpaste(p);
+}
+void hit3(int button, int item){
+ char buf[1024];
+ char name[NNAME];
+ char *s;
+ Panel *swap;
+ int fd;
+ USED(button);
+ switch(item){
+ case 0:
+ swap=root;
+ root=alt;
+ alt=swap;
+ if(current)
+ current->yoffs=plgetpostextview(text);
+ swap=text;
+ text=alttext;
+ alttext=swap;
+ defdisplay=!defdisplay;
+ plpack(root, screen->r);
+ if(current){
+ plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
+ plsetpostextview(text, current->yoffs);
+ }
+ pldraw(root, screen);
+ break;
+ case 1:
+ mothon(current, !mothmode);
+ break;
+ case 2:
+ snarf(plkbfocus);
+ break;
+ case 3:
+ paste(plkbfocus);
+ break;
+ case 4:
+ if(plkbfocus==nil || plkbfocus==cmd){
+ if(text==nil || text->snarf==nil || selection==nil)
+ return;
+ if((s=text->snarf(text))==nil)
+ s=smprint("%s", urlstr(selection));
+ }else
+ if((s=plkbfocus->snarf(plkbfocus))==nil)
+ return;
+ if((fd=plumbopen("send", OWRITE))<0){
+ message("can't plumb");
+ free(s);
+ return;
+ }
+ plumbsendtext(fd, "mothra", nil, getwd(buf, sizeof buf), s);
+ close(fd);
+ free(s);
+ break;
+ case 5:
+ search();
+ break;
+ case 6:
+ if(!selection){
+ message("no url selected");
+ break;
+ }
+ snprint(name, sizeof(name), "%s/hit.html", mkhome());
+ fd=open(name, OWRITE);
+ if(fd==-1){
+ fd=create(name, OWRITE, 0666);
+ if(fd==-1){
+ message("can't open %s", name);
+ return;
+ }
+ fprint(fd, "<html><head><title>Hit List</title></head>\n");
+ fprint(fd, "<body><h1>Hit list</h1>\n");
+ }
+ seek(fd, 0, 2);
+ fprint(fd, "<p><a href=\"%s\">%s</a>\n", urlstr(selection), urlstr(selection));
+ close(fd);
+ break;
+ case 7:
+ snprint(name, sizeof(name), "file:%s/hit.html", mkhome());
+ geturl(name, -1, 1, 0);
+ break;
+ case 8:
+ if(confirm(3))
+ exits(0);
+ break;
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/mothra/rdhtml.c
@@ -1,0 +1,1249 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "mothra.h"
+#include "html.h"
+#include "rtext.h"
+
+typedef struct Fontdata Fontdata;
+struct Fontdata{
+ char *name;
+ Font *font;
+ int space;
+}fontlist[4][4]={
+/* original */
+ "dejavusans/unicode.12", 0, 0,
+ "dejavusans/unicode.12", 0, 0,
+ "dejavusans/unicode.14", 0, 0,
+ "dejavusans/unicode.16", 0, 0,
+
+ "dejavusansit/unicode.12", 0, 0,
+ "dejavusansit/unicode.12", 0, 0,
+ "dejavusansit/unicode.14", 0, 0,
+ "dejavusansit/unicode.16", 0, 0,
+
+ "dejavusansbd/unicode.12", 0, 0,
+ "dejavusansbd/unicode.12", 0, 0,
+ "dejavusansbd/unicode.14", 0, 0,
+ "dejavusansbd/unicode.16", 0, 0,
+
+ "terminus/unicode.12", 0, 0,
+ "terminus/unicode.12", 0, 0,
+ "terminus/unicode.14", 0, 0,
+ "terminus/unicode.16", 0, 0,
+};
+
+static struct{
+ char *prefix;
+ int len;
+}links[]={
+ {"http://", 7},
+ {"https://", 8},
+ {"gemini://", 9},
+ {"ftp://", 6},
+};
+
+Font *pl_whichfont(int f, int s, int *space){
+ char name[NNAME];
+
+ assert(f >= 0 && f < 4);
+ assert(s >= 0 && s < 4);
+
+ if(fontlist[f][s].font==0){
+ snprint(name, sizeof(name), "/lib/font/bit/%s.font", fontlist[f][s].name);
+ fontlist[f][s].font=openfont(display, name);
+ if(fontlist[f][s].font==0) fontlist[f][s].font=font;
+ fontlist[f][s].space=stringwidth(fontlist[f][s].font, "0");
+ }
+ if(space)
+ *space = fontlist[f][s].space;
+ return fontlist[f][s].font;
+}
+
+void getfonts(void){
+ int f, s;
+ for(f=0;f!=4;f++)
+ for(s=0;s!=4;s++)
+ pl_whichfont(f, s, nil);
+}
+void pl_pushstate(Hglob *g, int t){
+ ++g->state;
+ if(g->state==&g->stack[NSTACK]){
+ htmlerror(g->name, g->lineno, "stack overflow at <%s>", tag[t].name);
+ --g->state;
+ }
+ g->state[0]=g->state[-1];
+ g->state->tag=t;
+
+ if(g->state->name)
+ g->state->name = strdup(g->state->name);
+ if(g->state->link)
+ g->state->link = strdup(g->state->link);
+ if(g->state->image)
+ g->state->image = strdup(g->state->image);
+}
+void pl_popstate(Stack *state){
+ free(state->name);
+ state->name=0;
+ free(state->link);
+ state->link=0;
+ free(state->image);
+ state->image=0;
+}
+
+void pl_linespace(Hglob *g){
+ plrtbitmap(&g->dst->text, 1000000, 0, 0, linespace, 0, 0);
+ g->para=0;
+ g->linebrk=0;
+}
+
+int strtolength(Hglob *g, int dir, char *str){
+ double f;
+ Point p;
+
+ f = atof(str);
+ if(cistrstr(str, "%"))
+ return 0;
+ if(cistrstr(str, "em")){
+ p=stringsize(pl_whichfont(g->state->font, g->state->size, nil), "M");
+ return floor(f*((dir==HORIZ) ? p.x : p.y));
+ }
+ return floor(f);
+}
+
+void pl_htmloutput(Hglob *g, int nsp, char *s, Field *field){
+ Font *f;
+ int space, indent, flags, voff;
+ Action *ap;
+ if(g->state->tag==Tag_title
+/* || g->state->tag==Tag_textarea */
+ || g->state->tag==Tag_select){
+ if(s){
+ if(g->tp!=g->text && g->tp!=g->etext && g->tp[-1]!=' ')
+ *g->tp++=' ';
+ while(g->tp!=g->etext && *s) *g->tp++=*s++;
+ if(g->state->tag==Tag_title) g->dst->changed=1;
+ *g->tp='\0';
+ }
+ return;
+ }
+ voff = 0;
+ f=pl_whichfont(g->state->font, g->state->size, &space);
+ if(g->state->sub){
+ voff = g->state->sub * f->ascent / 2;
+ g->state->size = SMALL;
+ f=pl_whichfont(g->state->font, g->state->size, &space);
+ }
+ indent=g->state->margin;
+ if(g->para){
+ space=1000000;
+ indent+=g->state->indent;
+ }
+ else if(g->linebrk)
+ space=1000000;
+ else if(nsp<=0)
+ space=0;
+ if(g->state->image==0 && g->state->link==0 && g->state->name==0 && field==0)
+ ap=0;
+ else{
+ ap=emalloc(sizeof(Action));
+ if(g->state->image)
+ ap->image = strdup(g->state->image);
+ if(g->state->link)
+ ap->link = strdup(g->state->link);
+ if(g->state->name)
+ ap->name = strdup(g->state->name);
+ ap->ismap=g->state->ismap;
+ ap->width=g->state->width;
+ ap->height=g->state->height;
+ ap->field=field;
+ }
+ if(space<0) space=0;
+ if(indent<0) indent=0;
+ if(g->state->pre && s[0]=='\t'){
+ space=0;
+ while(s[0]=='\t'){
+ space++;
+ s++;
+ }
+ space=PL_TAB|space;
+ if(g->linebrk){
+ indent=space;
+ space=1000000;
+ }
+ }
+ flags = 0;
+ if(g->state->link)
+ flags |= PL_HOT;
+ if(g->state->strike)
+ flags |= PL_STR;
+ plrtstr(&g->dst->text, space, indent, voff, f, strdup(s), flags, ap);
+ g->para=0;
+ g->linebrk=0;
+ g->dst->changed=1;
+}
+
+/*
+ * Buffered read, no translation
+ * Save in cache.
+ */
+int pl_bread(Hglob *g){
+ int n, c;
+ char err[1024];
+ if(g->hbufp==g->ehbuf){
+ n=read(g->hfd, g->hbuf, NHBUF);
+ if(n<=0){
+ if(n<0){
+ snprint(err, sizeof(err), "%r reading %s", g->name);
+ pl_htmloutput(g, 1, err, 0);
+ }
+ g->heof=1;
+ return EOF;
+ }
+ g->hbufp=g->hbuf;
+ g->ehbuf=g->hbuf+n;
+ }
+ c=*g->hbufp++&255;
+ if(c=='\n') g->lineno++;
+ return c;
+}
+/*
+ * Read a character, translating \r\n, \n\r, \r and \n into \n
+ * convert to runes.
+ */
+int pl_readc(Hglob *g){
+ static int peek=-1;
+ char crune[UTFmax+1];
+ int c, n;
+ Rune r;
+
+ if(peek!=-1){
+ c=peek;
+ peek=-1;
+ }
+ else
+ c=pl_bread(g);
+ if(c=='\r'){
+ c=pl_bread(g);
+ if(c!='\n') peek=c;
+ return '\n';
+ }
+ if(c=='\n'){
+ c=pl_bread(g);
+ if(c!='\r') peek=c;
+ return '\n';
+ }
+
+ if(c < Runeself)
+ return c;
+
+ crune[0]=c;
+ for (n=1; n<=sizeof(crune); n++){
+ if(fullrune(crune, n)){
+ chartorune(&r, crune);
+ return r;
+ }
+ c=pl_bread(g);
+ if(c==EOF)
+ return EOF;
+ crune[n]=c;
+ }
+ return c;
+}
+void pl_putback(Hglob *g, int c){
+ if(g->npeekc==NPEEKC) htmlerror(g->name, g->lineno, "too much putback!");
+ else if(c!=EOF) g->peekc[g->npeekc++]=c;
+}
+int pl_nextc(Hglob *g){
+ int c;
+
+ if(g->heof) return EOF;
+ if(g->npeekc!=0) return g->peekc[--g->npeekc];
+ c=pl_readc(g);
+ if(c=='<'){
+ c=pl_readc(g);
+ if(c=='/'){
+ c=pl_readc(g);
+ pl_putback(g, c);
+ pl_putback(g, '/');
+ if('a'<=c && c<='z' || 'A'<=c && c<='Z') return STAG;
+ return '<';
+ }
+ pl_putback(g, c);
+ if(c=='!' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || c=='?') return STAG;
+ return '<';
+ }
+ if(c=='>') return ETAG;
+ return c;
+}
+
+char *unquot(char *src){
+ char *e, *dst;
+ int len;
+
+ e=0;
+ while(*src && strchr(" \t\r\n", *src))
+ src++;
+ if(*src=='\'' || *src=='"'){
+ e=strrchr(src+1, *src);
+ src++;
+ }
+ if(e==0) e=strchr(src, 0);
+ len=e-src;
+ dst = emalloc(len+1);
+ memmove(dst, src, len);
+ dst[len]=0;
+ return dst;
+}
+int alnumchar(int c){
+ return 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9';
+}
+int entchar(int c){
+ return c=='#' || alnumchar(c);
+}
+
+/* return url if text token looks like a hyperlink */
+char *linkify(char *s){
+ int i;
+ if(s == 0 && s[0] == 0)
+ return 0;
+ for(i = 0; i < nelem(links); i++)
+ if(!cistrncmp(s, links[i].prefix, links[i].len))
+ return strdup(s);
+ if(!cistrncmp(s, "www.", 4)){
+ int d, i;
+
+ d = 1;
+ for(i=4; s[i]; i++){
+ if(s[i] == '.'){
+ if(s[i-1] == '.')
+ return 0;
+ d++;
+ } else if(!alnumchar(s[i]))
+ break;
+ }
+ if(d >= 2)
+ return smprint("http://%s", s);
+ }
+ return 0;
+}
+
+/*
+ * remove entity references, in place.
+ * Potential bug:
+ * This doesn't work if removing an entity reference can lengthen the string!
+ * Fortunately, this doesn't happen.
+ */
+void pl_rmentities(Hglob *, char *s){
+ char *t, *u, c, svc;
+ t=s;
+ do{
+ c=*s++;
+ if(c=='&'
+ && ((*s=='#' && strchr("0123456789Xx", s[1]))
+ || 'a'<=*s && *s<='z'
+ || 'A'<=*s && *s<='Z')){
+ u=s;
+ while(entchar(*s)) s++;
+ svc=*s;
+ *s = 0;
+ if(svc==';') s++;
+ if(strcmp(u, "lt") == 0)
+ *t++='<';
+ else if(strcmp(u, "gt") == 0)
+ *t++='>';
+ else if(strcmp(u, "quot") == 0)
+ *t++='"';
+ else if(strcmp(u, "apos") == 0)
+ *t++='\'';
+ else if(strcmp(u, "amp") == 0)
+ *t++='&';
+ else {
+ if(svc==';') s--;
+ *s=svc;
+ *t++='&';
+ while(u<s)
+ *t++=*u++;
+ }
+ }
+ else if((uchar)c == 0xc2 && (uchar)*s == 0xad)
+ s++; /* ignore soft hyphens */
+ else
+ *t++=c;
+ }while(c);
+}
+/*
+ * Skip over white space
+ */
+char *pl_whitespace(char *s){
+ while(*s==' ' || *s=='\t' || *s=='\n' || *s=='\r') s++;
+ return s;
+}
+/*
+ * Skip over HTML word
+ */
+char *pl_word(char *s){
+ if ('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z') {
+ s++;
+ while('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z' || '0'<=*s && *s<='9' ||
+ *s=='-' || *s=='.' || *s==':') s++;
+ }
+ return s;
+}
+/*
+ * Skip to matching quote
+ */
+char *pl_quote(char *s){
+ char q;
+ q=*s++;
+ while(*s!=q && *s!='\0') s++;
+ return s;
+}
+void pl_dnl(char *s){
+ char *t;
+ for(t=s;*s;s++) if(*s!='\r' && *s!='\n') *t++=*s;
+ *t='\0';
+}
+void pl_tagparse(Hglob *g, char *str){
+ char *s, *t, *name, c;
+ Pair *ap;
+ Tag *tagp;
+ g->tag=Tag_end;
+ ap=g->attr;
+ if(str[0]=='!'){ /* test should be strncmp(str, "!--", 3)==0 */
+ g->tag=Tag_comment;
+ ap->name=0;
+ return;
+ }
+ if(str[0]=='/') str++;
+ name=str;
+ s=pl_word(str);
+ if(*s!='/' && *s!=' ' && *s!='\n' && *s!='\t' && *s!='\0'){
+ htmlerror(g->name, g->lineno, "bad tag name in %s", str);
+ ap->name=0;
+ return;
+ }
+ if(*s!='\0') *s++='\0';
+ for(t=name;t!=s;t++) if('A'<=*t && *t<='Z') *t+='a'-'A';
+ /*
+ * Binary search would be faster here
+ */
+ for(tagp=tag;tagp->name;tagp++) if(strcmp(name, tagp->name)==0) break;
+ g->tag=tagp-tag;
+ if(g->tag==Tag_end) htmlerror(g->name, g->lineno, "no tag %s", name);
+ for(;;){
+ s=pl_whitespace(s);
+ if(*s=='\0'){
+ ap->name=0;
+ return;
+ }
+ ap->name=s;
+ s=pl_word(s);
+ t=pl_whitespace(s);
+ c=*t;
+ *s='\0';
+ for(s=ap->name;*s;s++) if('A'<=*s && *s<='Z') *s+='a'-'A';
+ if(c=='='){
+ s=pl_whitespace(t+1);
+ if(*s=='\'' || *s=='"'){
+ ap->value=s+1;
+ s=pl_quote(s);
+ if(*s=='\0'){
+ htmlerror(g->name, g->lineno,
+ "No terminating quote in rhs of attribute %s",
+ ap->name);
+ ap->name=0;
+ return;
+ }
+ *s++='\0';
+ pl_dnl(ap->value);
+ }
+ else{
+ /* read up to white space or > */
+ ap->value=s;
+ while(*s!=' ' && *s!='\t' && *s!='\n' && *s!='\0') s++;
+ if(*s!='\0') *s++='\0';
+ }
+ pl_rmentities(g, ap->value);
+ }
+ else{
+ if(c!='\0') s++;
+ ap->value="";
+ }
+ if(ap==&g->attr[NATTR-1])
+ htmlerror(g->name, g->lineno, "too many attributes!");
+ else ap++;
+ }
+}
+int pl_getcomment(Hglob *g){
+ int c;
+ if((c=pl_nextc(g))=='-' && (c=pl_nextc(g))=='-'){
+ /* <!-- eats everything until --> or EOF */
+ for(;;){
+ while((c=pl_nextc(g))!='-' && c!=EOF)
+ ;
+ if(c==EOF)
+ break;
+ if(pl_nextc(g)=='-'){
+ while((c=pl_nextc(g))=='-')
+ ;
+ if(c==ETAG || c==EOF)
+ break;
+ }
+ }
+ } else {
+ /* <! eats everything until > or EOF */
+ while(c!=ETAG && c!=EOF)
+ c=pl_nextc(g);
+ }
+ if(c==EOF)
+ htmlerror(g->name, g->lineno, "EOF in comment");
+ g->tag=Tag_comment;
+ g->attr->name=0;
+ g->token[0]='\0';
+ return TAG;
+}
+
+int lrunetochar(char *p, int v)
+{
+ Rune r;
+
+ r=v;
+ return runetochar(p, &r);
+}
+
+int pl_getscript(Hglob *g){
+ char *tokp, *t;
+ int c;
+ tokp = g->token;
+ *tokp++ = '<';
+ while((c=pl_nextc(g)) != EOF){
+ if(c==STAG || c==' ' || c=='\t' || c=='\n'){
+ pl_putback(g, c);
+ break;
+ }
+ if(c==ETAG) c='>';
+ tokp += lrunetochar(tokp, c);
+ if(c==0 || c=='>' || tokp >= &g->token[NTOKEN-UTFmax-1])
+ break;
+ }
+ *tokp = '\0';
+ t = tag[g->state->tag].name;
+ if(g->token[1] == '/' && cistrncmp(g->token+2, t, strlen(t)) == 0){
+ g->tag=g->state->tag;
+ g->attr->name=0;
+ return ENDTAG;
+ }
+ pl_rmentities(g, g->token);
+ g->nsp=g->spacc;
+ g->spacc=0;
+ return TEXT;
+}
+
+/*
+ * Read a start or end tag -- the caller has read the initial <
+ */
+int pl_gettag(Hglob *g){
+ char *tokp;
+ int c, q;
+ if(g->state->isscript)
+ return pl_getscript(g);
+ if((c=pl_nextc(g))=='!' || c=='?')
+ return pl_getcomment(g);
+ pl_putback(g, c);
+ q = 0;
+ tokp=g->token;
+ while((c=pl_nextc(g))!=EOF){
+ if(c == '=' && q == 0)
+ q = '=';
+ else if(c == '\'' || c == '"'){
+ if(q == '=')
+ q = c;
+ else if(q == c)
+ q = 0;
+ }
+ else if(c == ETAG && q != '\'' && q != '"')
+ break;
+ else if(q == '=' && c != ' ' && c != '\t' && c != '\n')
+ q = 0;
+ if(tokp < &g->token[NTOKEN-UTFmax-1])
+ tokp += lrunetochar(tokp, c);
+ }
+ *tokp='\0';
+ if(c==EOF) htmlerror(g->name, g->lineno, "EOF in tag");
+ pl_tagparse(g, g->token);
+ if(g->token[0]!='/') return TAG;
+ if(g->attr[0].name!=0)
+ htmlerror(g->name, g->lineno, "end tag should not have attributes");
+ return ENDTAG;
+}
+/*
+ * The next token is a tag, an end tag or a sequence of non-white
+ * characters. If inside <pre>, single newlines are converted to <br>,
+ * double newlines are converted to <p> and spaces are preserved.
+ * Otherwise, spaces and newlines are noted and discarded.
+ */
+int pl_gettoken(Hglob *g){
+ char *tokp;
+ int c;
+ if(g->state->pre) switch(c=pl_nextc(g)){
+ case STAG: return pl_gettag(g);
+ case EOF: return EOF;
+ case '\n':
+ switch(c=pl_nextc(g)){
+ case '\n':
+ pl_tagparse(g, "p");
+ return TAG;
+ default:
+ pl_tagparse(g, "br");
+ pl_putback(g, c);
+ return TAG;
+ }
+ default:
+ tokp=g->token;
+ while(c=='\t'){
+ if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
+ c=pl_nextc(g);
+ }
+ while(c!='\t' && c!='\n' && c!=STAG && c!=EOF){
+ if(c==ETAG) c='>';
+ if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
+ c=pl_nextc(g);
+ }
+ *tokp='\0';
+ pl_rmentities(g, g->token);
+ pl_putback(g, c);
+ g->nsp=0;
+ g->spacc=0;
+ return TEXT;
+ }
+ while((c=pl_nextc(g))==' ' || c=='\t' || c=='\n')
+ if(g->spacc!=-1)
+ g->spacc++;
+ switch(c){
+ case STAG: return pl_gettag(g);
+ case EOF: return EOF;
+ default:
+ tokp=g->token;
+ do{
+ if(c==ETAG) c='>';
+ if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
+ c=pl_nextc(g);
+ }while(c!=' ' && c!='\t' && c!='\n' && c!=STAG && c!=EOF);
+ *tokp='\0';
+ pl_rmentities(g, g->token);
+ pl_putback(g, c);
+ g->nsp=g->spacc;
+ g->spacc=0;
+ return TEXT;
+ }
+}
+char *pl_getattr(Pair *attr, char *name){
+ for(;attr->name;attr++)
+ if(strcmp(attr->name, name)==0)
+ return attr->value;
+ return 0;
+}
+int pl_hasattr(Pair *attr, char *name){
+ for(;attr->name;attr++)
+ if(strcmp(attr->name, name)==0)
+ return 1;
+ return 0;
+}
+void plaintext(Hglob *g){
+ char line[NLINE];
+ char *lp, *elp;
+ int c;
+ g->state->font=CWIDTH;
+ g->state->size=NORMAL;
+ g->state->sub = 0;
+ elp=&line[NLINE-UTFmax-1];
+ lp=line;
+ for(;;){
+ c=pl_readc(g);
+ if(c==EOF) break;
+ if(c=='\n' || lp>=elp){
+ *lp='\0';
+ g->linebrk=1;
+ pl_htmloutput(g, 0, line, 0);
+ lp=line;
+ }
+ if(c=='\t'){
+ do *lp++=' '; while(lp<elp && utfnlen(line, lp-line)%8!=0);
+ }
+ else if(c!='\n')
+ lp += lrunetochar(lp, c);
+ }
+ if(lp!=line){
+ *lp='\0';
+ g->linebrk=1;
+ pl_htmloutput(g, 0, line, 0);
+ }
+}
+void plrdplain(char *name, int fd, Www *dst){
+ Hglob g;
+ g.state=g.stack;
+ g.state->tag=Tag_html;
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ g.state->sub=0;
+ g.state->pre=0;
+ g.state->image=0;
+ g.state->link=0;
+ g.state->name=0;
+ g.state->margin=0;
+ g.state->indent=20;
+ g.state->ismap=0;
+ g.state->isscript=0;
+ g.state->strike=0;
+ g.state->width=0;
+ g.state->height=0;
+ g.dst=dst;
+ g.hfd=fd;
+ g.name=name;
+ g.ehbuf=g.hbufp=g.hbuf;
+ g.npeekc=0;
+ g.heof=0;
+ g.lineno=1;
+ g.linebrk=1;
+ g.para=0;
+ g.text=dst->title;
+ g.tp=g.text;
+ g.etext=g.text+NTITLE-1;
+ g.spacc=0;
+ g.form=0;
+ nstrcpy(g.text, name, NTITLE);
+ plaintext(&g);
+ finish(dst);
+}
+void plrdhtml(char *name, int fd, Www *dst, int killimgs){
+ int tagerr;
+ Stack *sp;
+ char buf[20];
+ char *str;
+ Hglob g;
+
+ g.state=g.stack;
+ g.state->tag=Tag_html;
+ g.state->font=ROMAN;
+ g.state->size=NORMAL;
+ g.state->sub=0;
+ g.state->pre=0;
+ g.state->image=0;
+ g.state->link=0;
+ g.state->name=0;
+ g.state->margin=0;
+ g.state->indent=25;
+ g.state->ismap=0;
+ g.state->isscript=0;
+ g.state->strike=0;
+ g.state->width=0;
+ g.state->height=0;
+ g.dst=dst;
+ g.hfd=fd;
+ g.name=name;
+ g.ehbuf=g.hbufp=g.hbuf;
+ g.npeekc=0;
+ g.heof=0;
+ g.lineno=1;
+ g.linebrk=1;
+ g.para=0;
+ g.text=dst->title;
+ g.tp=g.text;
+ g.etext=g.text+NTITLE-1;
+ dst->title[0]='\0';
+ g.spacc=0;
+ g.form=0;
+
+ for(;;) switch(pl_gettoken(&g)){
+ case TAG:
+ switch(tag[g.tag].action){
+ case OPTEND:
+ for(sp=g.state;sp!=g.stack && sp->tag!=g.tag;--sp);
+ if(sp->tag!=g.tag)
+ pl_pushstate(&g, g.tag);
+ else
+ for(;g.state!=sp;--g.state){
+ if(tag[g.state->tag].action!=OPTEND)
+ htmlerror(g.name, g.lineno,
+ "end tag </%s> missing",
+ tag[g.state->tag].name);
+ pl_popstate(g.state);
+ }
+ break;
+ case END:
+ pl_pushstate(&g, g.tag);
+ break;
+ }
+ str=pl_getattr(g.attr, "id");
+ if(str && *str){
+ char *swap;
+
+ swap = g.state->name;
+ g.state->name = str;
+ pl_htmloutput(&g, 0, "", 0);
+ g.state->name = swap;
+ }
+ switch(g.tag){
+ default:
+ htmlerror(g.name, g.lineno,
+ "unimplemented tag <%s>", tag[g.tag].name);
+ break;
+ case Tag_end: /* unrecognized start tag */
+ break;
+ case Tag_img:
+ case Tag_image:
+ str=pl_getattr(g.attr, "src");
+ if(str && *str){
+ free(g.state->image);
+ g.state->image = strdup(str);
+ } else {
+ Pair *a;
+
+ /*
+ * hack to emulate javascript that rewrites some attribute
+ * into src= after page got loaded. just look for some
+ * attribute that looks like a url.
+ */
+ for(a = g.attr; a->name; a++){
+ if(strcmp(a->name, "longdesc") == 0)
+ continue;
+ if(str = linkify(a->value)){
+ free(g.state->image);
+ g.state->image = str;
+ break;
+ }
+ }
+ }
+ g.state->ismap=pl_hasattr(g.attr, "ismap");
+ str=pl_getattr(g.attr, "width");
+ if(str && *str)
+ g.state->width=strtolength(&g, HORIZ, str);
+ str=pl_getattr(g.attr, "height");
+ if(str && *str)
+ g.state->height=strtolength(&g, VERT, str);
+ str=pl_getattr(g.attr, "alt");
+ if(str==0 || *str == 0){
+ if(g.state->image)
+ str=g.state->image;
+ else
+ str="[[image]]";
+ }
+ pl_htmloutput(&g, 0, str, 0);
+ free(g.state->image);
+ g.state->image=0;
+ g.state->ismap=0;
+ g.state->width=0;
+ g.state->height=0;
+ break;
+ case Tag_plaintext:
+ g.spacc=0;
+ plaintext(&g);
+ break;
+ case Tag_comment:
+ case Tag_html:
+ case Tag_link:
+ case Tag_nextid:
+ case Tag_table:
+ break;
+ case Tag_tr:
+ g.spacc=0;
+ g.linebrk=1;
+ break;
+ case Tag_th:
+ g.state->font=BOLD;
+ case Tag_td:
+ g.spacc++;
+ break;
+ case Tag_base:
+ str=pl_getattr(g.attr, "href");
+ if(str && *str){
+ seturl(g.dst->url, str, g.dst->url->fullname);
+ nstrcpy(g.dst->url->fullname, str, sizeof(g.dst->url->fullname));
+ /* base should be a full url, but it often isnt so have to resolve */
+ urlresolve(g.dst->url);
+ }
+ break;
+ case Tag_a:
+ str=pl_getattr(g.attr, "name");
+ if(str && *str){
+ free(g.state->name);
+ g.state->name = strdup(str);
+ }
+ pl_htmloutput(&g, 0, "", 0);
+ str=pl_getattr(g.attr, "href");
+ if(str && *str){
+ free(g.state->link);
+ g.state->link = strdup(str);
+ }
+ break;
+ case Tag_meta:
+ if((str=pl_getattr(g.attr, "http-equiv"))==0)
+ break;
+ if(cistrcmp(str, "refresh"))
+ break;
+ if((str=pl_getattr(g.attr, "content"))==0)
+ break;
+ if((str=strchr(str, '='))==0)
+ break;
+ str++;
+ pl_htmloutput(&g, 0, "[refresh: ", 0);
+ free(g.state->link);
+ g.state->link=unquot(str);
+ pl_htmloutput(&g, 0, g.state->link, 0);
+ free(g.state->link);
+ g.state->link=0;
+ pl_htmloutput(&g, 0, "]", 0);
+ g.linebrk=1;
+ g.spacc=0;
+ break;
+ case Tag_source:
+ case Tag_video:
+ case Tag_audio:
+ case Tag_embed:
+ case Tag_frame:
+ case Tag_iframe:
+ snprint(buf, sizeof(buf), "[%s: ", tag[g.tag].name);
+ pl_htmloutput(&g, 0, buf, 0);
+ str=pl_getattr(g.attr, "src");
+ if(str && *str){
+ free(g.state->link);
+ g.state->link = strdup(str);
+ }
+ str=pl_getattr(g.attr, "name");
+ if(str && *str){
+ free(g.state->name);
+ g.state->name = strdup(str);
+ } else if(g.state->link)
+ str = g.state->link;
+ else
+ str = "";
+ pl_htmloutput(&g, 0, str, 0);
+ free(g.state->link);
+ g.state->link=0;
+ free(g.state->name);
+ g.state->name=0;
+ pl_htmloutput(&g, 0, "]", 0);
+ g.linebrk=1;
+ g.spacc=0;
+ break;
+ case Tag_address:
+ g.spacc=0;
+ g.linebrk=1;
+ g.state->font=ROMAN;
+ g.state->size=NORMAL;
+ g.state->margin=300;
+ g.state->indent=50;
+ break;
+ case Tag_b:
+ case Tag_strong:
+ g.state->font=BOLD;
+ break;
+ case Tag_s:
+ case Tag_strike:
+ case Tag_del:
+ g.state->strike=1;
+ break;
+ case Tag_sub:
+ g.state->sub++;
+ break;
+ case Tag_sup:
+ g.state->sub--;
+ break;
+ case Tag_blockquot:
+ g.spacc=0;
+ g.linebrk=1;
+ g.state->margin+=50;
+ g.state->indent=20;
+ break;
+ case Tag_body:
+ break;
+ case Tag_head:
+ g.state->font=ROMAN;
+ g.state->size=NORMAL;
+ g.state->margin=0;
+ g.state->indent=20;
+ g.spacc=0;
+ break;
+ case Tag_div:
+ g.spacc=0;
+ break;
+ case Tag_br:
+ case Tag_wbr:
+ g.spacc=0;
+ g.linebrk=1;
+ break;
+ case Tag_span:
+ case Tag_center:
+ /* more to come */
+ break;
+ case Tag_cite:
+ case Tag_acronym:
+ g.state->font=ITALIC;
+ g.state->size=NORMAL;
+ break;
+ case Tag_code:
+ case Tag_samp:
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ break;
+ case Tag_dd:
+ g.linebrk=1;
+ g.state->indent=0;
+ g.state->font=ROMAN;
+ g.spacc=0;
+ break;
+ case Tag_dfn:
+ htmlerror(g.name, g.lineno, "<dfn> deprecated");
+ case Tag_abbr:
+ g.state->font=BOLD;
+ g.state->size=NORMAL;
+ break;
+ case Tag_dl:
+ g.state->font=BOLD;
+ g.state->size=NORMAL;
+ g.state->margin+=40;
+ g.spacc=0;
+ break;
+ case Tag_dt:
+ g.para=1;
+ g.state->indent=-40;
+ g.state->font=BOLD;
+ g.spacc=0;
+ break;
+ case Tag_figcaption:
+ g.linebrk=1;
+ break;
+ case Tag_font:
+ /* more to come */
+ break;
+ case Tag_u:
+ htmlerror(g.name, g.lineno, "<u> deprecated");
+ case Tag_ins:
+ case Tag_em:
+ case Tag_i:
+ case Tag_var:
+ g.state->font=ITALIC;
+ break;
+ case Tag_h1:
+ g.linebrk=1;
+ g.state->font=BOLD;
+ g.state->size=ENORMOUS;
+ g.state->margin+=100;
+ g.spacc=0;
+ break;
+ case Tag_h2:
+ pl_linespace(&g);
+ g.state->font=BOLD;
+ g.state->size=ENORMOUS;
+ g.spacc=0;
+ break;
+ case Tag_h3:
+ g.linebrk=1;
+ pl_linespace(&g);
+ g.state->font=ITALIC;
+ g.state->size=ENORMOUS;
+ g.state->margin+=20;
+ g.spacc=0;
+ break;
+ case Tag_h4:
+ pl_linespace(&g);
+ g.state->font=BOLD;
+ g.state->size=LARGE;
+ g.state->margin+=10;
+ g.spacc=0;
+ break;
+ case Tag_h5:
+ pl_linespace(&g);
+ g.state->font=ITALIC;
+ g.state->size=LARGE;
+ g.state->margin+=10;
+ g.spacc=0;
+ break;
+ case Tag_h6:
+ pl_linespace(&g);
+ g.state->font=BOLD;
+ g.state->size=LARGE;
+ g.spacc=0;
+ break;
+ case Tag_hr:
+ g.spacc=0;
+ plrtbitmap(&g.dst->text, 1000000, g.state->margin, 0, hrule, 0, 0);
+ break;
+ case Tag_key:
+ htmlerror(g.name, g.lineno, "<key> deprecated");
+ case Tag_kbd:
+ g.state->font=CWIDTH;
+ break;
+ case Tag_dir:
+ case Tag_menu:
+ case Tag_ol:
+ case Tag_ul:
+ g.state->number=0;
+ g.linebrk=1;
+ g.state->margin+=25;
+ g.state->indent=-25;
+ g.spacc=0;
+ break;
+ case Tag_li:
+ g.spacc=0;
+ switch(g.state->tag){
+ default:
+ htmlerror(g.name, g.lineno, "can't have <li> in <%s>",
+ tag[g.state->tag].name);
+ case Tag_dir: /* supposed to be multi-columns, can't do! */
+ case Tag_menu:
+ g.linebrk=1;
+ break;
+ case Tag_ol:
+ g.para=1;
+ snprint(buf, sizeof(buf), "%2d ", ++g.state->number);
+ pl_htmloutput(&g, 0, buf, 0);
+ break;
+ case Tag_ul:
+ g.para=0;
+ g.linebrk=0;
+ g.spacc=-1;
+ plrtbitmap(&g.dst->text, 100000,
+ g.state->margin+g.state->indent, 0, bullet, 0, 0);
+ break;
+ }
+ break;
+ case Tag_p:
+ pl_linespace(&g);
+ g.linebrk=1;
+ g.spacc=0;
+ break;
+ case Tag_listing:
+ case Tag_xmp:
+ htmlerror(g.name, g.lineno, "<%s> deprecated", tag[g.tag].name);
+ case Tag_pre:
+ g.state->indent=0;
+ g.state->pre=1;
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ pl_linespace(&g);
+ break;
+ case Tag_tt:
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ break;
+ case Tag_title:
+ g.text=dst->title+strlen(dst->title);
+ g.tp=g.text;
+ g.etext=dst->title+NTITLE-1;
+ break;
+ case Tag_form:
+ case Tag_input:
+ case Tag_button:
+ case Tag_select:
+ case Tag_option:
+ case Tag_textarea:
+ case Tag_isindex:
+ rdform(&g);
+ break;
+ case Tag_script:
+ case Tag_style:
+ g.state->isscript=1;
+ break;
+ }
+ break;
+
+ case ENDTAG:
+ /*
+ * If the end tag doesn't match the top, we try to uncover a match
+ * on the stack.
+ */
+ if(g.state->tag!=g.tag){
+ tagerr=0;
+ for(sp=g.state;sp!=g.stack;--sp){
+ if(sp->tag==g.tag)
+ break;
+ if(tag[g.state->tag].action!=OPTEND) tagerr++;
+ }
+ if(sp==g.stack){
+ if(tagerr)
+ htmlerror(g.name, g.lineno,
+ "end tag mismatch <%s>...</%s>, ignored",
+ tag[g.state->tag].name, tag[g.tag].name);
+ }
+ else{
+ if(tagerr)
+ htmlerror(g.name, g.lineno,
+ "end tag mismatch <%s>...</%s>, "
+ "intervening tags popped",
+ tag[g.state->tag].name, tag[g.tag].name);
+
+ for(--sp; g.state!=sp; --g.state)
+ pl_popstate(g.state);
+ }
+ }
+ else if(g.state==g.stack)
+ htmlerror(g.name, g.lineno, "end tag </%s> at stack bottom",
+ tag[g.tag].name);
+ else
+ pl_popstate(g.state--);
+ switch(g.tag){
+ case Tag_select:
+ case Tag_form:
+ case Tag_textarea:
+ endform(&g);
+ break;
+ case Tag_h1:
+ case Tag_h2:
+ case Tag_h3:
+ case Tag_h4:
+ pl_linespace(&g);
+ break;
+ case Tag_div:
+ case Tag_address:
+ case Tag_blockquot:
+ case Tag_body:
+ case Tag_dir:
+ case Tag_dl:
+ case Tag_dt:
+ case Tag_h5:
+ case Tag_h6:
+ case Tag_listing:
+ case Tag_menu:
+ case Tag_ol:
+ case Tag_title:
+ case Tag_ul:
+ case Tag_xmp:
+ case Tag_table:
+ g.linebrk=1;
+ break;
+ case Tag_article:
+ case Tag_pre:
+ pl_linespace(&g);
+ break;
+ }
+ break;
+ case TEXT:
+ if(g.state->isscript)
+ continue;
+ if(g.state->link==0 && (str = linkify(g.token))){
+ g.state->link=str;
+ pl_htmloutput(&g, g.nsp, g.token, 0);
+ free(g.state->link);
+ g.state->link=0;
+ } else
+ pl_htmloutput(&g, g.nsp, g.token, 0);
+ break;
+ case EOF:
+ for(;g.state!=g.stack;--g.state){
+ if(tag[g.state->tag].action!=OPTEND)
+ htmlerror(g.name, g.lineno,
+ "missing </%s> at EOF", tag[g.state->tag].name);
+ pl_popstate(g.state);
+ }
+ pl_popstate(g.state);
+ *g.tp='\0';
+ if (!killimgs)
+ getpix(dst->text, dst);
+ finish(dst);
+ return;
+ }
+}