├── README.md ├── a.h ├── glenda.bit ├── libpt ├── README.md ├── mkfile ├── pt.c ├── pt.h ├── pt_private.h ├── stb_truetype.h └── test.c ├── mkfile ├── sample.spit ├── screenshot1.png ├── screenshot2.png ├── spit.c ├── spit.man ├── style.h └── utils.c /README.md: -------------------------------------------------------------------------------- 1 | spit 2 | ==== 3 | A simple presentation tool for 9front. 4 | spit relies on sigrid's [libpt](https://git.sr.ht/~ft/libpt) for text rendering using TTF fonts. 5 | ![screenshot](screenshot1.png) 6 | ![screenshot](screenshot2.png) 7 | 8 | To toggle fullscreen mode press `f`. Navigate between slides using left/right arrow (or backspace/space) or home/end. Press `q` or `del` to quit spit. 9 | 10 | Installation: 11 | ------------- 12 | ```sh 13 | % git/clone 14 | % cd spit 15 | % mk 16 | % mk install 17 | ``` 18 | 19 | Usage: 20 | ------ 21 | See spit(1) and `sample.spit` for a sample presentation 22 | ```sh 23 | % spit sample.spit 24 | ``` 25 | 26 | Author: 27 | ------- 28 | phil9 29 | 30 | License: 31 | -------- 32 | MIT 33 | 34 | Bugs: 35 | ----- 36 | Sure, please report! 37 | -------------------------------------------------------------------------------- /a.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct Lines Lines; 12 | 13 | enum 14 | { 15 | Maxslides = 128, 16 | Maxlines = 16 17 | }; 18 | 19 | struct Lines 20 | { 21 | char *lines[Maxlines]; 22 | uint nlines; 23 | }; 24 | 25 | enum 26 | { 27 | Cbg, 28 | Cfg, 29 | Cqbg, 30 | Cqbord, 31 | Ccbg, 32 | Ccbord, 33 | Ncols, 34 | }; 35 | 36 | Sfont* loadsfont(char*, float); 37 | void* emalloc(ulong); 38 | Image* ealloccol(ulong); 39 | -------------------------------------------------------------------------------- /glenda.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telephil9/spit/bdef1272330cf36855a68abd15d437077402f48e/glenda.bit -------------------------------------------------------------------------------- /libpt/README.md: -------------------------------------------------------------------------------- 1 | # libpt 2 | 3 | Pretty text. Rendering pretty text in 9front is not that easy, this 4 | project aims at providing libraries for low- and high-level text 5 | rendering that produces GOOD LOOKING text. 6 | 7 | ## Testing 8 | 9 | Put your favourite font as `font.ttf` and run: 10 | 11 | ``` 12 | mk && ./6.test font.ttf README.md 13 | ``` 14 | 15 | Click with right mouse button and drag up/down or press `-` and 16 | `+` to change font size. 17 | 18 | Middle mouse button moves the text around. 19 | 20 | Pressing `i` inverts the color scheme. 21 | 22 | `Up`/`down` changes gamma (contrast?) correction. `Enter` resets it. 23 | -------------------------------------------------------------------------------- /libpt/mkfile: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "pt.h" 9 | 10 | #define MAX(a,b) ((a)>=(b)?(a):(b)) 11 | 12 | #define rchr(s) (be ? ((s)[0]<<8 | (s)[1]) : ((s)[1]<<8 | (s)[0])) 13 | 14 | static const uchar mark[] = {0x00, 0x00, 0xc0, 0xe0, 0xf0}; 15 | 16 | static int 17 | utf16to8(uchar *o, int osz, const uchar *s, int sz) 18 | { 19 | int i, be, c, c2, wr, j; 20 | 21 | i = 0; 22 | be = 1; 23 | if(s[0] == 0xfe && s[1] == 0xff) 24 | i += 2; 25 | else if(s[0] == 0xff && s[1] == 0xfe){ 26 | be = 0; 27 | i += 2; 28 | } 29 | 30 | for(; i < sz-1 && osz > 1;){ 31 | c = rchr(&s[i]); 32 | i += 2; 33 | if(c >= 0xd800 && c <= 0xdbff && i < sz-1){ 34 | c2 = rchr(&s[i]); 35 | if(c2 >= 0xdc00 && c2 <= 0xdfff){ 36 | c = 0x10000 | (c - 0xd800)<<10 | (c2 - 0xdc00); 37 | i += 2; 38 | }else 39 | return -1; 40 | }else if(c >= 0xdc00 && c <= 0xdfff) 41 | return -1; 42 | 43 | if(c < 0x80) 44 | wr = 1; 45 | else if(c < 0x800) 46 | wr = 2; 47 | else if(c < 0x10000) 48 | wr = 3; 49 | else 50 | wr = 4; 51 | 52 | osz -= wr; 53 | if(osz < 1) 54 | break; 55 | 56 | o += wr; 57 | for(j = wr; j > 1; j--){ 58 | *(--o) = (c & 0xbf) | 0x80; 59 | c >>= 6; 60 | } 61 | *(--o) = c | mark[wr]; 62 | o += wr; 63 | } 64 | 65 | *o = 0; 66 | return i; 67 | } 68 | 69 | #define STB_TRUETYPE_IMPLEMENTATION 70 | static void * 71 | STBTT_malloc(int x, void *) 72 | { 73 | return malloc(x); 74 | } 75 | static void 76 | STBTT_free(void *p, void *) 77 | { 78 | free(p); 79 | } 80 | #define STBTT_malloc STBTT_malloc 81 | #define STBTT_free STBTT_free 82 | #define STBTT_ifloor(x) ((int)floor(x)) 83 | #define STBTT_iceil(x) ((int)ceil(x)) 84 | #define STBTT_sqrt(x) sqrt(x) 85 | #define STBTT_pow(x,y) pow(x,y) 86 | #define STBTT_fmod(x,y) fmod(x,y) 87 | #define STBTT_cos(x) cos(x) 88 | #define STBTT_acos(x) acos(x) 89 | #define STBTT_fabs(x) fabs(x) 90 | #define STBTT_assert(x) assert(x) 91 | #define STBTT_strlen(x) strlen(x) 92 | #define STBTT_memcpy memmove 93 | #define STBTT_memset memset 94 | typedef usize size_t; 95 | #define NULL nil 96 | #define STBTT_RASTERIZER_VERSION 2 /* that one is definitely FASTER on amd64 */ 97 | #include "pt_private.h" 98 | 99 | Sfont * 100 | pt_font(uchar *data, float height) 101 | { 102 | Sfont *f; 103 | 104 | f = mallocz(sizeof(*f), 1); 105 | if(stbtt_InitFont(&f->fi, data, stbtt_GetFontOffsetForIndex(data, 0)) == 0){ 106 | werrstr("stbtt_InitFont: %r"); 107 | goto err; 108 | } 109 | 110 | f->scale = stbtt_ScaleForPixelHeight(&f->fi, height); 111 | f->height = height; 112 | stbtt_GetFontVMetrics(&f->fi, &f->asc, &f->desc, &f->linegap); 113 | f->lineh = f->asc - f->desc + f->linegap; 114 | 115 | stbtt_GetFontBoundingBox(&f->fi, &f->bb.min.x, &f->bb.min.y, &f->bb.max.x, &f->bb.max.y); 116 | f->bb.max.x = ceil(f->scale*(f->bb.max.x - f->bb.min.x)); 117 | f->bb.max.y = ceil(f->scale*(f->bb.max.y - f->bb.min.y)); 118 | f->bb.min.x = 0; 119 | f->bb.min.y = 0; 120 | 121 | f->bb.max.x = (f->bb.max.x + 3) & ~3; 122 | f->bb.max.y = (f->bb.max.y + 3) & ~3; 123 | 124 | if(badrect(f->bb)){ 125 | werrstr("invalid glyph bbox: %R", f->bb); 126 | goto err; 127 | } 128 | if((f->g = allocmemimage(f->bb, GREY8)) == nil) 129 | goto err; 130 | if((f->b = allocmemimage(f->bb, GREY8)) == nil) 131 | goto err; 132 | memfillcolor(f->b, DWhite); 133 | 134 | return f; 135 | err: 136 | werrstr("pt_font: %r"); 137 | pt_freefont(f); 138 | return nil; 139 | } 140 | 141 | void 142 | pt_freefont(Sfont *f) 143 | { 144 | if(f == nil) 145 | return; 146 | freememimage(f->g); 147 | freememimage(f->b); 148 | free(f); 149 | } 150 | 151 | static u32int * 152 | bsearch(u32int c, u32int *t, int n) 153 | { 154 | u32int *p; 155 | int m; 156 | 157 | while(n > 1){ 158 | m = n/2; 159 | p = t + m; 160 | if(c >= p[0]) { 161 | t = p; 162 | n = n-m; 163 | } else 164 | n = m; 165 | } 166 | if(n && c >= t[0]) 167 | return t; 168 | return 0; 169 | } 170 | 171 | static int 172 | code2glyph(Sfont *f, u32int c) 173 | { 174 | return stbtt_FindGlyphIndex(&f->fi, c); 175 | } 176 | 177 | Rectangle 178 | pt_textrect(Sfont *f, char *s) 179 | { 180 | int i, n; 181 | int glyph, prevglyph, adv, kern, leftb; 182 | int w, h, maxw; 183 | Rune r; 184 | 185 | w = maxw = 0; 186 | h = f->lineh; 187 | prevglyph = 0; 188 | for(i = 0; s[i]; i += n){ 189 | n = chartorune(&r, s+i); 190 | glyph = code2glyph(f, r); 191 | if(r != '\n' && glyph > 0){ 192 | stbtt_GetGlyphHMetrics(&f->fi, glyph, &adv, &leftb); 193 | kern = prevglyph ? stbtt_GetGlyphKernAdvance(&f->fi, prevglyph, glyph) : 0; 194 | w += adv + kern; 195 | }else 196 | glyph = 0; 197 | prevglyph = glyph; 198 | maxw = MAX(maxw, w); 199 | if(r == '\n'){ 200 | h += f->lineh; 201 | w = 0; 202 | } 203 | } 204 | w = ceil(f->scale*maxw); 205 | h = ceil(f->scale*(h+1)); 206 | 207 | return Rect(0, 0, w, h); 208 | } 209 | 210 | Image * 211 | pt_textdraw(Sfont *f, char *s, Rectangle rect, Sdopts *opts) 212 | { 213 | int i, base, n, x0, y0, prefilter; 214 | int glyph, prevglyph, adv, leftb; 215 | float x, shift, subx, suby; 216 | Rune r; 217 | Rectangle o; 218 | Memimage *mi; 219 | Image *image; 220 | uchar *p; 221 | 222 | if((mi = allocmemimage(rect, GREY8)) == nil) 223 | return nil; 224 | memfillcolor(mi, DWhite); 225 | prevglyph = 0; 226 | x = 0.0f; 227 | o = rect; 228 | base = 0; 229 | prefilter = opts != nil ? opts->prefilter : 0; 230 | if(prefilter > STBTT_MAX_OVERSAMPLE) 231 | prefilter = STBTT_MAX_OVERSAMPLE; 232 | 233 | for(i = 0; s[i]; i += n){ 234 | n = chartorune(&r, s+i); 235 | glyph = code2glyph(f, r); 236 | 237 | if(prevglyph > 0 && glyph > 0 && r != '\n') 238 | x += stbtt_GetGlyphKernAdvance(&f->fi, prevglyph, glyph) * f->scale; 239 | shift = x - floor(x); 240 | 241 | if(r != '\n' && glyph > 0){ 242 | memfillcolor(f->g, DBlack); 243 | 244 | if(prefilter > 0){ 245 | stbtt_MakeGlyphBitmapSubpixelPrefilter( 246 | &f->fi, 247 | byteaddr(f->g, f->bb.min), 248 | Dx(f->bb), Dy(f->bb), 249 | bytesperline(f->bb, f->g->depth), 250 | f->scale, f->scale, 251 | shift, 0.0f, 252 | prefilter, prefilter, 253 | &subx, &suby, 254 | glyph 255 | ); 256 | }else{ 257 | stbtt_MakeGlyphBitmapSubpixel( 258 | &f->fi, 259 | byteaddr(f->g, f->bb.min), 260 | Dx(f->bb), Dy(f->bb), 261 | bytesperline(f->bb, f->g->depth), 262 | f->scale, f->scale, 263 | shift, 0.0f, 264 | glyph 265 | ); 266 | } 267 | 268 | stbtt_GetGlyphBitmapBoxSubpixel( 269 | &f->fi, 270 | glyph, 271 | f->scale, f->scale, 272 | shift, 0.0f, 273 | &x0, &y0, nil, nil 274 | ); 275 | o.min.x = floor(x) + x0; 276 | o.min.y = base + f->asc*f->scale + y0; 277 | memimagedraw(mi, o, f->b, ZP, f->g, ZP, DoutS); 278 | stbtt_GetGlyphHMetrics(&f->fi, glyph, &adv, &leftb); 279 | x += adv * f->scale; 280 | }else 281 | glyph = 0; 282 | prevglyph = glyph; 283 | if(r == '\n'){ 284 | x = x0 = 0; 285 | prevglyph = 0; 286 | base += ceil(f->scale * f->lineh); 287 | } 288 | } 289 | 290 | n = 60 + Dx(rect)*Dy(rect); 291 | p = malloc(n); 292 | n = unloadmemimage(mi, rect, p, n); 293 | freememimage(mi); 294 | 295 | image = allocimage(display, rect, GREY8, 0, DNofill); 296 | if(opts != nil && opts->gamma != 1.0){ 297 | float igamma = 1.0/opts->gamma; 298 | for(i = 0; i < n; i++){ 299 | if(p[i] < 255){ 300 | float g = pow(p[i], igamma); 301 | p[i] = g < 256 ? g : 255; 302 | } 303 | } 304 | } 305 | for(i = 0; i < n; i++) 306 | p[i] = 255 - p[i]; 307 | loadimage(image, rect, p, n); 308 | free(p); 309 | 310 | return image; 311 | } 312 | -------------------------------------------------------------------------------- /libpt/pt.h: -------------------------------------------------------------------------------- 1 | typedef struct Sdopts Sdopts; 2 | typedef struct Sfont Sfont; 3 | #pragma incomplete Sfont 4 | 5 | struct Sdopts { 6 | int prefilter; 7 | float gamma; 8 | }; 9 | 10 | struct Sfontinfo { 11 | char *family; 12 | char *subfamily; 13 | 14 | }; 15 | 16 | Sfont *pt_font(uchar *data, float height); 17 | void pt_freefont(Sfont *f); 18 | Rectangle pt_textrect(Sfont *f, char *s); 19 | Image *pt_textdraw(Sfont *f, char *s, Rectangle rect, Sdopts *opts); 20 | -------------------------------------------------------------------------------- /libpt/pt_private.h: -------------------------------------------------------------------------------- 1 | #include "stb_truetype.h" 2 | 3 | struct Sfont { 4 | Memimage *g; 5 | Memimage *b; 6 | 7 | Rectangle bb; 8 | float scale; 9 | float height; 10 | int asc; 11 | int desc; 12 | int linegap; 13 | int lineh; 14 | 15 | stbtt_fontinfo fi; 16 | }; 17 | -------------------------------------------------------------------------------- /libpt/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "pt.h" 9 | 10 | #define MAX(a,b) ((a)>=(b)?(a):(b)) 11 | 12 | static Sfont *fo; 13 | static Image *image; 14 | static int invert, prefilter; 15 | static Point offset = {8, 8}; 16 | static Sdopts opts = { 17 | .prefilter = 0, 18 | .gamma = 1.0, 19 | }; 20 | static char info[128]; 21 | int mainstacksize = 32768; 22 | static Image *fg; 23 | 24 | static void 25 | redraw(void) 26 | { 27 | Rectangle r; 28 | 29 | lockdisplay(display); 30 | 31 | draw(screen, screen->r, display->white, nil, ZP); 32 | 33 | r = rectaddpt(screen->r, offset); 34 | r.max = screen->r.max; 35 | draw(screen, r, fg, image, ZP); 36 | 37 | r = screen->r; 38 | r.min.y = r.max.y - font->height - 4; 39 | r.min.x += 4; 40 | stringbg(screen, r.min, invert ? display->white : display->black, ZP, font, info, invert ? display->black : display->white, ZP); 41 | 42 | flushimage(display, 1); 43 | unlockdisplay(display); 44 | } 45 | 46 | static void * 47 | loadall(char *path) 48 | { 49 | uchar *d; 50 | uvlong sz; 51 | Dir *st; 52 | int f, r, n; 53 | 54 | d = nil; 55 | sz = 0; 56 | if((f = open(path, OREAD)) < 0 || (st = dirfstat(f)) == nil) 57 | goto end; 58 | sz = st->length; 59 | free(st); 60 | if((d = malloc(sz+1)) == nil) 61 | goto end; 62 | 63 | for(r = 0; r < sz; r += n){ 64 | if((n = read(f, d+r, sz-r)) <= 0){ 65 | free(d); 66 | d = nil; 67 | goto end; 68 | } 69 | } 70 | 71 | end: 72 | if(d != nil) 73 | d[sz] = 0; 74 | close(f); 75 | return d; 76 | } 77 | 78 | void 79 | threadmain(int argc, char **argv) 80 | { 81 | Mousectl *mctl; 82 | Keyboardctl *kctl; 83 | uchar *data; 84 | char *s; 85 | Rune key; 86 | Mouse m; 87 | Alt a[] = { 88 | { nil, &m, CHANRCV }, 89 | { nil, nil, CHANRCV }, 90 | { nil, &key, CHANRCV }, 91 | { nil, nil, CHANEND }, 92 | }; 93 | int oldheight, height; 94 | Point oldxy, oldoffset; 95 | int oldb; 96 | Rectangle rect; 97 | 98 | quotefmtinstall(); 99 | 100 | if(argc < 2) 101 | sysfatal("usage"); 102 | if((data = loadall(argv[1])) == nil) 103 | sysfatal("%r"); 104 | s = "Hello there, 9fans. ljil|."; 105 | if(argc > 2 && (s = loadall(argv[2])) == nil) 106 | s = argv[2]; 107 | 108 | threadsetname("libpt"); 109 | if(initdraw(nil, nil, "libpt") < 0) 110 | sysfatal("initdraw: %r"); 111 | fg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DBlack); 112 | display->locking = 1; 113 | unlockdisplay(display); 114 | if((mctl = initmouse(nil, screen)) == nil) 115 | sysfatal("initmouse: %r"); 116 | if((kctl = initkeyboard(nil)) == nil) 117 | sysfatal("initkeyboard: %r"); 118 | 119 | a[0].c = mctl->c; 120 | a[1].c = mctl->resizec; 121 | a[2].c = kctl->c; 122 | memimageinit(); 123 | height = 32.0; 124 | oldheight = height; 125 | oldxy = ZP; 126 | 127 | goto changed; 128 | 129 | for(;;){ 130 | oldb = m.buttons; 131 | redraw(); 132 | 133 | switch(alt(a)){ 134 | case 0: /* mouse */ 135 | if(m.buttons != 0){ 136 | if(oldb == 0){ 137 | oldxy = m.xy; 138 | oldoffset = offset; 139 | oldheight = height; 140 | }else if(m.buttons == 4){ 141 | height = oldheight + (m.xy.y - oldxy.y)/20.0; 142 | if(height < 4) 143 | height = 4; 144 | goto changed; 145 | }else if(m.buttons == 2){ 146 | offset = addpt(oldoffset, subpt(m.xy, oldxy)); 147 | } 148 | } 149 | break; 150 | 151 | case 1: /* resize */ 152 | getwindow(display, Refnone); 153 | redraw(); 154 | break; 155 | 156 | case 2: /* keyboard */ 157 | switch(key){ 158 | case 'q': 159 | case Kdel: 160 | goto end; 161 | case '-': 162 | height -= height < 6 ? 0 : 1; 163 | goto changed; 164 | case '+': 165 | height += 1; 166 | goto changed; 167 | case 'i': 168 | invert = !invert; 169 | goto changed; 170 | case Kleft: 171 | opts.prefilter--; 172 | if(prefilter < 0) 173 | prefilter = 0; 174 | goto changed; 175 | case Kright: 176 | opts.prefilter++; 177 | goto changed; 178 | case Kup: 179 | opts.gamma += 0.01; 180 | goto changed; 181 | case Kdown: 182 | opts.gamma -= 0.01; 183 | if(opts.gamma < 0.4) 184 | opts.gamma = 0.4; 185 | goto changed; 186 | break; 187 | case '\n': 188 | opts.gamma = 1.0; 189 | goto changed; 190 | } 191 | } 192 | continue; 193 | 194 | changed: 195 | pt_freefont(fo); 196 | if((fo = pt_font(data, height)) == nil) 197 | sysfatal("%r"); 198 | rect = pt_textrect(fo, s); 199 | freeimage(image); 200 | image = pt_textdraw(fo, s, rect, &opts); 201 | } 202 | 203 | end: 204 | threadexitsall(nil); 205 | } 206 | -------------------------------------------------------------------------------- /mkfile: -------------------------------------------------------------------------------- 1 | ' 12 | - Code if the line is ``` (code block is ended with a similar line). 13 | - Image, in p9 format, if the line starts with '! ' 14 | Lines starting with a semicolon are comments and ignored by spit. 15 | 16 | spit doesn't try to do proper layout or resizing, it's on you. 17 | 18 | # Some examples 19 | Here is a simple hello world in plan9 C: 20 | ``` 21 | #include 22 | #include 23 | 24 | void 25 | main(void) 26 | { 27 | print("hello world\n"); 28 | } 29 | ``` 30 | 31 | As Rob once said: 32 | > It's a terrible idea. 33 | 34 | # The end 35 | And here was a simple tour of spit: 36 | ! glenda.bit 37 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telephil9/spit/bdef1272330cf36855a68abd15d437077402f48e/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telephil9/spit/bdef1272330cf36855a68abd15d437077402f48e/screenshot2.png -------------------------------------------------------------------------------- /spit.c: -------------------------------------------------------------------------------- 1 | #include "a.h" 2 | #include "style.h" 3 | 4 | Mousectl *mc; 5 | Keyboardctl *kc; 6 | Image *cols[Ncols]; 7 | char *ftitlename; 8 | Sfont *ftitle; 9 | char *ftextname; 10 | Sfont *ftext; 11 | char *ffixedname; 12 | Sfont *ffixed; 13 | Sdopts opts = {.prefilter = 0, .gamma = 1.0 }; 14 | int fullscreen; 15 | Rectangle screenr; 16 | Image *slides[Maxslides]; 17 | int nslides; 18 | int curslide; 19 | Image *bol; 20 | Image *bullet; 21 | int nodraw; 22 | 23 | void 24 | ladd(Lines *l, char *s) 25 | { 26 | if(l->nlines == Maxlines) 27 | sysfatal("too many lines"); 28 | l->lines[l->nlines++] = strdup(s); 29 | } 30 | 31 | void 32 | lclear(Lines *l) 33 | { 34 | int i; 35 | 36 | for(i = 0; i < l->nlines; i++) 37 | free(l->lines[i]); 38 | l->nlines = 0; 39 | } 40 | 41 | void 42 | error(char *f, int l, char *m) 43 | { 44 | fprint(2, "error: %s at %s:%d\n", m, f, l); 45 | threadexitsall("error"); 46 | } 47 | 48 | Image* 49 | addslide(void) 50 | { 51 | ++nslides; 52 | if(nslides >= Maxslides) 53 | sysfatal("too many slides"); 54 | slides[nslides] = allocimage(display, display->image->r, screen->chan, 0, coldefs[Cbg]); 55 | if(slides[nslides] == nil) 56 | sysfatal("allocimage: %r"); 57 | return slides[nslides]; 58 | } 59 | 60 | Point 61 | rendertitle(Image *b, Point p, char *s) 62 | { 63 | Rectangle r; 64 | Image *i; 65 | 66 | r = pt_textrect(ftitle, s); 67 | i = pt_textdraw(ftitle, s, r, &opts); 68 | draw(b, rectaddpt(r, p), cols[Cfg], i, ZP); 69 | freeimage(i); 70 | p.y += Dy(r); 71 | line(b, Pt(p.x, p.y), Pt(b->r.max.x - margin, p.y), 0, 0, 2, cols[Cfg], ZP); 72 | p.y += Dy(r); 73 | return p; 74 | } 75 | 76 | Point 77 | rendertext(Image *b, Point p, char *s) 78 | { 79 | Rectangle r; 80 | Image *i; 81 | 82 | r = pt_textrect(ftext, s); 83 | if(strlen(s) > 0){ 84 | i = pt_textdraw(ftext, s, r, &opts); 85 | draw(b, Rect(p.x, p.y, p.x+Dx(bol->r), p.y+Dy(bol->r)), bol, 0, ZP); 86 | draw(b, rectaddpt(r, Pt(p.x + Dx(bol->r) + padding, p.y)), cols[Cfg], i, ZP); 87 | freeimage(i); 88 | } 89 | p.y += Dy(r)*lineheight; 90 | return p; 91 | } 92 | 93 | Point 94 | renderlist(Image *b, Point p, Lines *lines) 95 | { 96 | Point q; 97 | Rectangle r; 98 | Image *t; 99 | int i; 100 | 101 | p.x += Dx(bol->r); 102 | for(i = 0; i < lines->nlines; i++){ 103 | draw(b, rectaddpt(bullet->r, p), bullet, nil, ZP); 104 | q = addpt(p, Pt(Dx(bullet->r) + padding, 0)); 105 | r = pt_textrect(ftext, lines->lines[i]); 106 | t = pt_textdraw(ftext, lines->lines[i], r, &opts); 107 | draw(b, rectaddpt(r, q), cols[Cfg], t, ZP); 108 | freeimage(t); 109 | p.y += Dy(r); 110 | } 111 | p.x -= Dx(bol->r); 112 | return p; 113 | } 114 | 115 | Point 116 | renderquote(Image *b, Point p, Lines *lines) 117 | { 118 | Rectangle r[Maxlines], br; 119 | Image *t; 120 | int i, maxw, maxh; 121 | 122 | maxw = 0; 123 | maxh = 0; 124 | for(i = 0; i < lines->nlines; i++){ 125 | r[i] = pt_textrect(ftext, lines->lines[i]); 126 | maxh += Dy(r[i]); 127 | if(Dx(r[i]) > maxw) 128 | maxw = Dx(r[i]); 129 | } 130 | p.x += Dx(bol->r) + margin; 131 | br = Rect(p.x, p.y, p.x + 1.5*maxw + 2*padding, p.y + maxh + 2*padding); 132 | draw(b, br, cols[Cqbg], nil, ZP); 133 | line(b, br.min, Pt(br.min.x, br.max.y), 0, 0, padding/2, cols[Cqbord], ZP); 134 | p.x += padding; 135 | p.y += padding; 136 | for(i = 0; i < lines->nlines; i++){ 137 | t = pt_textdraw(ftext, lines->lines[i], r[i], &opts); 138 | draw(b, rectaddpt(r[i], Pt(p.x+padding, p.y)), cols[Cfg], t, ZP); 139 | freeimage(t); 140 | p.y += Dy(r[i]); 141 | } 142 | p.x -= padding; 143 | p.x -= Dx(bol->r) + margin; 144 | return p; 145 | } 146 | 147 | 148 | Point 149 | rendercode(Image *b, Point p, Lines *lines) 150 | { 151 | Rectangle r[Maxlines], br; 152 | Image *t; 153 | int i, maxw, maxh; 154 | 155 | maxw = 0; 156 | maxh = 0; 157 | for(i = 0; i < lines->nlines; i++){ 158 | r[i] = pt_textrect(ffixed, lines->lines[i]); 159 | maxh += Dy(r[i]); 160 | if(Dx(r[i]) > maxw) 161 | maxw = Dx(r[i]); 162 | } 163 | p.x += Dx(bol->r) + margin; 164 | br = Rect(p.x, p.y, p.x + 1.5*maxw + 2*padding, p.y + maxh + 2*padding); 165 | draw(b, br, cols[Ccbg], nil, ZP); 166 | border(b, br, 2, cols[Ccbord], ZP); 167 | p.y += padding; 168 | for(i = 0; i < lines->nlines; i++){ 169 | t = pt_textdraw(ffixed, lines->lines[i], r[i], &opts); 170 | draw(b, rectaddpt(r[i], Pt(p.x+padding, p.y)), cols[Cfg], t, ZP); 171 | freeimage(t); 172 | p.y += Dy(r[i]); 173 | } 174 | p.x -= Dx(bol->r) + margin; 175 | return p; 176 | } 177 | 178 | Point 179 | renderimage(Image *b, Point p, char *f) 180 | { 181 | Image *i; 182 | int fd; 183 | 184 | fd = open(f, OREAD); 185 | if(fd <= 0) 186 | sysfatal("open: %r"); 187 | i = readimage(display, fd, 0); 188 | draw(b, rectaddpt(i->r, p), i, nil, ZP); 189 | p.y += Dy(i->r) + margin; 190 | freeimage(i); 191 | close(fd); 192 | return p; 193 | } 194 | 195 | char* 196 | skipws(char *s) 197 | { 198 | while(*s == ' ' || *s == '\t') 199 | ++s; 200 | return s; 201 | } 202 | 203 | ulong 204 | estrtoul(char *f, int line, char *s) 205 | { 206 | char *e; 207 | ulong c; 208 | 209 | c = strtoul(s, &e, 16); 210 | if(e == s || e == nil) 211 | error(f, line, "invalid number"); 212 | return (c << 8) | 0xff; 213 | } 214 | 215 | void 216 | parsestyle(char *f, int line, char *s) 217 | { 218 | char k[32] = {0}, *p; 219 | 220 | s += 6; /* skip '@style' */ 221 | if(s[0] != '[') 222 | error(f, line, "expected '[' character in style declaration"); 223 | p = strchr(s, ']'); 224 | if(p == nil) 225 | error(f, line, "expected ']' character in style declaration"); 226 | if(p == s+1) 227 | error(f, line, "empty style declaration"); 228 | if(p-s >= 32) 229 | error(f, line, "invalid key in style declaration"); 230 | strncpy(k, s+1, p-s-1); 231 | s = skipws(p+1); 232 | if(*s != '=') 233 | error(f, line, "expected '=' character in style declaration"); 234 | s = skipws(s+1); 235 | if(*s == 0) 236 | error(f, line, "empty style value"); 237 | if(strcmp(k, "margin") == 0){ 238 | margin = atoi(s); 239 | if(margin == 0) error(f, line, "invalid 'margin' value"); 240 | }else if(strcmp(k, "padding") == 0){ 241 | padding = atoi(s); 242 | if(padding == 0) error(f, line, "invalid 'padding' value"); 243 | }else if(strcmp(k, "lineheight") == 0){ 244 | lineheight = atof(s); 245 | if(lineheight < 1.0) error(f, line, "invalid 'lineheight' value"); 246 | }else if(strcmp(k, "color.bg") == 0) 247 | coldefs[Cbg] = estrtoul(f, line, s); 248 | else if(strcmp(k, "color.fg") == 0) 249 | coldefs[Cfg] = estrtoul(f, line, s); 250 | else if(strcmp(k, "color.quotebg") == 0) 251 | coldefs[Cqbg] = estrtoul(f, line, s); 252 | else if(strcmp(k, "color.quoteborder") == 0) 253 | coldefs[Cqbord] = estrtoul(f, line, s); 254 | else if(strcmp(k, "color.codebg") == 0) 255 | coldefs[Ccbg] = estrtoul(f, line, s); 256 | else if(strcmp(k, "color.codeborder") == 0) 257 | coldefs[Ccbord] = estrtoul(f, line, s); 258 | else if(strcmp(k, "title.font") == 0) 259 | ftitlename = strdup(s); 260 | else if(strcmp(k, "title.size") == 0) 261 | ftitlesz = atof(s); 262 | else if(strcmp(k, "text.font") == 0) 263 | ftextname = strdup(s); 264 | else if(strcmp(k, "text.size") == 0) 265 | ftextsz = atof(s); 266 | else if(strcmp(k, "fixed.font") == 0) 267 | ffixedname = strdup(s); 268 | else if(strcmp(k, "fixed.size") == 0) 269 | ffixedsz = atof(s); 270 | else 271 | error(f, line, "unknown style key"); 272 | } 273 | 274 | void 275 | initimages(void) 276 | { 277 | Point p[4]; 278 | 279 | bol = allocimage(display, Rect(0, 0, ftextsz, ftextsz), screen->chan, 0, coldefs[Cbg]); 280 | p[0] = Pt(0.25*ftextsz, 0.25*ftextsz); 281 | p[1] = Pt(0.25*ftextsz, 0.75*Dy(bol->r)); 282 | p[2] = Pt(0.75*ftextsz, 0.50*Dy(bol->r)); 283 | p[3] = p[0]; 284 | fillpoly(bol, p, 4, 0, cols[Cfg], ZP); 285 | bullet = allocimage(display, Rect(0, 0, ftextsz, ftextsz), screen->chan, 0, coldefs[Cbg]); 286 | fillellipse(bullet, Pt(0.5*ftextsz, 0.5*ftextsz), 0.15*ftextsz, 0.15*ftextsz, cols[Cfg], ZP); 287 | } 288 | 289 | void 290 | loadstyle(char *f) 291 | { 292 | int i; 293 | 294 | if(ftitlename == nil){ 295 | fprint(2, "%s: no title font defined", f); 296 | threadexitsall("missing font"); 297 | } 298 | for(i = 0; i < Ncols; i++) 299 | cols[i] = ealloccol(coldefs[i]); 300 | ftitle = loadsfont(ftitlename, ftitlesz); 301 | ftext = loadsfont(ftextname ? ftextname : ftitlename, ftextsz); 302 | ffixed = loadsfont(ffixedname ? ffixedname : ftitlename, ffixedsz); 303 | initimages(); 304 | } 305 | 306 | void 307 | render(char *f) 308 | { 309 | enum { Sstart, Scomment, Scontent, Slist, Squote, Scode }; 310 | Biobuf *bp; 311 | char *l; 312 | int s, ln; 313 | Image *b; 314 | Point p; 315 | Lines lines = {0}; 316 | 317 | s = Sstart; 318 | b = nil; 319 | ln = 0; 320 | nslides = -1; 321 | curslide = 0; 322 | if((bp = Bopen(f, OREAD)) == nil) 323 | sysfatal("Bopen: %r"); 324 | for(;;){ 325 | l = Brdstr(bp, '\n', 1); 326 | ++ln; 327 | if(l == nil) 328 | break; 329 | Again: 330 | switch(s){ 331 | case Sstart: 332 | if(l[0] == ';' || l[0] == 0){ 333 | free(l); 334 | continue; 335 | } 336 | if(strncmp(l, "@style", 6) == 0){ 337 | parsestyle(f, ln, l); 338 | free(l); 339 | continue; 340 | } 341 | if(l[0] != '#') error(f, ln, "expected title line"); 342 | if(nslides == -1) /* all style parsed but not slide rendered yet */ 343 | loadstyle(f); 344 | Title: 345 | p = Pt(margin, margin); 346 | b = addslide(); 347 | p = rendertitle(b, p, l+2); 348 | s = Scontent; 349 | break; 350 | case Scomment: 351 | s = Scontent; 352 | break; 353 | case Scontent: 354 | if(l[0] == '#') 355 | goto Title; 356 | else if(l[0] == '-'){ 357 | s = Slist; 358 | goto Again; 359 | }else if(l[0] == '>'){ 360 | s = Squote; 361 | goto Again; 362 | }else if(strncmp(l, "```", 3) == 0){ 363 | s = Scode; 364 | break; 365 | }else if(l[0] == '!') 366 | p = renderimage(b, p, l+2); 367 | else if(l[0] == ';'){ 368 | s = Scomment; 369 | break; 370 | }else 371 | p = rendertext(b, p, l); 372 | break; 373 | case Slist: 374 | if(l[0] != '-'){ 375 | p = renderlist(b, p, &lines); 376 | lclear(&lines); 377 | s = Scontent; 378 | goto Again; 379 | } 380 | ladd(&lines, l+2); 381 | break; 382 | case Squote: 383 | if(l[0] != '>'){ 384 | p = renderquote(b, p, &lines); 385 | lclear(&lines); 386 | s = Scontent; 387 | goto Again; 388 | } 389 | ladd(&lines, l+2); 390 | break; 391 | case Scode: 392 | if(strncmp(l, "```", 3) == 0){ 393 | p = rendercode(b, p, &lines); 394 | lclear(&lines); 395 | s = Scontent; 396 | break; 397 | } 398 | ladd(&lines, l); 399 | break; 400 | } 401 | free(l); 402 | } 403 | if(nslides == -1) 404 | error(f, ln, "no slides parsed"); 405 | } 406 | 407 | void 408 | barf(void) 409 | { 410 | int fd; 411 | char path[64]; 412 | Image **i; 413 | 414 | for(i=slides; ir, slides[curslide], nil, ZP); 427 | flushimage(display, 1); 428 | } 429 | 430 | void 431 | wresize(int x, int y, int w, int h) 432 | { 433 | int fd, n; 434 | char buf[255]; 435 | 436 | fd = open("/dev/wctl", OWRITE|OCEXEC); 437 | if(fd < 0) 438 | sysfatal("open: %r"); 439 | n = snprint(buf, sizeof buf, "resize -r %d %d %d %d", x, y, w, h); 440 | if(write(fd, buf, n) != n) 441 | fprint(2, "write error: %r\n"); 442 | close(fd); 443 | } 444 | 445 | void 446 | resize(void) 447 | { 448 | if(fullscreen) 449 | wresize(0, 0, 9999, 9999); 450 | redraw(); 451 | } 452 | 453 | void 454 | togglefullscreen(void) 455 | { 456 | int x, y, w, h; 457 | 458 | if(fullscreen){ 459 | fullscreen = 0; 460 | x = screenr.min.x; 461 | y = screenr.min.y; 462 | w = screenr.max.x; 463 | h = screenr.max.y; 464 | }else{ 465 | fullscreen = 1; 466 | x = 0; 467 | y = 0; 468 | w = 9999; 469 | h = 9999; 470 | } 471 | wresize(x, y, w, h); 472 | redraw(); 473 | } 474 | 475 | void 476 | usage(void) 477 | { 478 | fprint(2, "%s [-n] \n", argv0); 479 | exits("usage"); 480 | } 481 | 482 | void 483 | threadmain(int argc, char **argv) 484 | { 485 | enum { Emouse, Eresize, Ekeyboard }; 486 | char *f; 487 | Mouse m; 488 | Rune k; 489 | Alt alts[] = { 490 | { nil, &m, CHANRCV }, 491 | { nil, nil, CHANRCV }, 492 | { nil, &k, CHANRCV }, 493 | { nil, nil, CHANEND }, 494 | }; 495 | 496 | ftitlename = nil; 497 | ftextname = nil; 498 | ffixedname = nil; 499 | ARGBEGIN{ 500 | case 'n': 501 | nodraw = 1; 502 | break; 503 | default: 504 | usage(); 505 | }ARGEND; 506 | if((f = *argv) == nil){ 507 | fprint(2, "missing filename\n"); 508 | usage(); 509 | } 510 | setfcr(getfcr() & ~(FPZDIV | FPOVFL | FPINVAL)); 511 | if(initdraw(nil, nil, argv0) < 0) 512 | sysfatal("initdraw: %r"); 513 | if((mc = initmouse(nil, screen)) == nil) 514 | sysfatal("initmouse: %r"); 515 | if((kc = initkeyboard(nil)) == nil) 516 | sysfatal("initkeyboard: %r"); 517 | display->locking = 0; 518 | alts[Emouse].c = mc->c; 519 | alts[Eresize].c = mc->resizec; 520 | alts[Ekeyboard].c = kc->c; 521 | memimageinit(); 522 | fullscreen = 0; 523 | screenr = screen->r; 524 | render(f); 525 | if(nodraw){ 526 | barf(); 527 | threadexitsall(nil); 528 | } 529 | resize(); 530 | for(;;){ 531 | switch(alt(alts)){ 532 | case Emouse: 533 | break; 534 | case Eresize: 535 | if(getwindow(display, Refnone) < 0) 536 | sysfatal("getwindow: %r"); 537 | resize(); 538 | break; 539 | case Ekeyboard: 540 | switch(k){ 541 | case Kdel: 542 | case 'q': 543 | if(fullscreen) 544 | togglefullscreen(); 545 | threadexitsall(nil); 546 | break; 547 | case 'f': 548 | togglefullscreen(); 549 | break; 550 | case Kbs: 551 | case Kleft: 552 | if(curslide > 0){ 553 | curslide--; 554 | redraw(); 555 | } 556 | break; 557 | case ' ': 558 | case Kright: 559 | if(curslide < nslides){ 560 | curslide++; 561 | redraw(); 562 | } 563 | break; 564 | case Khome: 565 | curslide = 0; 566 | redraw(); 567 | break; 568 | case Kend: 569 | curslide = nslides; 570 | redraw(); 571 | break; 572 | } 573 | break; 574 | } 575 | } 576 | } 577 | -------------------------------------------------------------------------------- /spit.man: -------------------------------------------------------------------------------- 1 | .TH SPIT 1 2 | .SH NAME 3 | spit \- simple presentation tool 4 | .SH SYNOPSIS 5 | .B spit 6 | [ 7 | .I -n 8 | ] 9 | .I file 10 | .SH DESCRIPTION 11 | .I Spit 12 | reads the presentation described in 13 | .I file 14 | and renders it in a graphical window. The format of the presentation is described below in 15 | .IR "Presentation format" 16 | . 17 | With the presentation displayed, navigation between slides is achieved using the 18 | .B left and 19 | .B right arrows (or 20 | .B backspace and 21 | .B space 22 | respectively), it is also possible to go back to the first slide of the presentation using the 23 | .B home 24 | key and to the last one using the 25 | .B end 26 | key. Pressing the 27 | .B f 28 | key will toggle fullscreen mode making the window fill the entirety of the screen, or restore the window to its original size. 29 | .I Spit 30 | is exited with either 31 | .B q 32 | or the 33 | .B delete 34 | keys. 35 | .PP 36 | If the 37 | .I -n 38 | flag is passed, the presentation will not be displayed but instead each slide will be rendered to an image named 39 | .I spit.NNN.bit 40 | (with NNN being the slide number) in the current directory. 41 | .SS Presentation format 42 | Presentations are plaintext files written in a markdown-like format. A new slide is started with a line starting with a 43 | .I pound 44 | character (#) followed by a space then the title of the slide. Within a slide, 45 | .I spit 46 | recognizes the following directives 47 | .TP 48 | .B Lists 49 | A list starts when 50 | .I spit 51 | encounters a line starting with a 52 | .I dash 53 | followed by a space and until a line does not. 54 | .TP 55 | .B Quotes 56 | A quote starts when 57 | .I spit 58 | encounters a line starting with a 59 | .I greater than 60 | sign followed by a space and can span multiple lines. 61 | .TP 62 | .B Code 63 | A code listing can be inserted by putting three backticks on a single line. All following lines will then be rendered as code and this until a line with three backticks is encountered again. 64 | .TP 65 | .B Images 66 | An image, in p9 format, can be inserted by putting an 67 | .I exclamation mark 68 | followed by a space and the path to the file. 69 | .PP 70 | Lines starting with a semi-colon are comments and ignored by 71 | .I spit . 72 | All other lines are rendered as bullet points in the slide. 73 | .PP 74 | The presentation style can be configured with a style directive (see 75 | .IR "Style configuration" 76 | ). These directives must come first in the presentation (comments notwithstanding). 77 | .SS Style configuration 78 | .I Spit 79 | has a predefined style for all presentation elements but this can be changed using style directives. A style directive is defined using the 80 | .B @style 81 | keyword with the following syntax: 82 | .EX 83 | @style[element] = value 84 | .EE 85 | .PP 86 | The possible style elements are 87 | .TP 88 | .B title.font 89 | Path of the TTF font for the title of the slides. This element is 90 | .B mandatory 91 | for the presentation to render. 92 | .TP 93 | .B title.size 94 | Size, in points, of the title text (default: 120) 95 | .TP 96 | .B text.font 97 | Path of the TTF font for the text. If not specified, this will default to using 98 | .I title.font 99 | .TP 100 | .B text.size 101 | Size, in points, of the slides content text (default: 96) 102 | .TP 103 | .B fixed.font 104 | Path of the TTF font to used for code blocks. If not specified, this will default to using 105 | .I title.font 106 | .TP 107 | .B fixed.size 108 | Size, in points, of code blocks text (default: 72) 109 | .TP 110 | .B margin 111 | Size, in pixels, of the margin surrounding slides (default: 32) 112 | .TP 113 | .B padding 114 | Size, in pixels, of quote and code blocks internal padding (default: 12) 115 | .TP 116 | .B lineheight 117 | Multiplier for the height of text lines. By default, lines have no spacing with each line being the height of the font. This parameter will make lines 118 | .I lineheight 119 | times higher. 120 | .TP 121 | .B color.bg 122 | Background color of the slides (default: FFFFFF) 123 | .TP 124 | .B color.fg 125 | Foreground color of the text (default: 000000) 126 | .TP 127 | .B color.quotebg 128 | Background color of quote blocks (default: FAFAFA) 129 | .TP 130 | .B color.quoteborder 131 | Color of the border around quote blocks (default: CCCCCC) 132 | .TP 133 | .B color.codebg 134 | Background color of code blocks (default: FFFFEA) 135 | .TP 136 | .B color.codeborder 137 | Color of the border around code blocks (default: 99994C) 138 | .SH EXAMPLE 139 | A simple presentation with two slides: 140 | .PP 141 | .EX 142 | ; a comment that will not be rendered 143 | @style[title.font] = /lib/font/ttf/myfont.ttf 144 | # Introduction 145 | Spit is a presentation tool 146 | Spit has very basic functionalities 147 | > Mandatory Rob quote 148 | # Second slide 149 | Here is a list of items: 150 | - Item 1 151 | - Item 2 152 | - Item 3 153 | And an image to end it all: 154 | ! nein.bit 155 | .EE 156 | 157 | 158 | -------------------------------------------------------------------------------- /style.h: -------------------------------------------------------------------------------- 1 | /* slide margin */ 2 | int margin = 32; 3 | 4 | /* box (quote or code) internal padding */ 5 | int padding = 12; 6 | 7 | /* line height multiplier */ 8 | float lineheight = 1.0; 9 | 10 | /* slide title font size */ 11 | float ftitlesz = 120.0; 12 | 13 | /* text font size */ 14 | float ftextsz = 96.0; 15 | 16 | /* fixed font size */ 17 | float ffixedsz = 72.0; 18 | 19 | /* color definitions */ 20 | ulong coldefs[Ncols] = { 21 | [Cbg] = 0xFFFFFFFF, /* slide background */ 22 | [Cfg] = 0x000000FF, /* text color */ 23 | [Cqbg] = 0xFAFAFAFF, /* quotes background */ 24 | [Cqbord] = 0xCCCCCCFF, /* quotes border */ 25 | [Ccbg] = 0xFFFFEAFF, /* code background */ 26 | [Ccbord] = 0x99994CFF, /* code border */ 27 | }; 28 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include "a.h" 2 | 3 | uchar* 4 | slurp(char *path) 5 | { 6 | int fd; 7 | long r, n, s; 8 | uchar *buf; 9 | 10 | n = 0; 11 | s = 8192; 12 | buf = malloc(s); 13 | if(buf == nil) 14 | return nil; 15 | fd = open(path, OREAD); 16 | if(fd < 0) 17 | return nil; 18 | for(;;){ 19 | r = read(fd, buf + n, s - n); 20 | if(r < 0) 21 | return nil; 22 | if(r == 0) 23 | break; 24 | n += r; 25 | if(n == s){ 26 | s *= 1.5; 27 | buf = realloc(buf, s); 28 | if(buf == nil) 29 | return nil; 30 | } 31 | } 32 | buf[n] = 0; 33 | close(fd); 34 | return buf; 35 | } 36 | 37 | Sfont* 38 | loadsfont(char *filename, float height) 39 | { 40 | Sfont *sfont; 41 | uchar *data; 42 | 43 | data = slurp(filename); 44 | if(data == nil) 45 | sysfatal("loadsfont: %r"); 46 | sfont = pt_font(data, height); 47 | if(sfont == nil) 48 | sysfatal("pt_font: %r"); 49 | return sfont; 50 | } 51 | 52 | void* 53 | emalloc(ulong n) 54 | { 55 | void *p; 56 | 57 | p = malloc(n); 58 | if(p == nil) 59 | sysfatal("malloc: %r"); 60 | return p; 61 | } 62 | 63 | Image* 64 | ealloccol(ulong col) 65 | { 66 | Image *i; 67 | 68 | i = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, col); 69 | if(i == nil) 70 | sysfatal("allocimage: %r"); 71 | return i; 72 | } 73 | 74 | --------------------------------------------------------------------------------