#include <u.h>
#include <libc.h>
#include <stdio.h>
#include <draw.h>
#include <event.h>
#include <cursor.h>
typedef struct Slide {
char *cmd;
char *arg;
int linum;
int nlines;
char *lines[];
} Slide;
typedef struct GalleryCell {
Image *img;
Rectangle location;
} GalleryCell;
enum {
MenuNext = 0,
MenuPrev,
MenuGoto,
MenuExit,
MenuN
};
char *menuitems[] = {
[MenuNext] = "Next Slide",
[MenuPrev] = "Prev Slide",
[MenuGoto] = "Goto Slide",
[MenuExit] = "Exit",
nil
};
Menu menu = {
.item = menuitems
};
char *mimetab[] = {
"image/png", "png",
"image/jpeg", "jpg",
"image/ppm", "ppm",
"image/tiff", "tif",
"image/bmp", "bmp",
"audio/mpeg", "", /* bug in file(1) */
"image/p9bit", "",
nil
};
int checkonly, lightmode, nslides, cslide, xm, ym;
char *file, *contents, *offset, *tfname, *hfname;
Image *bg,*fg,*tmp;
Font *tfont, *hfont, *nfont;
Slide **slides;
GalleryCell *cells;
Point
SPt(int x, int y)
{
return addpt(Pt(x, y), screen->r.min);
}
Rectangle
SRect(int x0, int y0, int x1, int y1)
{
return rectaddpt(Rect(x0, y0, x1, y1), screen->r.min);
}
Point
imagecenter(Image* img)
{
return divpt(addpt(img->r.min, img->r.max), 2);
}
Point
rectdims(Rectangle r)
{
return subpt(r.max, r.min);
}
char *
nextline(char **p, int *linum)
{
char *line;
for(;;){
(*linum)++;
line = *p;
while(**p != '\n' && **p != '\0') (*p)++;
if(**p == '\0')
return nil;
**p = '\0';
(*p)++;
return line;
}
}
Slide *
nextslide(char **p, int *linum)
{
char *cmd, *arg, *line1, *line, *lineN;
int nlines, blines, i, slinum;
Slide *s;
for(;;){
if((cmd = nextline(p, linum)) == nil)
return nil;
if(cmd[0] != '%')
continue;
break;
}
slinum = *linum;
cmd++;
arg = cmd;
while(*arg != ' ' && *arg != '\t' && *arg != '\0') arg++;
if(*arg == '\0')
arg = nil;
else{
*(arg++) = '\0';
while(*arg == ' ' && *arg == '\t' && *arg != '\0') arg++;
if(*arg == '\0')
arg = nil;
}
line1 = nextline(p, linum);
if(line1 != nil && *line1 == '%'){
*p = line1;
line1[strlen(line1)] = '\n';
line1 = nil;
}
if(line1 == nil){
s = malloc(sizeof(Slide));
s->cmd = cmd;
s->arg = arg;
s->linum = slinum;
s->nlines = 0;
return s;
}
lineN = line1;
nlines = 1;
blines = 0;
while((line = nextline(p, linum)) != nil){
if(*line == '\0'){
blines++;
continue;
}
if(*line == '%'){
*p = line;
line[strlen(line)] = '\n';
break;
}
nlines += blines;
blines = 0;
nlines++;
lineN = line;
}
s = malloc(sizeof(Slide) + sizeof(char*) * nlines);
s->cmd = cmd;
s->arg = arg;
s->linum = slinum;
s->nlines = nlines;
line = line1;
for(i = 0; line != lineN; i++){
s->lines[i] = line;
line += strlen(line) + 1;
}
s->lines[i] = lineN;
return s;
}
#pragma varargck argpos checkerr 2
void
checkerr(Slide *s, char *msg, ...)
{
va_list args;
va_start(args, msg);
fprint(2,"%s:%d: %%%s: ", file, s->linum, s->cmd);
vfprint(2,msg, args);
fprint(2,"\n");
}
void
checkfile(void)
{
int i, waserr, linum;
char *c2, *p;
Slide *s;
Dir *d;
waserr = 0;
linum = 0;
c2 = strdup(contents);
p = c2;
while((s = nextslide(&p, &linum)) != nil){
nslides++;
if(strcmp(s->cmd, "") == 0){
nslides--;
continue;
}else if(strcmp(s->cmd, "bullet") == 0)
continue;
else if(strcmp(s->cmd, "blank") == 0)
continue;
else if(strcmp(s->cmd, "text") == 0)
continue;
else if(strcmp(s->cmd, "gallery") == 0){
if(s->nlines == 0){
waserr = 1;
checkerr(s, "missing lines");
}
for(i = 0; i < s->nlines; i++){
if(*(s->lines[i]) == '\0')
continue;
if((d = dirstat(s->lines[i])) == nil){
waserr = 1;
checkerr(s, "missing file: '%s'", s->lines[i]);
}
free(d);
}
}else if(strcmp(s->cmd, "title") == 0){
if(s->arg == nil){
waserr = 1;
checkerr(s, "missing arg");
}
}else{
waserr = 1;
checkerr(s, "unknown cmd");
}
}
if(waserr)
exits("checkfile");
}
Image *
openimage(char *name)
{
int i, n, p[2], fd;
char buf[64], *conv;
Waitmsg *wm;
Image *ret;
pipe(p);
if(fork() == 0){
close(1);
dup(p[0], 1);
execl("/bin/file", "file", "-m", name, nil);
}
wm = wait();
if(wm->msg != nil && *wm->msg != '\0')
sysfatal("%s", wm->msg);
free(wm);
n = read(p[1], buf, sizeof(buf));
buf[n - 1] = '\0'; /* replace the newline */
conv = nil;
for(i = 0; mimetab[i] != nil; i+=2){
if(strcmp(buf, mimetab[i]) == 0){
conv = mimetab[i+1];
break;
}
}
if(conv == nil)
sysfatal("unknown image format '%s'", buf);
if(*conv == '\0'){
fd = open(name, OREAD);
if(fd < 0)
sysfatal("%r");
ret = readimage(display, fd, 0);
close(fd);
}else{
if(fork() == 0){
close(1);
dup(p[0], 1);
sprint(buf, "/bin/%s", conv);
execl(buf, conv, "-9c", name, nil);
}
ret = readimage(display, p[1], 0);
}
close(p[0]);
close(p[1]);
return ret;
}
Image *
resizeimage(Image *img, Point sdims)
{
int p[2], fd;
char namebuf[64], dimsbuf[64];
Point idims;
Image *ret;
pipe(p);
idims = subpt(img->r.max, img->r.min);
sprint(namebuf, "/tmp/%s.%d.img", argv0, getpid());
fd = create(namebuf, OWRITE, 0600);
if(fd < 0)
sysfatal("couldn't open tmp file: %r");
writeimage(fd, img, 0);
close(fd);
if(fork() == 0){
close(1);
dup(p[0], 1);
if(idims.x < idims.y || (idims.x == idims.y && sdims.x > sdims.y)){
sprint(dimsbuf, "%d", sdims.x);
execl("/bin/resize", "resize", "-x", dimsbuf, namebuf, nil);
}else{
sprint(dimsbuf, "%d", sdims.y);
execl("/bin/resize", "resize", "-y", dimsbuf, namebuf, nil);
}
}
ret = readimage(display, p[1], 0);
close(p[0]);
close(p[1]);
return ret;
}
// FIXME: some of this could be done in-process but i am lazy
// TODO: should check image formats in checkimage() instead
void
loadimages(void){
const Slide *s;
GalleryCell *nextcell;
Image *cimg;
Point sdims;
int i, j, cols, coln, rows, rowstart;
if(cells != nil)
return;
s = slides[cslide];
cells = calloc(s->nlines, sizeof(GalleryCell));
nextcell = cells;
cols = 0;
i = 0;
while(i < s->nlines && *(s->lines[i]) == '\0') i++;
while(i < s->nlines){
cols++;
while(i < s->nlines && *(s->lines[i]) != '\0') i++;
while(i < s->nlines && *(s->lines[i]) == '\0') i++;
}
coln = -1;
rows = 0;
rowstart = 0;
for(i = 0; i < s->nlines; i++){
if(*(s->lines[i]) == '\0'){
rows = 0;
continue;
}
if(rows == 0){
coln++;
j = i;
rowstart = i;
while(j < s->nlines && *(s->lines[j]) != '\0'){
j++;
rows++;
}
}
sdims = subpt(screen->r.max, screen->r.min);
sdims.x /= cols;
sdims.y /= rows;
cimg = openimage(s->lines[i]);
if(cimg == nil)
sysfatal("couldn't convert '%s': %r", s->lines[i]);
waitpid();
nextcell->img = resizeimage(cimg, sdims);
if(nextcell->img == nil)
sysfatal("couldn't scale '%s': %r", s->lines[i]);
freeimage(cimg);
nextcell->location = Rpt(Pt(0,0), sdims);
nextcell->location = rectaddpt(nextcell->location, Pt(sdims.x * coln, sdims.y * (i - rowstart)));
nextcell->location = rectaddpt(nextcell->location, screen->r.min);
nextcell++;
}
}
void
freeimages(void){
const Slide *s;
GalleryCell *p;
int i;
if(cells == nil)
return;
s = slides[cslide];
p = cells;
for(i = 0; i < s->nlines; i++){
if(*(s->lines[i]) == '\0')
continue;
freeimage(p->img);
p++;
}
free(cells);
cells = nil;
}
void
rmtmp(void)
{
char buf[64];
sprint(buf, "/tmp/%s.%d.img", argv0, getpid());
remove(buf);
}
void
render(void)
{
int i, voff, height, width;
char buf[32];
Point p, center, prog0, prog1, smin, smax, imin, imax;
const Slide *s;
GalleryCell *cell;
if(cslide < 0)
cslide = 0;
else if(cslide >= nslides)
cslide = nslides -1;
s = slides[cslide];
center = imagecenter(screen);
smin = screen->r.min;
smax = screen->r.max;
if(strcmp(s->cmd, "blank") == 0)
draw(screen, screen->r, bg, nil, SPt(0, 0));
else if(strcmp(s->cmd, "text") == 0){
draw(screen, screen->r, bg, nil, SPt(0, 0));
voff = ym;
if(s->arg != nil){
string(screen, SPt(xm, ym), fg, Pt(0, 0), hfont, s->arg);
voff += hfont->height;
}else
voff = xm;
for(i = 0; i < s->nlines; i++){
string(screen, SPt(xm + 10, voff), fg, Pt(0, 0), tfont, s->lines[i]);
voff += tfont->height;
}
}else if(strcmp(s->cmd, "title") == 0){
draw(screen, screen->r, bg, nil, SPt(0, 0));
height = hfont->height + tfont->height * s->nlines;
voff = center.y - (height / 2);
p = string(screen, smax, fg, Pt(0,0), hfont, s->arg);
width = p.x - smax.x;
string(screen, Pt(center.x - (width / 2), voff), fg, Pt(0,0), hfont, s->arg);
voff += hfont->height;
for(i = 0; i < s->nlines; i++){
p = string(screen, smax, fg, Pt(0,0), tfont, s->lines[i]);
width = p.x - smax.x;
string(screen, Pt(center.x - (width / 2), voff), fg, Pt(0,0), tfont, s->lines[i]);
voff += tfont->height;
}
}else if(strcmp(s->cmd, "gallery") == 0){
loadimages();
draw(screen, screen->r, bg, nil, SPt(0, 0));
cell = cells;
for(i = 0; i < s->nlines; i++){
if(*(s->lines[i]) == '\0')
continue;
imin = cell->img->r.min;
imax = cell->img->r.max;
p = divpt(subpt(rectdims(cell->img->r), rectdims(cell->location)), 2);
draw(screen, cell->location, cell->img, nil, p);
cell++;
}
}
if(cslide != 0){
prog0 = Pt(smin.x, smax.y);
prog1 = Pt(smin.x + cslide + (smax.x - smin.x) / (nslides - 1) * cslide, smax.y);
line(screen, Pt(smin.x, smax.y), smax, Endsquare, Endsquare, nfont->height, bg, Pt(0,0));
line(screen, prog0, prog1, Endsquare, Enddisc, nfont->height, fg, Pt(0,0));
sprint(buf, "Slide %d", cslide);
stringbg(screen, SPt(2, smax.y - nfont->height - 2), bg, Pt(0,0), nfont, buf, fg, Pt(0,0));
}
}
void
eresized(int new)
{
if(new && getwindow(display, Refnone) < 0)
fprint(2,"can't reattach to window\n");
freeimages();
render();
}
void
usage(void)
{
fprint(2,"usage: %s [ -cl ] [ -x xmargin ] [ -y ymargin ] [ -f textfont ] [ -h headerfont ] filename\n", argv0);
exits("usage");
}
void
main(int argc, char *argv[])
{
int etype, fd, i, linum;
char slabel[32], ask[32];
Event e;
Mouse m, lastm;
Dir *d;
xm = 50;
ym = 10;
tfname = "/n/ttf/lucida.ttf.32/font";
hfname = "/n/ttf/lucida.ttf.64/font";
ARGBEGIN{
case 'c':
checkonly = 1;
break;
case 'l':
lightmode = 1;
break;
case 'f':
tfname = EARGF(usage());
break;
case 'h':
hfname = EARGF(usage());
break;
case 'x':
xm = atoi(EARGF(usage()));
break;
case 'y':
ym = atoi(EARGF(usage()));
break;
default:
usage();
}ARGEND
if(argc != 1)
usage();
fmtinstall('P', Pfmt);
fmtinstall('R', Rfmt);
file = argv[0];
if((fd = open(file, OREAD)) < 0)
sysfatal("%r");
if((d = dirfstat(fd)) == nil)
sysfatal("%r");
contents = malloc(d->length + 2);
if(readn(fd, contents, d->length) < 0)
sysfatal("%r");
// insert a newline, just in case
contents[d->length] = '\n';
contents[d->length + 1] = '\0';
offset = contents;
snprintf(slabel, sizeof(slabel), "%s %s", argv0, d->name);
free(d);
close(fd);
checkfile();
if(checkonly)
exits(nil);
atexit(rmtmp);
atexit(freeimages);
slides = malloc(nslides*sizeof(Slide*));
for(i = 0; i < nslides; i++){
slides[i] = nextslide(&offset, &linum);
if(*slides[i]->cmd == '\0')
i--;
}
if(initdraw(nil, nil, slabel) < 0)
sysfatal("initdraw: %r");
bg = allocimage(display, Rect(0,0, 1,1), RGB16, 1, DBlack);
if(bg == nil)
sysfatal("couldn't allocimage!");
fg = allocimage(display, Rect(0,0, 1,1), RGB16, 1, DWhite);
if(fg == nil)
sysfatal("couldn't allocimage!");
if(lightmode){
tmp = fg;
fg = bg;
bg = tmp;
}
if(strncmp(tfname, "/n/ttf", 6) == 0 || strncmp(tfname, "/n/ttf", 6) == 0){
if(fork() == 0)
execl("/bin/truetypefs", "truetypefs", nil);
else
sleep(200);
}
tfont = openfont(display, tfname);
if(tfont == nil)
sysfatal("couldn't open font '%s'", tfname);
hfont = openfont(display, hfname);
if(hfont == nil)
sysfatal("couldn't open font '%s'", hfname);
nfont = openfont(display, "/lib/font/bit/vga/unicode.font");
if(nfont == nil)
sysfatal("couldn't open font '/lib/bit/vga/unicode.font'");
einit(Emouse|Ekeyboard);
for(;;){
render();
etype = event(&e);
if(etype == Emouse){
m = e.mouse;
if(m.buttons & 1 && !(lastm.buttons & 1)){
freeimages();
cslide--;
}
if(m.buttons & 4 && !(lastm.buttons & 4)){
freeimages();
cslide++;
}
if(m.buttons & 2){
emenuhit(2, &m, &menu);
switch(menu.lasthit){
case MenuNext:
freeimages();
cslide++;
break;
case MenuPrev:
freeimages();
cslide--;
break;
case MenuGoto:
sprintf(ask, "%d", cslide);
if(eenter("Go to", ask, sizeof(ask), &m) <= 0)
break;
freeimages();
if(strncmp(ask, "end", 3) == 0)
cslide = nslides;
else
cslide = atoi(ask);
break;
case MenuExit:
exits(nil);
break;
}
}
lastm = m;
}else if(etype == Ekeyboard){
switch(e.kbdc){
case 0xf012:
case ' ':
freeimages();
cslide++;
break;
case 0xf011:
freeimages();
cslide--;
break;
case 0x7f:
case 'q':
exits(nil);
break;
}
}
}
}