shithub: spit

ref: bdef1272330cf36855a68abd15d437077402f48e
dir: /spit.c/

View raw version
#include "a.h"
#include "style.h"

Mousectl	*mc;
Keyboardctl *kc;
Image		*cols[Ncols];
char		*ftitlename;
Sfont		*ftitle;
char		*ftextname;
Sfont		*ftext;
char		*ffixedname;
Sfont		*ffixed;
Sdopts		 opts = {.prefilter = 0, .gamma = 1.0 };
int			fullscreen;
Rectangle	screenr;
Image		*slides[Maxslides];
int			nslides;
int			curslide;
Image		*bol;
Image		*bullet;
int			nodraw;

void
ladd(Lines *l, char *s)
{
	if(l->nlines == Maxlines)
		sysfatal("too many lines");
	l->lines[l->nlines++] = strdup(s);
}

void
lclear(Lines *l)
{
	int i;

	for(i = 0; i < l->nlines; i++)
		free(l->lines[i]);
	l->nlines = 0;
}

void
error(char *f, int l, char *m)
{
	fprint(2, "error: %s at %s:%d\n", m, f, l);
	threadexitsall("error");
}

Image*
addslide(void)
{
	++nslides;
	if(nslides >= Maxslides)
		sysfatal("too many slides");
	slides[nslides] = allocimage(display, display->image->r, screen->chan, 0, coldefs[Cbg]);
	if(slides[nslides] == nil)
		sysfatal("allocimage: %r");
	return slides[nslides];
}

Point
rendertitle(Image *b, Point p, char *s)
{
	Rectangle r;
	Image *i;

	r = pt_textrect(ftitle, s);
	i = pt_textdraw(ftitle, s, r, &opts);
	draw(b, rectaddpt(r, p), cols[Cfg], i, ZP);
	freeimage(i);
	p.y += Dy(r);
	line(b, Pt(p.x, p.y), Pt(b->r.max.x - margin, p.y), 0, 0, 2, cols[Cfg], ZP);
	p.y += Dy(r);
	return p;
}

Point
rendertext(Image *b, Point p, char *s)
{
	Rectangle r;
	Image *i;

	r = pt_textrect(ftext, s);
	if(strlen(s) > 0){
		i = pt_textdraw(ftext, s, r, &opts);
		draw(b, Rect(p.x, p.y, p.x+Dx(bol->r), p.y+Dy(bol->r)), bol, 0, ZP);
		draw(b, rectaddpt(r, Pt(p.x + Dx(bol->r) + padding, p.y)), cols[Cfg], i, ZP);
		freeimage(i);
	}
	p.y += Dy(r)*lineheight;
	return p;
}

Point
renderlist(Image *b, Point p, Lines *lines)
{
	Point q;
	Rectangle r;
	Image *t;
	int i;

	p.x += Dx(bol->r);
	for(i = 0; i < lines->nlines; i++){
		draw(b, rectaddpt(bullet->r, p), bullet, nil, ZP);
		q = addpt(p, Pt(Dx(bullet->r) + padding, 0));
		r = pt_textrect(ftext, lines->lines[i]);
		t = pt_textdraw(ftext, lines->lines[i], r, &opts);
		draw(b, rectaddpt(r, q), cols[Cfg], t, ZP);
		freeimage(t);
		p.y += Dy(r);
	}
	p.x -= Dx(bol->r);
	return p;
}

Point
renderquote(Image *b, Point p, Lines *lines)
{
	Rectangle r[Maxlines], br;
	Image *t;
	int i, maxw, maxh;

	maxw = 0;
	maxh = 0;
	for(i = 0; i < lines->nlines; i++){
		r[i] = pt_textrect(ftext, lines->lines[i]);
		maxh += Dy(r[i]);
		if(Dx(r[i]) > maxw)
			maxw = Dx(r[i]);
	}
	p.x += Dx(bol->r) + margin;
	br = Rect(p.x, p.y, p.x + 1.5*maxw + 2*padding, p.y + maxh + 2*padding);
	draw(b, br, cols[Cqbg], nil, ZP);
	line(b, br.min, Pt(br.min.x, br.max.y), 0, 0, padding/2, cols[Cqbord], ZP);
	p.x += padding;
	p.y += padding;
	for(i = 0; i < lines->nlines; i++){
		t = pt_textdraw(ftext, lines->lines[i], r[i], &opts);
		draw(b, rectaddpt(r[i], Pt(p.x+padding, p.y)), cols[Cfg], t, ZP);
		freeimage(t);
		p.y += Dy(r[i]);
	}
	p.x -= padding;
	p.x -= Dx(bol->r) + margin;
	return p;
}


Point
rendercode(Image *b, Point p, Lines *lines)
{
	Rectangle r[Maxlines], br;
	Image *t;
	int i, maxw, maxh;

	maxw = 0;
	maxh = 0;
	for(i = 0; i < lines->nlines; i++){
		r[i] = pt_textrect(ffixed, lines->lines[i]);
		maxh += Dy(r[i]);
		if(Dx(r[i]) > maxw)
			maxw = Dx(r[i]);
	}
	p.x += Dx(bol->r) + margin;
	br = Rect(p.x, p.y, p.x + 1.5*maxw + 2*padding, p.y + maxh + 2*padding);
	draw(b, br, cols[Ccbg], nil, ZP);
	border(b, br, 2, cols[Ccbord], ZP);
	p.y += padding;
	for(i = 0; i < lines->nlines; i++){
		t = pt_textdraw(ffixed, lines->lines[i], r[i], &opts);
		draw(b, rectaddpt(r[i], Pt(p.x+padding, p.y)), cols[Cfg], t, ZP);
		freeimage(t);
		p.y += Dy(r[i]);
	}
	p.x -= Dx(bol->r) + margin;
	return p;
}

Point
renderimage(Image *b, Point p, char *f)
{
	Image *i;
	int fd;

	fd = open(f, OREAD);
	if(fd <= 0)
		sysfatal("open: %r");
	i = readimage(display, fd, 0);
	draw(b, rectaddpt(i->r, p), i, nil, ZP);
	p.y += Dy(i->r) + margin;
	freeimage(i);
	close(fd);
	return p;
}

char*
skipws(char *s)
{
	while(*s == ' ' || *s == '\t')
		++s;
	return s;
}

ulong
estrtoul(char *f, int line, char *s)
{
	char *e;
	ulong c;

	c = strtoul(s, &e, 16);
	if(e == s || e == nil)
		error(f, line, "invalid number");
	return (c << 8) | 0xff;
}	

void
parsestyle(char *f, int line, char *s)
{
	char k[32] = {0}, *p;

	s += 6; /* skip '@style' */
	if(s[0] != '[')
		error(f, line, "expected '[' character in style declaration");
	p = strchr(s, ']');
	if(p == nil)
		error(f, line, "expected ']' character in style declaration");
	if(p == s+1)
		error(f, line, "empty style declaration");
	if(p-s >= 32)
		error(f, line, "invalid key in style declaration");
	strncpy(k, s+1, p-s-1);
	s = skipws(p+1);
	if(*s != '=')
		error(f, line, "expected '=' character in style declaration");
	s = skipws(s+1);
	if(*s == 0)
		error(f, line, "empty style value");
	if(strcmp(k, "margin") == 0){
		margin = atoi(s);
		if(margin == 0) error(f, line, "invalid 'margin' value");
	}else if(strcmp(k, "padding") == 0){
		padding = atoi(s);
		if(padding == 0) error(f, line, "invalid 'padding' value");
	}else if(strcmp(k, "lineheight") == 0){
		lineheight = atof(s);
		if(lineheight < 1.0) error(f, line, "invalid 'lineheight' value");
	}else if(strcmp(k, "color.bg") == 0)
		coldefs[Cbg] = estrtoul(f, line, s);
	else if(strcmp(k, "color.fg") == 0)
		coldefs[Cfg] = estrtoul(f, line, s);
	else if(strcmp(k, "color.quotebg") == 0)
		coldefs[Cqbg] = estrtoul(f, line, s);
	else if(strcmp(k, "color.quoteborder") == 0)
		coldefs[Cqbord] = estrtoul(f, line, s);
	else if(strcmp(k, "color.codebg") == 0)
		coldefs[Ccbg] = estrtoul(f, line, s);
	else if(strcmp(k, "color.codeborder") == 0)
		coldefs[Ccbord] = estrtoul(f, line, s);
	else if(strcmp(k, "title.font") == 0)
		ftitlename = strdup(s);
	else if(strcmp(k, "title.size") == 0)
		ftitlesz = atof(s);
	else if(strcmp(k, "text.font") == 0)
		ftextname = strdup(s);
	else if(strcmp(k, "text.size") == 0)
		ftextsz = atof(s);
	else if(strcmp(k, "fixed.font") == 0)
		ffixedname = strdup(s);
	else if(strcmp(k, "fixed.size") == 0)
		ffixedsz = atof(s);
	else
		error(f, line, "unknown style key");		
}

void
initimages(void)
{
	Point p[4];

	bol = allocimage(display, Rect(0, 0, ftextsz, ftextsz), screen->chan, 0, coldefs[Cbg]);
	p[0] = Pt(0.25*ftextsz, 0.25*ftextsz);
	p[1] = Pt(0.25*ftextsz, 0.75*Dy(bol->r));
	p[2] = Pt(0.75*ftextsz, 0.50*Dy(bol->r));
	p[3] = p[0];
	fillpoly(bol, p, 4, 0, cols[Cfg], ZP);
	bullet = allocimage(display, Rect(0, 0, ftextsz, ftextsz), screen->chan, 0, coldefs[Cbg]);
	fillellipse(bullet, Pt(0.5*ftextsz, 0.5*ftextsz), 0.15*ftextsz, 0.15*ftextsz, cols[Cfg], ZP);
}

void
loadstyle(char *f)
{
	int i;

	if(ftitlename == nil){
		fprint(2, "%s: no title font defined", f);
		threadexitsall("missing font");
	}
	for(i = 0; i < Ncols; i++)
		cols[i] = ealloccol(coldefs[i]);
	ftitle = loadsfont(ftitlename, ftitlesz);
	ftext  = loadsfont(ftextname ? ftextname : ftitlename, ftextsz);
	ffixed = loadsfont(ffixedname ? ffixedname : ftitlename, ffixedsz);
	initimages();
}

void
render(char *f)
{
	enum { Sstart, Scomment, Scontent, Slist, Squote, Scode };
	Biobuf *bp;
	char *l;
	int s, ln;
	Image *b;
	Point p;
	Lines lines = {0};

	s = Sstart;
	b = nil;
	ln = 0;
	nslides = -1;
	curslide = 0;
	if((bp = Bopen(f, OREAD)) == nil)
		sysfatal("Bopen: %r");
	for(;;){
		l = Brdstr(bp, '\n', 1);
		++ln;
		if(l == nil)
			break;
Again:
		switch(s){
		case Sstart:
			if(l[0] == ';' || l[0] == 0){
				free(l);
				continue;
			}
			if(strncmp(l, "@style", 6) == 0){
				parsestyle(f, ln, l);
				free(l);
				continue;
			}
			if(l[0] != '#') error(f, ln, "expected title line");
			if(nslides == -1) /* all style parsed but not slide rendered yet */
				loadstyle(f);
Title:
			p = Pt(margin, margin);
			b = addslide();
			p = rendertitle(b, p, l+2);
			s = Scontent;
			break;
		case Scomment:
			s = Scontent;
			break;
		case Scontent:
			if(l[0] == '#')
				goto Title;
			else if(l[0] == '-'){
				s = Slist;
				goto Again;
			}else if(l[0] == '>'){
				s = Squote;
				goto Again;
			}else if(strncmp(l, "```", 3) == 0){
				s = Scode;
				break;
			}else if(l[0] == '!')
				p = renderimage(b, p, l+2);
			else if(l[0] == ';'){
				s = Scomment;
				break;
			}else
				p = rendertext(b, p, l);
			break;
		case Slist:
			if(l[0] != '-'){
				p = renderlist(b, p, &lines);
				lclear(&lines);
				s = Scontent;
				goto Again;
			}
			ladd(&lines, l+2);
			break;
		case Squote:
			if(l[0] != '>'){
				p = renderquote(b, p, &lines);
				lclear(&lines);
				s = Scontent;
				goto Again;
			}
			ladd(&lines, l+2);
			break;
		case Scode:
			if(strncmp(l, "```", 3) == 0){
				p = rendercode(b, p, &lines);
				lclear(&lines);
				s = Scontent;
				break;
			}
			ladd(&lines, l);
			break;
		}
		free(l);
	}
	if(nslides == -1)
		error(f, ln, "no slides parsed");
}

void
barf(void)
{
	int fd;
	char path[64];
	Image **i;

	for(i=slides; i<slides+nslides+1; i++){
		snprint(path, sizeof path, "spit.%03zd.bit", i - slides);
		if((fd = create(path, OWRITE, 0644)) < 0)
			sysfatal("open: %r");
		writeimage(fd, *i, 0);
		close(fd);
	}
}

void
redraw(void)
{
	draw(screen, screen->r, slides[curslide], nil, ZP);
	flushimage(display, 1);
}

void
wresize(int x, int y, int w, int h)
{
	int fd, n;
	char buf[255];

	fd = open("/dev/wctl", OWRITE|OCEXEC);
	if(fd < 0)
		sysfatal("open: %r");
	n = snprint(buf, sizeof buf, "resize -r %d %d %d %d", x, y, w, h);
	if(write(fd, buf, n) != n)
		fprint(2, "write error: %r\n");
	close(fd);
}

void
resize(void)
{
	if(fullscreen)
		wresize(0, 0, 9999, 9999);
	redraw();
}

void
togglefullscreen(void)
{
	int x, y, w, h;

	if(fullscreen){
		fullscreen = 0;
		x = screenr.min.x;
		y = screenr.min.y;
		w = screenr.max.x;
		h = screenr.max.y;
	}else{
		fullscreen = 1;
		x = 0;
		y = 0;
		w = 9999;
		h = 9999;
	}
	wresize(x, y, w, h);
	redraw();
}

void
usage(void)
{
	fprint(2, "%s [-n] <filename>\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char **argv)
{
	enum { Emouse, Eresize, Ekeyboard };
	char *f;
	Mouse m;
	Rune k;
	Alt alts[] = {
		{ nil, &m,  CHANRCV },
		{ nil, nil, CHANRCV },
		{ nil, &k,  CHANRCV },
		{ nil, nil, CHANEND },
	};

	ftitlename = nil;
	ftextname = nil;
	ffixedname = nil;
	ARGBEGIN{
	case 'n':
		nodraw = 1;
		break;
	default:
		usage();
	}ARGEND;
	if((f = *argv) == nil){
		fprint(2, "missing filename\n");
		usage();
	}
	setfcr(getfcr() & ~(FPZDIV | FPOVFL | FPINVAL));
	if(initdraw(nil, nil, argv0) < 0)
		sysfatal("initdraw: %r");
	if((mc = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if((kc = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");
	display->locking = 0;
	alts[Emouse].c = mc->c;
	alts[Eresize].c = mc->resizec;
	alts[Ekeyboard].c = kc->c;
	memimageinit();
	fullscreen = 0;
	screenr = screen->r;
	render(f);
	if(nodraw){
		barf();
		threadexitsall(nil);
	}
	resize();
	for(;;){
		switch(alt(alts)){
		case Emouse:
			break;
		case Eresize:
			if(getwindow(display, Refnone) < 0)
				sysfatal("getwindow: %r");
			resize();
			break;
		case Ekeyboard:
			switch(k){
			case Kdel:
			case 'q':
				if(fullscreen)
					togglefullscreen();
				threadexitsall(nil);
				break;
			case 'f':
				togglefullscreen();
				break;
			case Kbs:
			case Kleft:
				if(curslide > 0){
					curslide--;
					redraw();
				}
				break;
			case ' ':
			case Kright:
				if(curslide < nslides){
					curslide++;
					redraw();
				}
				break;
			case Khome:
				curslide = 0;
				redraw();
				break;
			case Kend:
				curslide = nslides;
				redraw();
				break;
			}
			break;
		}
	}
}