libdraw: autoscale fonts when moving between low and high dpi screens

Change-Id: I6093955b222db89dfe437fb723593b173d888d01
Reviewed-on: https://plan9port-review.googlesource.com/1170
Reviewed-by: Russ Cox <rsc@swtch.com>
diff --git a/include/draw.h b/include/draw.h
index ff760dd..329108d 100644
--- a/include/draw.h
+++ b/include/draw.h
@@ -206,6 +206,9 @@
 	struct Mux	*mux;
 	int		srvfd;
 	int		dpi;
+	
+	Font	*firstfont;
+	Font	*lastfont;
 };
 
 struct Image
@@ -319,6 +322,15 @@
 	Cachesubf	*subf;
 	Cachefont	**sub;	/* as read from file */
 	Image		*cacheimage;
+	
+	/* doubly linked list of fonts known to display */
+	int ondisplaylist;
+	Font *next;
+	Font *prev;
+	
+	/* on hi-dpi systems, one of these is set to f and the other is the other-dpi version of f */
+	Font	*lodpi;
+	Font	*hidpi;
 };
 
 #define	Dx(r)	((r).max.x-(r).min.x)
@@ -460,6 +472,7 @@
  * Font management
  */
 extern Font*	openfont(Display*, char*);
+extern int	parsefontscale(char*, char**);
 extern Font*	buildfont(Display*, char*, char*);
 extern void	freefont(Font*);
 extern Font*	mkfont(Subfont*, Rune);
@@ -483,11 +496,13 @@
 extern Point	strsubfontwidth(Subfont*, char*);
 extern int	loadchar(Font*, Rune, Cacheinfo*, int, int, char**);
 extern char*	subfontname(char*, char*, int);
-extern Subfont*	_getsubfont(Display*, Font*, char*);
+extern Subfont*	_getsubfont(Display*, char*);
 extern Subfont*	getdefont(Display*);
 extern void		lockdisplay(Display*);
 extern void	unlockdisplay(Display*);
 extern int		drawlsetrefresh(u32int, int, void*, void*);
+extern void	loadhidpi(Font*);
+extern void	swapfont(Font*, Font**, Font**);
 
 /*
  * Predefined 
diff --git a/man/man3/graphics.3 b/man/man3/graphics.3
index d07c269..43214f1 100644
--- a/man/man3/graphics.3
+++ b/man/man3/graphics.3
@@ -487,6 +487,21 @@
 sophisticated clients may use
 .B _screen
 to make further subwindows.
+If
+.I getwindow
+is being called due to a resizing of the window,
+the resize may be accompanied by a change in screen pixel density (DPI),
+in which case the value of the
+.BR Display 's
+.B dpi
+field and any open
+.BR Font 's
+.B height
+and
+.B ascent
+fields may be updated during the call to
+.IR getwindow .
+Programs should discard any cached information about display or font sizes.
 .\" Programs desiring multiple independent windows
 .\" may use the mechanisms of
 .\" .IR rio (4)
diff --git a/src/libdraw/buildfont.c b/src/libdraw/buildfont.c
index 0f14022..512bc9d 100644
--- a/src/libdraw/buildfont.c
+++ b/src/libdraw/buildfont.c
@@ -138,5 +138,23 @@
 	free(f->cache);
 	free(f->subf);
 	free(f->sub);
+
+	if(f->ondisplaylist) {
+		f->ondisplaylist = 0;
+		if(f->next)
+			f->next->prev = f->prev;
+		else
+			f->display->lastfont = f->prev;
+		if(f->prev)
+			f->prev->next = f->next;
+		else
+			f->display->firstfont = f->next;
+	}
+
+	if(f->lodpi != f)	
+		freefont(f->lodpi);
+	if(f->hidpi != f)
+		freefont(f->hidpi);
+
 	free(f);
 }
diff --git a/src/libdraw/getsubfont.c b/src/libdraw/getsubfont.c
index 3f3b695..1a5006b 100644
--- a/src/libdraw/getsubfont.c
+++ b/src/libdraw/getsubfont.c
@@ -11,17 +11,20 @@
 static void scalesubfont(Subfont*, int);
 
 Subfont*
-_getsubfont(Display *d, Font *ff, char *name)
+_getsubfont(Display *d, char *name)
 {
 	int fd;
 	Subfont *f;
-
-	fd = open(name, OREAD);
-	if(fd < 0 && strncmp(name, "/mnt/font/", 10) == 0)
-		fd = _fontpipe(name+10);
+	int scale;
+	char *fname;
+	
+	scale = parsefontscale(name, &fname);
+	fd = open(fname, OREAD);
+	if(fd < 0 && strncmp(fname, "/mnt/font/", 10) == 0)
+		fd = _fontpipe(fname+10);
 
 	if(fd < 0){
-		fprint(2, "getsubfont: can't open %s: %r\n", name);
+		fprint(2, "getsubfont: can't open %s: %r\n", fname);
 		return 0;
 	}
 	/*
@@ -38,8 +41,8 @@
 	if(f == 0)
 		fprint(2, "getsubfont: can't read %s: %r\n", name);
 	close(fd);
-	if(ff->scale != 1 && ff->scale != 0)
-		scalesubfont(f, ff->scale);
+	if(scale > 1)
+		scalesubfont(f, scale);
 	return f;
 }
 
diff --git a/src/libdraw/init.c b/src/libdraw/init.c
index b2df7fd..452b6da 100644
--- a/src/libdraw/init.c
+++ b/src/libdraw/init.c
@@ -199,6 +199,7 @@
 getwindow(Display *d, int ref)
 {
 	Image *i, *oi;
+	Font *f;
 	
 	/* XXX check for destroyed? */
 	
@@ -219,6 +220,17 @@
 	_freeimage1(screen);
 	screen = _allocwindow(screen, _screen, i->r, ref, DWhite);
 	d->screenimage = screen;
+
+
+	if(d->dpi >= DefaultDPI*3/2) {
+		for(f=d->firstfont; f != nil; f=f->next)
+			loadhidpi(f);
+	} else {
+		for(f=d->firstfont; f != nil; f=f->next)
+			if(f->lodpi != nil && f->lodpi != f)
+				swapfont(f, &f->hidpi, &f->lodpi);
+	}
+
 	return 0;
 }
 
diff --git a/src/libdraw/openfont.c b/src/libdraw/openfont.c
index ae1462d..97102a2 100644
--- a/src/libdraw/openfont.c
+++ b/src/libdraw/openfont.c
@@ -5,23 +5,41 @@
 extern vlong _drawflength(int);
 int _fontpipe(char*);
 
+int
+parsefontscale(char *name, char **base)
+{
+	char *p;
+	int scale;
+	
+	p = name;
+	scale = 0;
+	while('0' <= *p && *p <= '9') {
+		scale = scale*10 + *p - '0';
+		p++;
+	}
+	if(*p == '*' && scale > 0)
+		*base = p+1;
+	else {
+		*base = name;
+		scale = 1;
+	}
+	return scale;
+}	
+
 Font*
-openfont(Display *d, char *name)
+openfont1(Display *d, char *name)
 {
 	Font *fnt;
 	int fd, i, n, scale;
-	char *buf, *nambuf;
+	char *buf, *nambuf, *fname, *freename;
 
 	nambuf = 0;
-	scale = 1;
-	if('1' <= name[0] && name[0] <= '9' && name[1] == '*') {
-		scale = name[0] - '0';
-		name += 2;
-	}
-	fd = open(name, OREAD);
+	freename = nil;
+	scale = parsefontscale(name, &fname);
 
-	if(fd < 0 && strncmp(name, "/lib/font/bit/", 14) == 0){
-		nambuf = smprint("#9/font/%s", name+14);
+	fd = open(fname, OREAD);
+	if(fd < 0 && strncmp(fname, "/lib/font/bit/", 14) == 0){
+		nambuf = smprint("#9/font/%s", fname+14);
 		if(nambuf == nil)
 			return 0;
 		nambuf = unsharp(nambuf);
@@ -31,12 +49,18 @@
 			free(nambuf);
 			return 0;
 		}
-		name = nambuf;
+		fname = nambuf;
+		if(scale > 1) {
+			name = smprint("%d*%s", scale, fname);
+			freename = name;
+		} else {
+			name = fname;
+		}
 	}
 	if(fd >= 0)
 		n = _drawflength(fd);
-	if(fd < 0 && strncmp(name, "/mnt/font/", 10) == 0) {
-		fd = _fontpipe(name+10);
+	if(fd < 0 && strncmp(fname, "/mnt/font/", 10) == 0) {
+		fd = _fontpipe(fname+10);
 		n = 8192;
 	}
 	if(fd < 0)
@@ -59,6 +83,7 @@
 	fnt = buildfont(d, buf, name);
 	free(buf);
 	free(nambuf);
+	free(freename);
 	if(scale != 1) {
 		fnt->scale = scale;
 		fnt->height *= scale;
@@ -68,6 +93,120 @@
 	return fnt;
 }
 
+void
+swapfont(Font *targ, Font **oldp, Font **newp)
+{
+	Font f, *old, *new;
+
+	if(targ != *oldp)
+		sysfatal("bad swapfont %p %p %p", targ, *oldp, *newp);
+	
+	old = *oldp;
+	new = *newp;
+
+	f.name = old->name;
+	f.display = old->display;
+	f.height = old->height;
+	f.ascent = old->ascent;
+	f.width = old->width;
+	f.nsub = old->nsub;
+	f.age = old->age;
+	f.maxdepth = old->maxdepth;
+	f.ncache = old->ncache;
+	f.nsubf = old->nsubf;
+	f.scale = old->scale;
+	f.cache = old->cache;
+	f.subf = old->subf;
+	f.sub = old->sub;
+	f.cacheimage = old->cacheimage;
+
+	old->name = new->name;
+	old->display = new->display;
+	old->height = new->height;
+	old->ascent = new->ascent;
+	old->width = new->width;
+	old->nsub = new->nsub;
+	old->age = new->age;
+	old->maxdepth = new->maxdepth;
+	old->ncache = new->ncache;
+	old->nsubf = new->nsubf;
+	old->scale = new->scale;
+	old->cache = new->cache;
+	old->subf = new->subf;
+	old->sub = new->sub;
+	old->cacheimage = new->cacheimage;
+
+	new->name = f.name;
+	new->display = f.display;
+	new->height = f.height;
+	new->ascent = f.ascent;
+	new->width = f.width;
+	new->nsub = f.nsub;
+	new->age = f.age;
+	new->maxdepth = f.maxdepth;
+	new->ncache = f.ncache;
+	new->nsubf = f.nsubf;
+	new->scale = f.scale;
+	new->cache = f.cache;
+	new->subf = f.subf;
+	new->sub = f.sub;
+	new->cacheimage = f.cacheimage;
+
+	*oldp = new;
+	*newp = old;
+}
+
+void
+loadhidpi(Font *f)
+{
+	char *name;
+	Font *fnew;
+
+	if(f->hidpi == f)
+		return;
+	if(f->hidpi != nil) {
+		swapfont(f, &f->lodpi, &f->hidpi);
+		return;
+	}
+	
+	name = smprint("%d*%s", f->scale*2, f->name);
+	fnew = openfont1(f->display, name);
+	if(fnew == nil)
+		return;
+	f->hidpi = fnew;
+	free(name);
+
+	swapfont(f, &f->lodpi, &f->hidpi);
+}
+
+Font*
+openfont(Display *d, char *name)
+{
+	Font *f;
+	
+	f = openfont1(d, name);
+	f->lodpi = f;
+	
+	/* add to display list for when dpi changes */
+	/* d can be nil when invoked from mc. */
+	if(d != nil) {
+		f->ondisplaylist = 1;
+		f->prev = d->lastfont;
+		f->next = nil;
+		if(f->prev)
+			f->prev->next = f;
+		else
+			d->firstfont = f;
+		d->lastfont = f;
+	
+		/* if this is a hi-dpi display, find hi-dpi version and swap */
+		if(d->dpi >= DefaultDPI*3/2)
+			loadhidpi(f);
+	}
+
+	return f;
+}
+
 int
 _fontpipe(char *name)
 {
diff --git a/src/libdraw/string.c b/src/libdraw/string.c
index c84112e..392a7e8 100644
--- a/src/libdraw/string.c
+++ b/src/libdraw/string.c
@@ -130,7 +130,7 @@
 		}
 		if(subfontname){
 			freesubfont(sf);
-			if((sf=_getsubfont(f->display, f, subfontname)) == 0){
+			if((sf=_getsubfont(f->display, subfontname)) == 0){
 				def = f->display ? f->display->defaultfont : nil;
 				if(def && f!=def)
 					f = def;
diff --git a/src/libdraw/stringwidth.c b/src/libdraw/stringwidth.c
index e4630ca..522fbc0 100644
--- a/src/libdraw/stringwidth.c
+++ b/src/libdraw/stringwidth.c
@@ -48,7 +48,7 @@
 			}
 			if(subfontname){
 				freesubfont(sf);
-				if((sf=_getsubfont(f->display, f, subfontname)) == 0){
+				if((sf=_getsubfont(f->display, subfontname)) == 0){
 					def = f->display ? f->display->defaultfont : nil;
 					if(def && f!=def)
 						f = def;
diff --git a/src/libdraw/subfontname.c b/src/libdraw/subfontname.c
index 874528b..9280244 100644
--- a/src/libdraw/subfontname.c
+++ b/src/libdraw/subfontname.c
@@ -9,14 +9,16 @@
 char*
 subfontname(char *cfname, char *fname, int maxdepth)
 {
-	char *t, *u, *tmp1, *tmp2;
-	int i;
+	char *t, *u, *tmp1, *tmp2, *base;
+	int i, scale;
+	
+	scale = parsefontscale(fname, &base);
 
 	t = strdup(cfname);  /* t is the return string */
 	if(strcmp(cfname, "*default*") == 0)
 		return t;
 	if(t[0] != '/'){
-		tmp2 = strdup(fname);
+		tmp2 = strdup(base);
 		u = utfrrune(tmp2, '/');
 		if(u)
 			u[0] = 0;
@@ -38,13 +40,24 @@
 		tmp2 = smprint("%s.%d", t, i);
 		if(access(tmp2, AREAD) == 0) {
 			free(t);
+			if(scale > 1) {
+				t = smprint("%d*%s", scale, tmp2);
+				free(tmp2);
+				tmp2 = t;
+			}
 			return tmp2;
 		}
 	}
 
 	/* try default */
-	if(strncmp(t, "/mnt/font/", 10) == 0 || access(t, AREAD) == 0)
+	if(strncmp(t, "/mnt/font/", 10) == 0 || access(t, AREAD) == 0) {
+		if(scale > 1) {
+			tmp2 = smprint("%d*%s", scale, t);
+			free(t);
+			t = tmp2;
+		}
 		return t;
+	}
 
 	return nil;
 }