ref: 8dff7b6ee540e18d4cc50e69c1775b2eaf28ca01
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Tue Apr 14 09:08:54 EDT 2020
first version
--- /dev/null
+++ b/README.md
@@ -1,0 +1,15 @@
+# fontsel
+
+Simple font selector for Plan 9.
+
+It scans `/lib/font/bit` and show the user a text. User select font
+directory using middle mouse button and a specific font from that
+directory with the right mouse button.
+
+When the user decides to quit, using either `q` or `del`, program
+prints out the full path to the last font selected.
+
+## TODO
+
+ * Custom font paths
+ * truetypefs support
--- /dev/null
+++ b/fontsel.c
@@ -1,0 +1,272 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+
+typedef struct Fontdir Fontdir;
+
+struct Fontdir {
+ char *name;
+ char **fonts;
+ int nfonts;
+ int ifont;
+};
+
+enum
+{
+ Ckey,
+ Cmouse,
+ Cresize,
+ Numchan,
+};
+
+static char *text[] = {
+ "Cwm fjord bank glyphs vext quiz!",
+ "Gud hjälpe Zorns mö qvickt få byxa?",
+ "Разъярённый чтец эгоистично бьёт пятью",
+ "жердями шустрого фехтовальщика.",
+ "",
+ "static int",
+ "dosomestuff(Stuff *s, char *t, int n)",
+ "{",
+ " if(s == nil && (t == nil || n < 1))",
+ " return 0;",
+ " fprint(2, \"# %c\\n\", t[0]);",
+ " return dostuff(s) || dot(t, n);",
+ "}",
+};
+
+static char *prefix = "/lib/font/bit/";
+static Font *f;
+static Fontdir *dirs, *cdir;
+static int ndirs, idir;
+static char lasterr[256];
+
+static void
+redraw(void)
+{
+ Point p;
+ int i, w, maxw;
+ char t[256];
+
+ lockdisplay(display);
+ draw(screen, screen->r, display->white, nil, ZP);
+ p = screen->r.min;
+
+ if(f == nil){
+ p.x += Dx(screen->r)/2 - stringwidth(font, lasterr)/2;
+ p.y += Dy(screen->r)/2 - font->height/2;
+ string(screen, p, display->black, ZP, font, lasterr);
+ }else{
+ maxw = 0;
+ for(i = 0; i < nelem(text); i++){
+ if((w = stringwidth(f, text[i])) > maxw)
+ maxw = w;
+ }
+ p.x += Dx(screen->r)/2 - maxw/2;
+ p.y += Dy(screen->r)/2 - nelem(text)*f->height/2;
+
+ for(i = 0; i < nelem(text); i++){
+ string(screen, p, display->black, ZP, f, text[i]);
+ p.y += f->height;
+ }
+
+ }
+
+ snprint(t, sizeof(t), "%s/%s", cdir->name, cdir->fonts[cdir->ifont]);
+ p = screen->r.max;
+ p.x -= Dx(screen->r)/2 + stringwidth(font, t)/2;
+ p.y -= font->height;
+ string(screen, p, display->black, ZP, font, t);
+
+ flushimage(display, 1);
+ unlockdisplay(display);
+}
+
+static int
+fcmp(void *a, void *b)
+{
+ return strcmp(*(char**)a, *(char**)b);
+}
+
+static int
+fontdir(char *t, int f, Fontdir *fdir)
+{
+ Dir *d;
+ int doff, k;
+ long i, n;
+
+ if((n = dirreadall(f, &d)) < 1)
+ return -1;
+ doff = strlen(t);
+ t[doff++] = '/';
+ for(i = 0; i < n; i++){
+ if((k = strlen(d[i].name)) < 5 || strcmp(&d[i].name[k-5], ".font") != 0)
+ continue;
+ if((fdir->fonts = realloc(fdir->fonts, sizeof(*fdir->fonts)*(fdir->nfonts+1))) == nil)
+ sysfatal("no memory");
+ d[i].name[k-5] = 0;
+ strcpy(t+doff, d[i].name);
+ fdir->fonts[fdir->nfonts++] = strdup(t+doff);
+ }
+ free(d);
+ if(fdir->nfonts > 0)
+ qsort(fdir->fonts, fdir->nfonts, sizeof(*fdir->fonts), fcmp);
+
+ return 0;
+}
+
+static int
+dcmp(void *a_, void *b_)
+{
+ Fontdir *a, *b;
+
+ a = (Fontdir*)a_;
+ b = (Fontdir*)b_;
+ return strcmp(a->name, b->name);
+}
+
+static void
+findfonts(void)
+{
+ Dir *d, *din;
+ int f, fin, doff;
+ long i, n;
+ char t[1024];
+
+ doff = sprint(t, prefix);
+ if((f = open(t, OREAD)) < 0)
+ sysfatal("font dir: %r");
+ if((n = dirreadall(f, &d)) < 1)
+ sysfatal("no fonts");
+ for(i = 0; i < n; i++){
+ sprint(t+doff, d[i].name);
+ if((fin = open(t, OREAD)) < 0)
+ continue;
+ if((din = dirfstat(fin)) != nil){
+ if((dirs = realloc(dirs, sizeof(Fontdir)*(ndirs+1))) == nil)
+ sysfatal("no memory");
+ memset(&dirs[ndirs], 0, sizeof(Fontdir));
+ if((din->mode&DMDIR) && fontdir(t, fin, &dirs[ndirs]) == 0 && dirs[ndirs].nfonts > 0)
+ dirs[ndirs++].name = strdup(d[i].name);
+ free(din);
+ }
+ }
+ free(d);
+
+ qsort(dirs, ndirs, sizeof(*dirs), dcmp);
+}
+
+static void
+newfont(void)
+{
+ char t[512];
+
+ lockdisplay(display);
+ if(f != nil)
+ freefont(f);
+ snprint(t, sizeof(t), "%s/%s/%s.font", prefix, cdir->name, cdir->fonts[cdir->ifont]);
+ if((f = openfont(display, t)) == nil)
+ snprint(lasterr, sizeof(lasterr), "%r");
+ unlockdisplay(display);
+}
+
+static char *
+dirgen(int i)
+{
+ return i < ndirs ? dirs[i].name : nil;
+}
+
+static char *
+fontgen(int i)
+{
+ return i < cdir->nfonts ? cdir->fonts[i] : nil;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Mousectl *mctl;
+ Keyboardctl *kctl;
+ Rune r;
+ Mouse m;
+ Menu menu;
+ Alt a[Numchan+1] = {
+ [Ckey] = {nil, &r, CHANRCV},
+ [Cmouse] = {nil, &m, CHANRCV },
+ [Cresize] = {nil, nil, CHANRCV},
+ {nil, nil, CHANEND},
+ };
+ int n;
+
+ USED(argc); USED(argv);
+
+ findfonts();
+ if(ndirs < 1)
+ sysfatal("no fonts");
+
+ if(initdraw(nil, nil, "picker") < 0)
+ sysfatal("initdraw: %r");
+ if((kctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+ a[Ckey].c = kctl->c;
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ a[Cmouse].c = mctl->c;
+ a[Cresize].c = mctl->resizec;
+ display->locking = 1;
+ unlockdisplay(display);
+
+ memset(&menu, 0, sizeof(menu));
+ cdir = &dirs[0];
+ newfont();
+ redraw();
+
+ for(;;){
+ switch (alt(a)) {
+ case -1:
+ goto end;
+
+ case Ckey:
+ switch (r) {
+ case Kdel:
+ case 'q':
+ goto end;
+ }
+ break;
+
+ case Cmouse:
+ if(m.buttons == 2){
+ menu.gen = dirgen;
+ menu.lasthit = idir;
+ if((n = menuhit(2, mctl, &menu, nil)) >= 0){
+ idir = n;
+ cdir = &dirs[idir];
+ newfont();
+ redraw();
+ }
+ }else if(m.buttons == 4){
+ menu.gen = fontgen;
+ menu.lasthit = cdir->ifont;
+ if((n = menuhit(3, mctl, &menu, nil)) >= 0){
+ cdir->ifont = n;
+ newfont();
+ redraw();
+ }
+ }
+ break;
+
+ case Cresize:
+ getwindow(display, Refnone);
+ redraw();
+ break;
+ }
+ }
+
+end:
+ if(f != nil)
+ print("%s/%s/%s.font\n", prefix, cdir->name, cdir->fonts[cdir->ifont]);
+ threadexitsall(nil);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,11 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=fontsel
+
+OFILES=\
+ fontsel.$O\
+
+HFILES=\
+
+</sys/src/cmd/mkone