xref: /plan9/sys/src/libdraw/font.c (revision 6bbfed0d85c6d7248503ef0614d0f1e40438b735)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 
5 static int	fontresize(Font*, int, int, int);
6 static int	freeup(Font*);
7 
8 #define	PJW	0	/* use NUL==pjw for invisible characters */
9 
10 int
cachechars(Font * f,char ** ss,Rune ** rr,ushort * cp,int max,int * wp,char ** subfontname)11 cachechars(Font *f, char **ss, Rune **rr, ushort *cp, int max, int *wp, char **subfontname)
12 {
13 	int i, th, sh, h, ld, w, rw, wid, nc;
14 	char *sp;
15 	Rune r, *rp, vr;
16 	ulong a;
17 	Cacheinfo *c, *tc, *ec;
18 
19 	if(ss){
20 		sp = *ss;
21 		rp = L"";
22 	}else{
23 		sp = "";
24 		rp = *rr;
25 	}
26 	wid = 0;
27 	*subfontname = 0;
28 	for(i=0; i<max && (*sp || *rp); sp+=w, rp+=rw){
29 		if(ss){
30 			r = *(uchar*)sp;
31 			if(r < Runeself)
32 				w = 1;
33 			else{
34 				w = chartorune(&vr, sp);
35 				r = vr;
36 			}
37 			rw = 0;
38 		}else{
39 			r = *rp;
40 			w = 0;
41 			rw = 1;
42 		}
43 
44 		sh = (17 * (uint)r) & (f->ncache-NFLOOK-1);
45 		c = &f->cache[sh];
46 		ec = c+NFLOOK;
47 		h = sh;
48 		while(c < ec){
49 			if(c->value==r && c->age)
50 				goto Found;
51 			c++;
52 			h++;
53 		}
54 
55 		/*
56 		 * Not found; toss out oldest entry
57 		 */
58 		a = ~0;
59 		th = sh;
60 		tc = &f->cache[th];
61 		while(tc < ec){
62 			if(tc->age < a){
63 				a = tc->age;
64 				h = th;
65 				c = tc;
66 			}
67 			tc++;
68 			th++;
69 		}
70 
71 		if(a && (f->age-a)<500){	/* kicking out too recent; resize */
72 			nc = 2*(f->ncache-NFLOOK) + NFLOOK;
73 			if(nc <= MAXFCACHE){
74 				if(i == 0)
75 					fontresize(f, f->width, nc, f->maxdepth);
76 				/* else flush first; retry will resize */
77 				break;
78 			}
79 		}
80 
81 		if(c->age == f->age)	/* flush pending string output */
82 			break;
83 
84 		ld = loadchar(f, r, c, h, i, subfontname);
85 		if(ld <= 0){
86 			if(ld == 0)
87 				continue;
88 			break;
89 		}
90 		c = &f->cache[h];	/* may have reallocated f->cache */
91 
92 	    Found:
93 		wid += c->width;
94 		c->age = f->age;
95 		cp[i] = h;
96 		i++;
97 	}
98 	if(ss)
99 		*ss = sp;
100 	else
101 		*rr = rp;
102 	*wp = wid;
103 	return i;
104 }
105 
106 void
agefont(Font * f)107 agefont(Font *f)
108 {
109 	Cacheinfo *c, *ec;
110 	Cachesubf *s, *es;
111 
112 	f->age++;
113 	if(f->age == 65536){
114 		/*
115 		 * Renormalize ages
116 		 */
117 		c = f->cache;
118 		ec = c+f->ncache;
119 		while(c < ec){
120 			if(c->age){
121 				c->age >>= 2;
122 				c->age++;
123 			}
124 			c++;
125 		}
126 		s = f->subf;
127 		es = s+f->nsubf;
128 		while(s < es){
129 			if(s->age){
130 				if(s->age<SUBFAGE && s->cf->name != nil){
131 					/* clean up */
132 					if(display &&
133 					    s->f != display->defaultsubfont)
134 						freesubfont(s->f);
135 					s->cf = nil;
136 					s->f = nil;
137 					s->age = 0;
138 				}else{
139 					s->age >>= 2;
140 					s->age++;
141 				}
142 			}
143 			s++;
144 		}
145 		f->age = (65536>>2) + 1;
146 	}
147 }
148 
149 static Subfont*
cf2subfont(Cachefont * cf,Font * f)150 cf2subfont(Cachefont *cf, Font *f)
151 {
152 	int depth;
153 	char *name;
154 	Subfont *sf;
155 
156 	name = cf->subfontname;
157 	if(name == nil){
158 		if(f->display && f->display->screenimage)
159 			depth = f->display->screenimage->depth;
160 		else
161 			depth = 8;
162 		name = subfontname(cf->name, f->name, depth);
163 		if(name == nil)
164 			return nil;
165 		cf->subfontname = name;
166 	}
167 	sf = lookupsubfont(f->display, name);
168 	return sf;
169 }
170 
171 /* return 1 if load succeeded, 0 if failed, -1 if must retry */
172 int
loadchar(Font * f,Rune r,Cacheinfo * c,int h,int noflush,char ** subfontname)173 loadchar(Font *f, Rune r, Cacheinfo *c, int h, int noflush, char **subfontname)
174 {
175 	int i, oi, wid, top, bottom;
176 	Rune pic;
177 	Fontchar *fi;
178 	Cachefont *cf;
179 	Cachesubf *subf, *of;
180 	uchar *b;
181 
182 	pic = r;
183     Again:
184 	for(i=0; i<f->nsub; i++){
185 		cf = f->sub[i];
186 		if(cf->min<=pic && pic<=cf->max)
187 			goto Found;
188 	}
189     TryPJW:
190 	if(pic != PJW){
191 		pic = PJW;
192 		goto Again;
193 	}
194 	return 0;
195 
196     Found:
197 	/*
198 	 * Choose exact or oldest
199 	 */
200 	oi = 0;
201 	subf = &f->subf[0];
202 	for(i=0; i<f->nsubf; i++){
203 		if(cf == subf->cf)
204 			goto Found2;
205 		if(subf->age < f->subf[oi].age)
206 			oi = i;
207 		subf++;
208 	}
209 	subf = &f->subf[oi];
210 
211 	if(subf->f){
212 		if(f->age-subf->age>SUBFAGE || f->nsubf>MAXSUBF){
213     Toss:
214 			/* ancient data; toss */
215 			freesubfont(subf->f);
216 			subf->cf = nil;
217 			subf->f = nil;
218 			subf->age = 0;
219 		}else{				/* too recent; grow instead */
220 			of = f->subf;
221 			f->subf = malloc((f->nsubf+DSUBF)*sizeof *subf);
222 			if(f->subf == nil){
223 				f->subf = of;
224 				goto Toss;
225 			}
226 			memmove(f->subf, of, (f->nsubf+DSUBF)*sizeof *subf);
227 			memset(f->subf+f->nsubf, 0, DSUBF*sizeof *subf);
228 			subf = &f->subf[f->nsubf];
229 			f->nsubf += DSUBF;
230 			free(of);
231 		}
232 	}
233 	subf->age = 0;
234 	subf->cf = nil;
235 	subf->f = cf2subfont(cf, f);
236 	if(subf->f == nil){
237 		if(cf->subfontname == nil)
238 			goto TryPJW;
239 		*subfontname = cf->subfontname;
240 		return -1;
241 	}
242 
243 	subf->cf = cf;
244 	if(subf->f->ascent > f->ascent && f->display){
245 		/* should print something? this is a mistake in the font file */
246 		/* must prevent c->top from going negative when loading cache */
247 		Image *b;
248 		int d, t;
249 		d = subf->f->ascent - f->ascent;
250 		b = subf->f->bits;
251 		draw(b, b->r, b, nil, addpt(b->r.min, Pt(0, d)));
252 		draw(b, Rect(b->r.min.x, b->r.max.y-d, b->r.max.x, b->r.max.y), f->display->black, nil, b->r.min);
253 		for(i=0; i<subf->f->n; i++){
254 			t = subf->f->info[i].top-d;
255 			if(t < 0)
256 				t = 0;
257 			subf->f->info[i].top = t;
258 			t = subf->f->info[i].bottom-d;
259 			if(t < 0)
260 				t = 0;
261 			subf->f->info[i].bottom = t;
262 		}
263 		subf->f->ascent = f->ascent;
264 	}
265 
266     Found2:
267 	subf->age = f->age;
268 
269 	/* possible overflow here, but works out okay */
270 	pic += cf->offset;
271 	pic -= cf->min;
272 	if(pic >= subf->f->n)
273 		goto TryPJW;
274 	fi = &subf->f->info[pic];
275 	if(fi->width == 0)
276 		goto TryPJW;
277 	wid = (fi+1)->x - fi->x;
278 	if(f->width < wid || f->width == 0 || f->maxdepth < subf->f->bits->depth){
279 		/*
280 		 * Flush, free, reload (easier than reformatting f->b)
281 		 */
282 		if(noflush)
283 			return -1;
284 		if(f->width < wid)
285 			f->width = wid;
286 		if(f->maxdepth < subf->f->bits->depth)
287 			f->maxdepth = subf->f->bits->depth;
288 		i = fontresize(f, f->width, f->ncache, f->maxdepth);
289 		if(i <= 0)
290 			return i;
291 		/* c is still valid as didn't reallocate f->cache */
292 	}
293 	c->value = r;
294 	top = fi->top + (f->ascent-subf->f->ascent);
295 	bottom = fi->bottom + (f->ascent-subf->f->ascent);
296 	c->width = fi->width;
297 	c->x = h*f->width;
298 	c->left = fi->left;
299 	if(f->display == nil)
300 		return 1;
301 	flushimage(f->display, 0);	/* flush any pending errors */
302 	b = bufimage(f->display, 37);
303 	if(b == 0)
304 		return 0;
305 	b[0] = 'l';
306 	BPLONG(b+1, f->cacheimage->id);
307 	BPLONG(b+5, subf->f->bits->id);
308 	BPSHORT(b+9, c-f->cache);
309 	BPLONG(b+11, c->x);
310 	BPLONG(b+15, top);
311 	BPLONG(b+19, c->x+((fi+1)->x-fi->x));
312 	BPLONG(b+23, bottom);
313 	BPLONG(b+27, fi->x);
314 	BPLONG(b+31, fi->top);
315 	b[35] = fi->left;
316 	b[36] = fi->width;
317 	return 1;
318 }
319 
320 /* release all subfonts, return number freed */
321 static
322 int
freeup(Font * f)323 freeup(Font *f)
324 {
325 	Cachesubf *s, *es;
326 	int nf;
327 
328 	if(f->sub[0]->name == nil)	/* font from mkfont; don't free */
329 		return 0;
330 	s = f->subf;
331 	es = s+f->nsubf;
332 	nf = 0;
333 	while(s < es){
334 		if(s->age){
335 			freesubfont(s->f);
336 			s->cf = nil;
337 			s->f = nil;
338 			s->age = 0;
339 			nf++;
340 		}
341 		s++;
342 	}
343 	return nf;
344 }
345 
346 /* return whether resize succeeded && f->cache is unchanged */
347 static int
fontresize(Font * f,int wid,int ncache,int depth)348 fontresize(Font *f, int wid, int ncache, int depth)
349 {
350 	Cacheinfo *i;
351 	int ret;
352 	Image *new;
353 	uchar *b;
354 	Display *d;
355 
356 	ret = 0;
357 	if(depth <= 0)
358 		depth = 1;
359 	if(wid <= 0)
360 		wid = 1;
361 
362 	d = f->display;
363 	if(d == nil)
364 		goto Nodisplay;
365 
366 	new = allocimage(d, Rect(0, 0, ncache*wid, f->height), CHAN1(CGrey, depth), 0, 0);
367 	if(new == nil){
368 		fprint(2, "font cache resize failed: %r\n");
369 		abort();
370 		goto Return;
371 	}
372 	flushimage(d, 0);	/* flush any pending errors */
373 	b = bufimage(d, 1+4+4+1);
374 	if(b == 0){
375 		freeimage(new);
376 		goto Return;
377 	}
378 	b[0] = 'i';
379 	BPLONG(b+1, new->id);
380 	BPLONG(b+5, ncache);
381 	b[9] = f->ascent;
382 	if(flushimage(d, 0) < 0){
383 		fprint(2, "resize: init failed: %r\n");
384 		freeimage(new);
385 		goto Return;
386 	}
387 	freeimage(f->cacheimage);
388 	f->cacheimage = new;
389     Nodisplay:
390 	f->width = wid;
391 	f->maxdepth = depth;
392 	ret = 1;
393 	if(f->ncache != ncache){
394 		i = malloc(ncache*sizeof f->cache[0]);
395 		if(i != nil){
396 			ret = 0;
397 			free(f->cache);
398 			f->ncache = ncache;
399 			f->cache = i;
400 		}
401 		/* else just wipe the cache clean and things will be ok */
402 	}
403     Return:
404 	memset(f->cache, 0, f->ncache*sizeof f->cache[0]);
405 	return ret;
406 }
407