xref: /illumos-gate/usr/src/common/font/font.c (revision 6e797a8f52756903fc70ae9b54c0d2223f49a9ef)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright 2017 Toomas Soome <tsoome@me.com>
29  */
30 
31 /*
32  * Generic font related data and functions shared by early boot console
33  * in dboot, kernel startup and full kernel.
34  */
35 #include <sys/types.h>
36 #include <sys/systm.h>
37 #include <sys/tem_impl.h>
38 #include <sys/rgb.h>
39 #include <sys/font.h>
40 #include <sys/sysmacros.h>
41 
42 /*
43  * To simplify my life, I am "temporarily" collecting the commonly used
44  * color bits here. The bits shared between loader, dboot, early boot, tem.
45  * This data would need some sort of API, but I am in no condition to figure
46  * something out right now.
47  */
48 
49 /* ANSI color to sun color translation. */
50 /* BEGIN CSTYLED */
51 /*                                         Bk  Rd  Gr  Br  Bl  Mg  Cy  Wh */
52 const uint8_t dim_xlate[XLATE_NCOLORS] = {  1,  5,  3,  7,  2,  6,  4,  8 };
53 const uint8_t brt_xlate[XLATE_NCOLORS] = {  9, 13, 11, 15, 10, 14, 12,  0 };
54 
55 const uint8_t solaris_color_to_pc_color[16] = {
56 	pc_brt_white,		/*  0 - brt_white	*/
57 	pc_black,		/*  1 - black		*/
58 	pc_blue,		/*  2 - blue		*/
59 	pc_green,		/*  3 - green		*/
60 	pc_cyan,		/*  4 - cyan		*/
61 	pc_red,			/*  5 - red		*/
62 	pc_magenta,		/*  6 - magenta		*/
63 	pc_brown,		/*  7 - brown		*/
64 	pc_white,		/*  8 - white		*/
65 	pc_grey,		/*  9 - grey		*/
66 	pc_brt_blue,		/* 10 - brt_blue	*/
67 	pc_brt_green,		/* 11 - brt_green	*/
68 	pc_brt_cyan,		/* 12 - brt_cyan	*/
69 	pc_brt_red,		/* 13 - brt_red		*/
70 	pc_brt_magenta,		/* 14 - brt_magenta	*/
71 	pc_yellow		/* 15 - yellow		*/
72 };
73 
74 const uint8_t pc_color_to_solaris_color[16] = {
75 	sun_black,		/*  0 - black		*/
76 	sun_blue,		/*  1 - blue		*/
77 	sun_green,		/*  2 - green		*/
78 	sun_cyan,		/*  3 - cyan		*/
79 	sun_red,		/*  4 - red		*/
80 	sun_magenta,		/*  5 - magenta		*/
81 	sun_brown,		/*  6 - brown		*/
82 	sun_white,		/*  7 - white		*/
83 	sun_grey,		/*  8 - grey		*/
84 	sun_brt_blue,		/*  9 - brt_blue	*/
85 	sun_brt_green,		/* 10 - brt_green	*/
86 	sun_brt_cyan,		/* 11 - brt_cyan	*/
87 	sun_brt_red,		/* 12 - brt_red		*/
88 	sun_brt_magenta,	/* 13 - brt_magenta	*/
89 	sun_yellow,		/* 14 - yellow		*/
90 	sun_brt_white		/* 15 - brt_white	*/
91 };
92 
93 /* 4-bit to 24-bit color translation. */
94 const text_cmap_t cmap4_to_24 = {
95 /* 0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15
96   Wh+  Bk   Bl   Gr   Cy   Rd   Mg   Br   Wh   Bk+  Bl+  Gr+  Cy+  Rd+  Mg+  Yw */
97   .red = {
98  0xff,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x40,0x00,0x00,0x00,0xff,0xff,0xff
99 },
100   .green = {
101  0xff,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x80,0x40,0x00,0xff,0xff,0x00,0x00,0xff
102 },
103   .blue = {
104  0xff,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x40,0xff,0x00,0xff,0x00,0xff,0x00
105 }
106 };
107 /* END CSTYLED */
108 
109 /* RGB configuration from boot loader */
110 rgb_t rgb_info = {
111 	.red = { .size = 8, .pos = 16 },
112 	.green = { .size = 8, .pos = 8 },
113 	.blue = { .size = 8, .pos = 0 }
114 };
115 
116 /*
117  * Map r, g, b to RGB value.
118  */
119 uint32_t
rgb_to_color(const rgb_t * rgb,uint32_t a,uint32_t r,uint32_t g,uint32_t b)120 rgb_to_color(const rgb_t *rgb, uint32_t a, uint32_t r, uint32_t g, uint32_t b)
121 {
122 	uint32_t color;
123 	int pos, size;
124 
125 	color = 0;
126 	if (a != 0) {
127 		if (rgb->red.pos != 0 &&
128 		    rgb->green.pos != 0 &&
129 		    rgb->blue.pos != 0) {
130 			pos = 0;
131 			size = MIN(rgb->red.pos,
132 			    MIN(rgb->green.pos, rgb->blue.pos));
133 		} else {
134 			pos = 24;
135 			size = (rgb->red.size + rgb->green.size +
136 			    rgb->blue.size) / 3;
137 		}
138 		color = ((a * ((1 << size) - 1)) / 0xff) << pos;
139 	}
140 
141 	pos = rgb->red.pos;
142 	size = rgb->red.size;
143 	color |= ((r * ((1 << size) - 1)) / 0xff) << pos;
144 
145 	pos = rgb->green.pos;
146 	size = rgb->green.size;
147 	color |= (((g * ((1 << size) - 1)) / 0xff) << pos);
148 
149 	pos = rgb->blue.pos;
150 	size = rgb->blue.size;
151 	color |= (((b * ((1 << size) - 1)) / 0xff) << pos);
152 
153 	return (color);
154 }
155 
156 uint32_t
rgb_color_map(const rgb_t * rgb,uint8_t index,uint8_t alpha)157 rgb_color_map(const rgb_t *rgb, uint8_t index, uint8_t alpha)
158 {
159 	uint32_t color, code, gray, level;
160 
161 	if (index < 16) {
162 		color = rgb_to_color(rgb, alpha, cmap4_to_24.red[index],
163 		    cmap4_to_24.green[index], cmap4_to_24.blue[index]);
164 		return (color);
165 	}
166 
167 	/* 6x6x6 color cube */
168 	if (index > 15 && index < 232) {
169 		uint32_t red, green, blue;
170 
171 		for (red = 0; red < 6; red++) {
172 			for (green = 0; green < 6; green++) {
173 				for (blue = 0; blue < 6; blue++) {
174 					code = 16 + (red * 36) +
175 					    (green * 6) + blue;
176 					if (code != index)
177 						continue;
178 					red = red ? (red * 40 + 55) : 0;
179 					green = green ? (green * 40 + 55) : 0;
180 					blue = blue ? (blue * 40 + 55) : 0;
181 					color = rgb_to_color(rgb, alpha,
182 					    red, green, blue);
183 					return (color);
184 				}
185 			}
186 		}
187 	}
188 
189 	/* colors 232-255 are a grayscale ramp */
190 	for (gray = 0; gray < 24; gray++) {
191 		level = (gray * 10) + 8;
192 		code = 232 + gray;
193 		if (code == index)
194 			break;
195 	}
196 	return (rgb_to_color(rgb, alpha, level, level, level));
197 }
198 /*
199  * Fonts are statically linked with this module. At some point an
200  * RFE might be desireable to allow dynamic font loading.  The
201  * original intention to facilitate dynamic fonts can be seen
202  * by examining the data structures and set_font().  As much of
203  * the original code is retained but modified to be suited for
204  * traversing a list of static fonts.
205  */
206 
207 /*
208  * Must be sorted by font size in descending order
209  */
210 font_list_t fonts = STAILQ_HEAD_INITIALIZER(fonts);
211 
212 /*
213  * Reset font flags to FONT_AUTO.
214  */
215 void
reset_font_flags(void)216 reset_font_flags(void)
217 {
218 	struct fontlist *fl;
219 
220 	STAILQ_FOREACH(fl, &fonts, font_next) {
221 		fl->font_flags = FONT_AUTO;
222 	}
223 }
224 
225 __weak_symbol bitmap_data_t *
gfx_get_font(short rows __unused,short cols __unused,short height __unused,short width __unused)226 gfx_get_font(short rows __unused, short cols __unused, short height __unused,
227     short width __unused)
228 {
229 	return (NULL);
230 }
231 
232 bitmap_data_t *
set_font(short * rows,short * cols,short h,short w)233 set_font(short *rows, short *cols, short h, short w)
234 {
235 	bitmap_data_t *font = NULL;
236 	struct fontlist	*fl;
237 	unsigned height = h;
238 	unsigned width = w;
239 
240 	/*
241 	 * First check for manually loaded font.
242 	 */
243 	STAILQ_FOREACH(fl, &fonts, font_next) {
244 		if (fl->font_flags == FONT_MANUAL ||
245 		    fl->font_flags == FONT_BOOT) {
246 			font = fl->font_data;
247 			if (font->font == NULL && fl->font_load != NULL &&
248 			    fl->font_name != NULL) {
249 				font = fl->font_load(fl->font_name);
250 			}
251 			if (font == NULL || font->font == NULL)
252 				font = NULL;
253 			break;
254 		}
255 	}
256 
257 	if (font == NULL)
258 		font = gfx_get_font(*rows, *cols, h, w);
259 
260 	if (font != NULL) {
261 		*rows = (height - BORDER_PIXELS) / font->height;
262 		*cols = (width - BORDER_PIXELS) / font->width;
263 		return (font);
264 	}
265 
266 	/*
267 	 * Find best font for these dimensions, or use default
268 	 *
269 	 * A 1 pixel border is the absolute minimum we could have
270 	 * as a border around the text window (BORDER_PIXELS = 2),
271 	 * however a slightly larger border not only looks better
272 	 * but for the fonts currently statically built into the
273 	 * emulator causes much better font selection for the
274 	 * normal range of screen resolutions.
275 	 */
276 	STAILQ_FOREACH(fl, &fonts, font_next) {
277 		font = fl->font_data;
278 		if ((((*rows * font->height) + BORDER_PIXELS) <= height) &&
279 		    (((*cols * font->width) + BORDER_PIXELS) <= width)) {
280 			if (font->font == NULL ||
281 			    fl->font_flags == FONT_RELOAD) {
282 				if (fl->font_load != NULL &&
283 				    fl->font_name != NULL) {
284 					font = fl->font_load(fl->font_name);
285 				}
286 				if (font == NULL)
287 					continue;
288 			}
289 			*rows = (height - BORDER_PIXELS) / font->height;
290 			*cols = (width - BORDER_PIXELS) / font->width;
291 			break;
292 		}
293 		font = NULL;
294 	}
295 
296 	if (font == NULL) {
297 		/*
298 		 * We have fonts sorted smallest last, try it before
299 		 * falling back to builtin.
300 		 */
301 		fl = STAILQ_LAST(&fonts, fontlist, font_next);
302 		if (fl != NULL && fl->font_load != NULL &&
303 		    fl->font_name != NULL) {
304 			font = fl->font_load(fl->font_name);
305 		}
306 		if (font == NULL)
307 			font = &DEFAULT_FONT_DATA;
308 
309 		*rows = (height - BORDER_PIXELS) / font->height;
310 		*cols = (width - BORDER_PIXELS) / font->width;
311 	}
312 
313 	return (font);
314 }
315 
316 /* Binary search for the glyph. Return 0 if not found. */
317 static uint16_t
font_bisearch(const struct font_map * map,uint32_t len,uint32_t src)318 font_bisearch(const struct font_map *map, uint32_t len, uint32_t src)
319 {
320 	unsigned min, mid, max;
321 
322 	min = 0;
323 	max = len - 1;
324 
325 	/* Empty font map. */
326 	if (len == 0)
327 		return (0);
328 	/* Character below minimal entry. */
329 	if (src < map[0].font_src)
330 		return (0);
331 	/* Optimization: ASCII characters occur very often. */
332 	if (src <= map[0].font_src + map[0].font_len)
333 		return (src - map[0].font_src + map[0].font_dst);
334 	/* Character above maximum entry. */
335 	if (src > map[max].font_src + map[max].font_len)
336 		return (0);
337 
338 	/* Binary search. */
339 	while (max >= min) {
340 		mid = (min + max) / 2;
341 		if (src < map[mid].font_src)
342 			max = mid - 1;
343 		else if (src > map[mid].font_src + map[mid].font_len)
344 			min = mid + 1;
345 		else
346 			return (src - map[mid].font_src + map[mid].font_dst);
347 	}
348 
349 	return (0);
350 }
351 
352 /*
353  * Return glyph bitmap. If glyph is not found, we will return bitmap
354  * for the first (offset 0) glyph.
355  */
356 const uint8_t *
font_lookup(const struct font * vf,uint32_t c)357 font_lookup(const struct font *vf, uint32_t c)
358 {
359 	uint32_t src;
360 	uint16_t dst;
361 	size_t stride;
362 
363 	src = TEM_CHAR(c);
364 
365 	/* Substitute bold with normal if not found. */
366 	if (TEM_CHAR_ATTR(c) & TEM_ATTR_BOLD) {
367 		dst = font_bisearch(vf->vf_map[VFNT_MAP_BOLD],
368 		    vf->vf_map_count[VFNT_MAP_BOLD], src);
369 		if (dst != 0)
370 			goto found;
371 	}
372 	dst = font_bisearch(vf->vf_map[VFNT_MAP_NORMAL],
373 	    vf->vf_map_count[VFNT_MAP_NORMAL], src);
374 
375 found:
376 	stride = howmany(vf->vf_width, 8) * vf->vf_height;
377 	return (&vf->vf_bytes[dst * stride]);
378 }
379 
380 /*
381  * bit_to_pix4 is for 4-bit frame buffers.  It will write one output byte
382  * for each 2 bits of input bitmap.  It inverts the input bits before
383  * doing the output translation, for reverse video.
384  *
385  * Assuming foreground is 0001 and background is 0000...
386  * An input data byte of 0x53 will output the bit pattern
387  * 00000001 00000001 00000000 00010001.
388  */
389 
390 void
font_bit_to_pix4(struct font * f,uint8_t * dest,uint32_t c,uint32_t fg_color,uint32_t bg_color)391 font_bit_to_pix4(
392     struct font *f,
393     uint8_t *dest,
394     uint32_t c,
395     uint32_t fg_color,
396     uint32_t bg_color)
397 {
398 	uint32_t row;
399 	int	byte;
400 	int	i;
401 	const uint8_t *cp, *ul;
402 	uint8_t	data;
403 	uint8_t	nibblett;
404 	int	bytes_wide;
405 
406 	if (TEM_CHAR_ATTR(c) & TEM_ATTR_UNDERLINE)
407 		ul = font_lookup(f, 0x0332);	/* combining low line */
408 	else
409 		ul = NULL;
410 
411 	cp = font_lookup(f, c);
412 	bytes_wide = (f->vf_width + 7) / 8;
413 
414 	for (row = 0; row < f->vf_height; row++) {
415 		for (byte = 0; byte < bytes_wide; byte++) {
416 			if (ul == NULL)
417 				data = *cp++;
418 			else
419 				data = *cp++ | *ul++;
420 			for (i = 0; i < 4; i++) {
421 				nibblett = (data >> ((3-i) * 2)) & 0x3;
422 				switch (nibblett) {
423 				case 0x0:
424 					*dest++ = bg_color << 4 | bg_color;
425 					break;
426 				case 0x1:
427 					*dest++ = bg_color << 4 | fg_color;
428 					break;
429 				case 0x2:
430 					*dest++ = fg_color << 4 | bg_color;
431 					break;
432 				case 0x3:
433 					*dest++ = fg_color << 4 | fg_color;
434 					break;
435 				}
436 			}
437 		}
438 	}
439 }
440 
441 /*
442  * bit_to_pix8 is for 8-bit frame buffers.  It will write one output byte
443  * for each bit of input bitmap.  It inverts the input bits before
444  * doing the output translation, for reverse video.
445  *
446  * Assuming foreground is 00000001 and background is 00000000...
447  * An input data byte of 0x53 will output the bit pattern
448  * 0000000 000000001 00000000 00000001 00000000 00000000 00000001 00000001.
449  */
450 
451 void
font_bit_to_pix8(struct font * f,uint8_t * dest,uint32_t c,uint32_t fg_color,uint32_t bg_color)452 font_bit_to_pix8(
453     struct font *f,
454     uint8_t *dest,
455     uint32_t c,
456     uint32_t fg_color,
457     uint32_t bg_color)
458 {
459 	uint32_t row;
460 	int	byte;
461 	int	i;
462 	const uint8_t *cp, *ul;
463 	uint8_t	data;
464 	int	bytes_wide;
465 	uint8_t	mask;
466 	int	bitsleft, nbits;
467 
468 	if (TEM_CHAR_ATTR(c) & TEM_ATTR_UNDERLINE)
469 		ul = font_lookup(f, 0x0332);	/* combining low line */
470 	else
471 		ul = NULL;
472 
473 	cp = font_lookup(f, c);
474 	bytes_wide = (f->vf_width + 7) / 8;
475 
476 	for (row = 0; row < f->vf_height; row++) {
477 		bitsleft = f->vf_width;
478 		for (byte = 0; byte < bytes_wide; byte++) {
479 			if (ul == NULL)
480 				data = *cp++;
481 			else
482 				data = *cp++ | *ul++;
483 			mask = 0x80;
484 			nbits = MIN(8, bitsleft);
485 			bitsleft -= nbits;
486 			for (i = 0; i < nbits; i++) {
487 				*dest++ = (data & mask ? fg_color: bg_color);
488 				mask = mask >> 1;
489 			}
490 		}
491 	}
492 }
493 
494 /*
495  * bit_to_pix16 is for 16-bit frame buffers.  It will write two output bytes
496  * for each bit of input bitmap.  It inverts the input bits before
497  * doing the output translation, for reverse video.
498  *
499  * Assuming foreground is 11111111 11111111
500  * and background is 00000000 00000000
501  * An input data byte of 0x53 will output the bit pattern
502  *
503  * 00000000 00000000
504  * 11111111 11111111
505  * 00000000 00000000
506  * 11111111 11111111
507  * 00000000 00000000
508  * 00000000 00000000
509  * 11111111 11111111
510  * 11111111 11111111
511  *
512  */
513 
514 void
font_bit_to_pix16(struct font * f,uint16_t * dest,uint32_t c,uint32_t fg_color16,uint32_t bg_color16)515 font_bit_to_pix16(
516     struct font *f,
517     uint16_t *dest,
518     uint32_t c,
519     uint32_t fg_color16,
520     uint32_t bg_color16)
521 {
522 	uint32_t row;
523 	int	byte;
524 	int	i;
525 	const uint8_t *cp, *ul;
526 	uint16_t data, d;
527 	int	bytes_wide;
528 	int	bitsleft, nbits;
529 
530 	if (TEM_CHAR_ATTR(c) & TEM_ATTR_UNDERLINE)
531 		ul = font_lookup(f, 0x0332);	/* combining low line */
532 	else
533 		ul = NULL;
534 
535 	cp = font_lookup(f, c);
536 	bytes_wide = (f->vf_width + 7) / 8;
537 
538 	for (row = 0; row < f->vf_height; row++) {
539 		bitsleft = f->vf_width;
540 		for (byte = 0; byte < bytes_wide; byte++) {
541 			if (ul == NULL)
542 				data = *cp++;
543 			else
544 				data = *cp++ | *ul++;
545 			nbits = MIN(8, bitsleft);
546 			bitsleft -= nbits;
547 			for (i = 0; i < nbits; i++) {
548 				d = ((data << i) & 0x80 ?
549 				    fg_color16 : bg_color16);
550 				*dest++ = d;
551 			}
552 		}
553 	}
554 }
555 
556 /*
557  * bit_to_pix24 is for 24-bit frame buffers.  It will write three output bytes
558  * for each bit of input bitmap.  It inverts the input bits before
559  * doing the output translation, for reverse video.
560  *
561  * Assuming foreground is 11111111 11111111 11111111
562  * and background is 00000000 00000000 00000000
563  * An input data byte of 0x53 will output the bit pattern
564  *
565  * 00000000 00000000 00000000
566  * 11111111 11111111 11111111
567  * 00000000 00000000 00000000
568  * 11111111 11111111 11111111
569  * 00000000 00000000 00000000
570  * 00000000 00000000 00000000
571  * 11111111 11111111 11111111
572  * 11111111 11111111 11111111
573  *
574  */
575 
576 void
font_bit_to_pix24(struct font * f,uint8_t * dest,uint32_t c,uint32_t fg_color32,uint32_t bg_color32)577 font_bit_to_pix24(
578     struct font *f,
579     uint8_t *dest,
580     uint32_t c,
581     uint32_t fg_color32,
582     uint32_t bg_color32)
583 {
584 	uint32_t row;
585 	int	byte;
586 	int	i;
587 	const uint8_t *cp, *ul;
588 	uint32_t data, d;
589 	int	bytes_wide;
590 	int	bitsleft, nbits;
591 
592 	if (TEM_CHAR_ATTR(c) & TEM_ATTR_UNDERLINE)
593 		ul = font_lookup(f, 0x0332);	/* combining low line */
594 	else
595 		ul = NULL;
596 
597 	cp = font_lookup(f, c);
598 	bytes_wide = (f->vf_width + 7) / 8;
599 
600 	for (row = 0; row < f->vf_height; row++) {
601 		bitsleft = f->vf_width;
602 		for (byte = 0; byte < bytes_wide; byte++) {
603 			if (ul == NULL)
604 				data = *cp++;
605 			else
606 				data = *cp++ | *ul++;
607 
608 			nbits = MIN(8, bitsleft);
609 			bitsleft -= nbits;
610 			for (i = 0; i < nbits; i++) {
611 				d = ((data << i) & 0x80 ?
612 				    fg_color32 : bg_color32);
613 				*dest++ = d & 0xff;
614 				*dest++ = (d >> 8) & 0xff;
615 				*dest++ = (d >> 16) & 0xff;
616 			}
617 		}
618 	}
619 }
620 
621 /*
622  * bit_to_pix32 is for 32-bit frame buffers.  It will write four output bytes
623  * for each bit of input bitmap.  It inverts the input bits before
624  * doing the output translation, for reverse video.  Note that each
625  * 24-bit RGB value is finally stored in a 32-bit unsigned int, with the
626  * high-order byte set to zero.
627  *
628  * Assuming foreground is 00000000 11111111 11111111 11111111
629  * and background is 00000000 00000000 00000000 00000000
630  * An input data byte of 0x53 will output the bit pattern
631  *
632  * 00000000 00000000 00000000 00000000
633  * 00000000 11111111 11111111 11111111
634  * 00000000 00000000 00000000 00000000
635  * 00000000 11111111 11111111 11111111
636  * 00000000 00000000 00000000 00000000
637  * 00000000 00000000 00000000 00000000
638  * 00000000 11111111 11111111 11111111
639  * 00000000 11111111 11111111 11111111
640  *
641  */
642 
643 void
font_bit_to_pix32(struct font * f,uint32_t * dest,uint32_t c,uint32_t fg_color32,uint32_t bg_color32)644 font_bit_to_pix32(
645     struct font *f,
646     uint32_t *dest,
647     uint32_t c,
648     uint32_t fg_color32,
649     uint32_t bg_color32)
650 {
651 	uint32_t row;
652 	int	byte;
653 	int	i;
654 	const uint8_t *cp, *ul;
655 	uint32_t data;
656 	int	bytes_wide;
657 	int	bitsleft, nbits;
658 
659 	if (TEM_CHAR_ATTR(c) & TEM_ATTR_UNDERLINE)
660 		ul = font_lookup(f, 0x0332);	/* combining low line */
661 	else
662 		ul = NULL;
663 
664 	cp = font_lookup(f, c);
665 	bytes_wide = (f->vf_width + 7) / 8;
666 
667 	for (row = 0; row < f->vf_height; row++) {
668 		bitsleft = f->vf_width;
669 		for (byte = 0; byte < bytes_wide; byte++) {
670 			if (ul == NULL)
671 				data = *cp++;
672 			else
673 				data = *cp++ | *ul++;
674 			nbits = MIN(8, bitsleft);
675 			bitsleft -= nbits;
676 			for (i = 0; i < nbits; i++) {
677 				*dest++ = ((data << i) & 0x80 ?
678 				    fg_color32 : bg_color32);
679 			}
680 		}
681 	}
682 }
683