#include #include #include #include #include #include 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 waserr, linum; char *c2, *p; Slide *s; 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) continue; 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, 5, bg, Pt(0,0)); line(screen, prog0, prog1, Endsquare, Enddisc, 5, fg, Pt(0,0)); sprint(buf, "Slide %d", cslide); stringbg(screen, Pt(5, smax.y - 5 - nfont->height), fg, Pt(0,0), nfont, buf, bg, 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.80/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"); // cursed but makes the handling nicer above 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; } } } }