diff --git a/usr.bin/vtfontcvt/vtfontcvt.8 b/usr.bin/vtfontcvt/vtfontcvt.8 index 451f8134181b..a059f7589242 100644 --- a/usr.bin/vtfontcvt/vtfontcvt.8 +++ b/usr.bin/vtfontcvt/vtfontcvt.8 @@ -1,89 +1,89 @@ -.\" Copyright (c) 2014 The FreeBSD Foundation. All rights reserved. +.\" Copyright (c) 2014 The FreeBSD Foundation. .\" .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd August 2, 2020 .Dt VTFONTCVT 8 .Os .Sh NAME .Nm vtfontcvt .Nd "convert font files for use by the video console" .Sh SYNOPSIS .Nm .Op Fl n .Op Fl f Ar font Ns | Ns Ar source Ns | Ns Ar compressed-source .Op Fl h Ar height .Fl o Ar output_file .Op Fl v .Op Fl w Ar width .Ar normal_font .Op Ar bold_font .Sh DESCRIPTION The .Nm utility reads source font files in either BDF or Unifont HEX format and outputs a binary font file, C source, or C source with font data compressed by using LZ4 compression method. The output in C source is intended to be used to embed the font into program binary. The binary font file is for use by .Xr vt 4 . HEX format files must have the file extension .Pa .hex . .Pp The following options are available: .Bl -tag -width "12345678" .It Fl f Ar font Ns | Ns Ar source Ns | Ns Ar compressed-source Specify the output format. The default is binary .Ar font file. .It Fl h Ar height Set font height. The default is 16. Font height is set automatically for BDF files and for HEX files that have a .Ql # Height: Ar height comment before any font data. .It Fl n Do not apply output filtering with C source output. .It Fl o Ar output_file Specify the name for the output file. .It Fl v Display verbose statistics about the converted font. .It Fl w Ar width Set font width. The default is 8. Font width is set automatically for BDF files and for HEX files that have a .Ql # Width: Ar width comment before any font data. .El .Sh SEE ALSO .Xr vidcontrol 1 , .Xr vt 4 .Sh HISTORY The .Nm utility first appeared in .Fx 10.1 . diff --git a/usr.bin/vtfontcvt/vtfontcvt.c b/usr.bin/vtfontcvt/vtfontcvt.c index b7587f5881ec..d236ff6a3e2f 100644 --- a/usr.bin/vtfontcvt/vtfontcvt.c +++ b/usr.bin/vtfontcvt/vtfontcvt.c @@ -1,1124 +1,1123 @@ /*- * Copyright (c) 2009, 2014 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VFNT_MAXGLYPHS 131072 #define VFNT_MAXDIMENSION 128 static unsigned int width = 8, wbytes, height = 16; struct glyph { TAILQ_ENTRY(glyph) g_list; SLIST_ENTRY(glyph) g_hash; uint8_t *g_data; unsigned int g_index; }; #define FONTCVT_NHASH 4096 TAILQ_HEAD(glyph_list, glyph); static SLIST_HEAD(, glyph) glyph_hash[FONTCVT_NHASH]; static struct glyph_list glyphs[VFNT_MAPS] = { TAILQ_HEAD_INITIALIZER(glyphs[0]), TAILQ_HEAD_INITIALIZER(glyphs[1]), TAILQ_HEAD_INITIALIZER(glyphs[2]), TAILQ_HEAD_INITIALIZER(glyphs[3]), }; static unsigned int glyph_total, glyph_count[4], glyph_unique, glyph_dupe; struct mapping { TAILQ_ENTRY(mapping) m_list; unsigned int m_char; unsigned int m_length; struct glyph *m_glyph; }; TAILQ_HEAD(mapping_list, mapping); static struct mapping_list maps[VFNT_MAPS] = { TAILQ_HEAD_INITIALIZER(maps[0]), TAILQ_HEAD_INITIALIZER(maps[1]), TAILQ_HEAD_INITIALIZER(maps[2]), TAILQ_HEAD_INITIALIZER(maps[3]), }; static unsigned int mapping_total, map_count[4], map_folded_count[4], mapping_unique, mapping_dupe; enum output_format { VT_FONT, /* default */ VT_C_SOURCE, /* C source for built in fonts */ VT_C_COMPRESSED /* C source with compressed font data */ }; struct whitelist { uint32_t c; uint32_t len; }; /* * Compressed font glyph list. To be used with boot loader, we need to have * ascii set and box drawing chars. */ static struct whitelist c_list[] = { { .c = 0, .len = 0 }, /* deault char */ { .c = 0x20, .len = 0x5f }, { .c = 0x2500, .len = 0 }, /* single frame */ { .c = 0x2502, .len = 0 }, { .c = 0x250c, .len = 0 }, { .c = 0x2510, .len = 0 }, { .c = 0x2514, .len = 0 }, { .c = 0x2518, .len = 0 }, { .c = 0x2550, .len = 1 }, /* double frame */ { .c = 0x2554, .len = 0 }, { .c = 0x2557, .len = 0 }, { .c = 0x255a, .len = 0 }, { .c = 0x255d, .len = 0 }, }; /* * Uncompressed source. For x86 we need cp437 so the vga text mode * can program font into the vga card. */ static struct whitelist s_list[] = { { .c = 0, .len = 0 }, /* deault char */ { .c = 0x20, .len = 0x5f }, /* ascii set */ { .c = 0xA0, .len = 0x5f }, /* latin 1 */ { .c = 0x0192, .len = 0 }, { .c = 0x0332, .len = 0 }, /* composing lower line */ { .c = 0x0393, .len = 0 }, { .c = 0x0398, .len = 0 }, { .c = 0x03A3, .len = 0 }, { .c = 0x03A6, .len = 0 }, { .c = 0x03A9, .len = 0 }, { .c = 0x03B1, .len = 1 }, { .c = 0x03B4, .len = 0 }, { .c = 0x03C0, .len = 0 }, { .c = 0x03C3, .len = 0 }, { .c = 0x03C4, .len = 0 }, { .c = 0x207F, .len = 0 }, { .c = 0x20A7, .len = 0 }, { .c = 0x2205, .len = 0 }, { .c = 0x220A, .len = 0 }, { .c = 0x2219, .len = 1 }, { .c = 0x221E, .len = 0 }, { .c = 0x2229, .len = 0 }, { .c = 0x2248, .len = 0 }, { .c = 0x2261, .len = 0 }, { .c = 0x2264, .len = 1 }, { .c = 0x2310, .len = 0 }, { .c = 0x2320, .len = 1 }, { .c = 0x2500, .len = 0 }, { .c = 0x2502, .len = 0 }, { .c = 0x250C, .len = 0 }, { .c = 0x2510, .len = 0 }, { .c = 0x2514, .len = 0 }, { .c = 0x2518, .len = 0 }, { .c = 0x251C, .len = 0 }, { .c = 0x2524, .len = 0 }, { .c = 0x252C, .len = 0 }, { .c = 0x2534, .len = 0 }, { .c = 0x253C, .len = 0 }, { .c = 0x2550, .len = 0x1c }, { .c = 0x2580, .len = 0 }, { .c = 0x2584, .len = 0 }, { .c = 0x2588, .len = 0 }, { .c = 0x258C, .len = 0 }, { .c = 0x2590, .len = 3 }, { .c = 0x25A0, .len = 0 }, }; static bool filter = true; static enum output_format format = VT_FONT; /* Type for write callback. */ typedef size_t (*vt_write)(const void *, size_t, size_t, FILE *); static uint8_t *uncompressed; static void usage(void) { (void)fprintf(stderr, "usage: vtfontcvt " "[-n] [-f font|source|compressed-source] [-w width] " "[-h height]\n\t[-v] normal.bdf [bold.bdf] out.fnt\n"); exit(1); } static void * xmalloc(size_t size) { void *m; if ((m = calloc(1, size)) == NULL) errx(1, "memory allocation failure"); return (m); } static int add_mapping(struct glyph *gl, unsigned int c, unsigned int map_idx) { struct mapping *mp, *mp_temp; struct mapping_list *ml; mapping_total++; mp = xmalloc(sizeof *mp); mp->m_char = c; mp->m_glyph = gl; mp->m_length = 0; ml = &maps[map_idx]; if (TAILQ_LAST(ml, mapping_list) == NULL || TAILQ_LAST(ml, mapping_list)->m_char < c) { /* Common case: empty list or new char at end of list. */ TAILQ_INSERT_TAIL(ml, mp, m_list); } else { /* Find insertion point for char; cannot be at end. */ TAILQ_FOREACH(mp_temp, ml, m_list) { if (mp_temp->m_char >= c) { TAILQ_INSERT_BEFORE(mp_temp, mp, m_list); break; } } } map_count[map_idx]++; mapping_unique++; return (0); } static int dedup_mapping(unsigned int map_idx) { struct mapping *mp_bold, *mp_normal, *mp_temp; unsigned normal_map_idx = map_idx - VFNT_MAP_BOLD; assert(map_idx == VFNT_MAP_BOLD || map_idx == VFNT_MAP_BOLD_RIGHT); mp_normal = TAILQ_FIRST(&maps[normal_map_idx]); TAILQ_FOREACH_SAFE(mp_bold, &maps[map_idx], m_list, mp_temp) { while (mp_normal->m_char < mp_bold->m_char) mp_normal = TAILQ_NEXT(mp_normal, m_list); if (mp_bold->m_char != mp_normal->m_char) errx(1, "Character %u not in normal font!", mp_bold->m_char); if (mp_bold->m_glyph != mp_normal->m_glyph) continue; /* No mapping is needed if it's equal to the normal mapping. */ TAILQ_REMOVE(&maps[map_idx], mp_bold, m_list); free(mp_bold); mapping_dupe++; } return (0); } static struct glyph * add_glyph(const uint8_t *bytes, unsigned int map_idx, int fallback) { struct glyph *gl; int hash; glyph_total++; glyph_count[map_idx]++; /* Return existing glyph if we have an identical one. */ hash = fnv_32_buf(bytes, wbytes * height, FNV1_32_INIT) % FONTCVT_NHASH; SLIST_FOREACH(gl, &glyph_hash[hash], g_hash) { if (memcmp(gl->g_data, bytes, wbytes * height) == 0) { glyph_dupe++; return (gl); } } /* Allocate new glyph. */ gl = xmalloc(sizeof *gl); gl->g_data = xmalloc(wbytes * height); memcpy(gl->g_data, bytes, wbytes * height); if (fallback) TAILQ_INSERT_HEAD(&glyphs[map_idx], gl, g_list); else TAILQ_INSERT_TAIL(&glyphs[map_idx], gl, g_list); SLIST_INSERT_HEAD(&glyph_hash[hash], gl, g_hash); glyph_unique++; if (glyph_unique > VFNT_MAXGLYPHS) errx(1, "too many glyphs (%u)", glyph_unique); return (gl); } static bool check_whitelist(unsigned c) { struct whitelist *w = NULL; int i, n = 0; if (filter == false) return (true); if (format == VT_C_SOURCE) { w = s_list; n = sizeof(s_list) / sizeof(s_list[0]); } if (format == VT_C_COMPRESSED) { w = c_list; n = sizeof(c_list) / sizeof(c_list[0]); } if (w == NULL) return (true); for (i = 0; i < n; i++) { if (c >= w[i].c && c <= w[i].c + w[i].len) return (true); } return (false); } static int add_char(unsigned curchar, unsigned map_idx, uint8_t *bytes, uint8_t *bytes_r) { struct glyph *gl; /* Prevent adding two glyphs for 0xFFFD */ if (curchar == 0xFFFD) { if (map_idx < VFNT_MAP_BOLD) gl = add_glyph(bytes, 0, 1); } else if (filter == false || curchar >= 0x20) { gl = add_glyph(bytes, map_idx, 0); if (add_mapping(gl, curchar, map_idx) != 0) return (1); if (bytes_r != NULL) { gl = add_glyph(bytes_r, map_idx + 1, 0); if (add_mapping(gl, curchar, map_idx + 1) != 0) return (1); } } return (0); } /* * Right-shift glyph row. */ static void rshift_row(uint8_t *buf, size_t len, size_t shift) { ssize_t i, off_byte = shift / 8; size_t off_bit = shift % 8; if (shift == 0) return; for (i = len - 1; i >= 0; i--) buf[i] = (i >= off_byte ? buf[i - off_byte] >> off_bit : 0) | (i > off_byte ? buf[i - off_byte - 1] << (8 - off_bit) : 0); } /* * Split double-width characters into left and right half. Single-width * characters in _left_ only. */ static int split_row(uint8_t *left, uint8_t *right, uint8_t *line, size_t w) { size_t s, i; s = wbytes * 8 - width; memcpy(left, line, wbytes); *(left + wbytes - 1) &= 0xFF << s; if (w > width) { /* Double-width character. */ uint8_t t; for (i = 0; i < wbytes; i++) { t = *(line + wbytes + i - 1); t <<= 8 - s; t |= *(line + wbytes + i) >> s; *(right + i) = t; } *(right + wbytes - 1) &= 0xFF << s; } return (0); } static void set_height(int h) { if (h <= 0 || h > VFNT_MAXDIMENSION) errx(1, "invalid height %d", h); height = h; } static void set_width(int w) { if (w <= 0 || w > VFNT_MAXDIMENSION) errx(1, "invalid width %d", w); width = w; wbytes = howmany(width, 8); } static int parse_bdf(FILE *fp, unsigned int map_idx) { char *ln, *p; size_t length; uint8_t *line, *bytes, *bytes_r; unsigned int curchar = 0, i, j, linenum = 0, bbwbytes; int bbw, bbh, bbox, bboy; /* Glyph bounding box. */ int fbbw = 0, fbbh, fbbox, fbboy; /* Font bounding box. */ int dwidth = 0, dwy = 0; int rv = -1; char spc = '\0'; /* * Step 1: Parse FONT logical font descriptor and FONTBOUNDINGBOX * bounding box. */ while ((ln = fgetln(fp, &length)) != NULL) { linenum++; ln[length - 1] = '\0'; if (strncmp(ln, "FONT ", 5) == 0) { p = ln + 5; i = 0; while ((p = strchr(p, '-')) != NULL) { p++; i++; if (i == 11) { spc = *p; break; } } } else if (strncmp(ln, "FONTBOUNDINGBOX ", 16) == 0) { if (sscanf(ln + 16, "%d %d %d %d", &fbbw, &fbbh, &fbbox, &fbboy) != 4) errx(1, "invalid FONTBOUNDINGBOX at line %u", linenum); set_width(fbbw); set_height(fbbh); break; } } if (fbbw == 0) errx(1, "broken font header"); if (spc != 'c' && spc != 'C') errx(1, "font spacing \"C\" (character cell) required"); /* Step 2: Validate DWIDTH (Device Width) of all glyphs. */ while ((ln = fgetln(fp, &length)) != NULL) { linenum++; ln[length - 1] = '\0'; if (strncmp(ln, "DWIDTH ", 7) == 0) { if (sscanf(ln + 7, "%d %d", &dwidth, &dwy) != 2) errx(1, "invalid DWIDTH at line %u", linenum); if (dwy != 0 || (dwidth != fbbw && dwidth * 2 != fbbw)) errx(1, "bitmap with unsupported DWIDTH %d %d at line %u", dwidth, dwy, linenum); if (dwidth < fbbw) set_width(dwidth); } } /* Step 3: Restart at the beginning of the file and read glyph data. */ dwidth = bbw = bbh = 0; rewind(fp); linenum = 0; bbwbytes = 0; /* GCC 4.2.1 "may be used uninitialized" workaround. */ bytes = xmalloc(wbytes * height); bytes_r = xmalloc(wbytes * height); line = xmalloc(wbytes * 2); while ((ln = fgetln(fp, &length)) != NULL) { linenum++; ln[length - 1] = '\0'; if (strncmp(ln, "ENCODING ", 9) == 0) { curchar = atoi(ln + 9); } else if (strncmp(ln, "DWIDTH ", 7) == 0) { dwidth = atoi(ln + 7); } else if (strncmp(ln, "BBX ", 4) == 0) { if (sscanf(ln + 4, "%d %d %d %d", &bbw, &bbh, &bbox, &bboy) != 4) errx(1, "invalid BBX at line %u", linenum); if (bbw < 1 || bbh < 1 || bbw > fbbw || bbh > fbbh || bbox < fbbox || bboy < fbboy || bbh + bboy > fbbh + fbboy) errx(1, "broken bitmap with BBX %d %d %d %d at line %u", bbw, bbh, bbox, bboy, linenum); bbwbytes = howmany(bbw, 8); } else if (strncmp(ln, "BITMAP", 6) == 0 && (ln[6] == ' ' || ln[6] == '\0')) { if (dwidth == 0 || bbw == 0 || bbh == 0) errx(1, "broken char header at line %u!", linenum); memset(bytes, 0, wbytes * height); memset(bytes_r, 0, wbytes * height); /* * Assume that the next _bbh_ lines are bitmap data. * ENDCHAR is allowed to terminate the bitmap * early but is not otherwise checked; any extra data * is ignored. */ for (i = (fbbh + fbboy) - (bbh + bboy); i < (unsigned int)((fbbh + fbboy) - bboy); i++) { if ((ln = fgetln(fp, &length)) == NULL) errx(1, "unexpected EOF"); linenum++; ln[length - 1] = '\0'; if (strcmp(ln, "ENDCHAR") == 0) break; if (strlen(ln) < bbwbytes * 2) errx(1, "broken bitmap at line %u", linenum); memset(line, 0, wbytes * 2); for (j = 0; j < bbwbytes; j++) { unsigned int val; if (sscanf(ln + j * 2, "%2x", &val) == 0) break; *(line + j) = (uint8_t)val; } rshift_row(line, wbytes * 2, bbox - fbbox); rv = split_row(bytes + i * wbytes, bytes_r + i * wbytes, line, dwidth); if (rv != 0) goto out; } if (check_whitelist(curchar) == true) { rv = add_char(curchar, map_idx, bytes, dwidth > (int)width ? bytes_r : NULL); if (rv != 0) goto out; } dwidth = bbw = bbh = 0; } } out: free(bytes); free(bytes_r); free(line); return (rv); } static int parse_hex(FILE *fp, unsigned int map_idx) { char *ln, *p; size_t length; uint8_t *bytes = NULL, *bytes_r = NULL, *line = NULL; unsigned curchar = 0, gwidth, gwbytes, i, j, chars_per_row; int rv = 0; while ((ln = fgetln(fp, &length)) != NULL) { ln[length - 1] = '\0'; if (strncmp(ln, "# Height: ", 10) == 0) { if (bytes != NULL) errx(1, "malformed input: Height tag after font data"); set_height(atoi(ln + 10)); } else if (strncmp(ln, "# Width: ", 9) == 0) { if (bytes != NULL) errx(1, "malformed input: Width tag after font data"); set_width(atoi(ln + 9)); } else if (sscanf(ln, "%6x:", &curchar)) { if (bytes == NULL) { bytes = xmalloc(wbytes * height); bytes_r = xmalloc(wbytes * height); line = xmalloc(wbytes * 2); } /* ln is guaranteed to have a colon here. */ p = strchr(ln, ':') + 1; chars_per_row = strlen(p) / height; if (chars_per_row < wbytes * 2) errx(1, "malformed input: broken bitmap, character %06x", curchar); gwidth = width * 2; gwbytes = howmany(gwidth, 8); if (chars_per_row < gwbytes * 2 || gwidth <= 8) { gwidth = width; /* Single-width character. */ gwbytes = wbytes; } for (i = 0; i < height; i++) { for (j = 0; j < gwbytes; j++) { unsigned int val; if (sscanf(p + j * 2, "%2x", &val) == 0) break; *(line + j) = (uint8_t)val; } rv = split_row(bytes + i * wbytes, bytes_r + i * wbytes, line, gwidth); if (rv != 0) goto out; p += gwbytes * 2; } if (check_whitelist(curchar) == true) { rv = add_char(curchar, map_idx, bytes, gwidth != width ? bytes_r : NULL); if (rv != 0) goto out; } } } out: free(bytes); free(bytes_r); free(line); return (rv); } static int parse_file(const char *filename, unsigned int map_idx) { FILE *fp; size_t len; int rv; fp = fopen(filename, "r"); if (fp == NULL) { perror(filename); return (1); } len = strlen(filename); if (len > 4 && strcasecmp(filename + len - 4, ".hex") == 0) rv = parse_hex(fp, map_idx); else rv = parse_bdf(fp, map_idx); fclose(fp); return (rv); } static void number_glyphs(void) { struct glyph *gl; unsigned int i, idx = 0; for (i = 0; i < VFNT_MAPS; i++) TAILQ_FOREACH(gl, &glyphs[i], g_list) gl->g_index = idx++; } /* Note we only deal with byte stream here. */ static size_t write_glyph_source(const void *ptr, size_t size, size_t nitems, FILE *stream) { const uint8_t *data = ptr; size_t i; size *= nitems; for (i = 0; i < size; i++) { if ((i % wbytes) == 0) { if (fprintf(stream, "\n") < 0) return (0); } if (fprintf(stream, "0x%02x, ", data[i]) < 0) return (0); } if (fprintf(stream, "\n") < 0) nitems = 0; return (nitems); } /* Write to buffer */ static size_t write_glyph_buf(const void *ptr, size_t size, size_t nitems, FILE *stream __unused) { static size_t index = 0; size *= nitems; (void) memmove(uncompressed + index, ptr, size); index += size; return (nitems); } static int write_glyphs(FILE *fp, vt_write cb) { struct glyph *gl; unsigned int i; for (i = 0; i < VFNT_MAPS; i++) { TAILQ_FOREACH(gl, &glyphs[i], g_list) if (cb(gl->g_data, wbytes * height, 1, fp) != 1) return (1); } return (0); } static void fold_mappings(unsigned int map_idx) { struct mapping_list *ml = &maps[map_idx]; struct mapping *mn, *mp, *mbase; mp = mbase = TAILQ_FIRST(ml); for (mp = mbase = TAILQ_FIRST(ml); mp != NULL; mp = mn) { mn = TAILQ_NEXT(mp, m_list); if (mn != NULL && mn->m_char == mp->m_char + 1 && mn->m_glyph->g_index == mp->m_glyph->g_index + 1) continue; mbase->m_length = mp->m_char - mbase->m_char + 1; mbase = mp = mn; map_folded_count[map_idx]++; } } static int write_mappings(FILE *fp, unsigned int map_idx) { struct mapping_list *ml = &maps[map_idx]; struct mapping *mp; vfnt_map_t fm; unsigned int i = 0, j = 0; TAILQ_FOREACH(mp, ml, m_list) { j++; if (mp->m_length > 0) { i += mp->m_length; fm.vfm_src = htobe32(mp->m_char); fm.vfm_dst = htobe16(mp->m_glyph->g_index); fm.vfm_len = htobe16(mp->m_length - 1); if (fwrite(&fm, sizeof fm, 1, fp) != 1) return (1); } } assert(i == j); return (0); } static int write_source_mappings(FILE *fp, unsigned int map_idx) { struct mapping_list *ml = &maps[map_idx]; struct mapping *mp; unsigned int i = 0, j = 0; TAILQ_FOREACH(mp, ml, m_list) { j++; if (mp->m_length > 0) { i += mp->m_length; if (fprintf(fp, "\t{ 0x%08x, 0x%04x, 0x%04x },\n", mp->m_char, mp->m_glyph->g_index, mp->m_length - 1) < 0) return (1); } } assert(i == j); return (0); } static int write_fnt(const char *filename) { FILE *fp; struct font_header fh = { .fh_magic = FONT_HEADER_MAGIC, }; fp = fopen(filename, "wb"); if (fp == NULL) { perror(filename); return (1); } fh.fh_width = width; fh.fh_height = height; fh.fh_glyph_count = htobe32(glyph_unique); fh.fh_map_count[0] = htobe32(map_folded_count[0]); fh.fh_map_count[1] = htobe32(map_folded_count[1]); fh.fh_map_count[2] = htobe32(map_folded_count[2]); fh.fh_map_count[3] = htobe32(map_folded_count[3]); if (fwrite(&fh, sizeof(fh), 1, fp) != 1) { perror(filename); fclose(fp); return (1); } if (write_glyphs(fp, &fwrite) != 0 || write_mappings(fp, VFNT_MAP_NORMAL) != 0 || write_mappings(fp, VFNT_MAP_NORMAL_RIGHT) != 0 || write_mappings(fp, VFNT_MAP_BOLD) != 0 || write_mappings(fp, VFNT_MAP_BOLD_RIGHT) != 0) { perror(filename); fclose(fp); return (1); } fclose(fp); return (0); } static int write_fnt_source(bool lz4, const char *filename) { FILE *fp; int rv = 1; size_t uncompressed_size = wbytes * height * glyph_unique; size_t compressed_size = uncompressed_size; uint8_t *compressed = NULL; fp = fopen(filename, "w"); if (fp == NULL) { perror(filename); return (1); } if (lz4 == true) { uncompressed = xmalloc(uncompressed_size); compressed = xmalloc(uncompressed_size); } if (fprintf(fp, "/* Generated %ux%u console font source. */\n\n", width, height) < 0) goto done; if (fprintf(fp, "#include \n") < 0) goto done; if (fprintf(fp, "#include \n") < 0) goto done; if (fprintf(fp, "#include \n\n") < 0) goto done; /* Write font bytes. */ if (fprintf(fp, "static uint8_t FONTDATA_%ux%u[] = {\n", width, height) < 0) goto done; if (lz4 == true) { if (write_glyphs(fp, &write_glyph_buf) != 0) goto done; compressed_size = lz4_compress(uncompressed, compressed, uncompressed_size, compressed_size, 0); if (write_glyph_source(compressed, compressed_size, 1, fp) != 1) goto done; free(uncompressed); free(compressed); } else { if (write_glyphs(fp, &write_glyph_source) != 0) goto done; } if (fprintf(fp, "};\n\n") < 0) goto done; /* Write font maps. */ if (!TAILQ_EMPTY(&maps[VFNT_MAP_NORMAL])) { if (fprintf(fp, "static vfnt_map_t " "FONTMAP_NORMAL_%ux%u[] = {\n", width, height) < 0) goto done; if (write_source_mappings(fp, VFNT_MAP_NORMAL) != 0) goto done; if (fprintf(fp, "};\n\n") < 0) goto done; } if (!TAILQ_EMPTY(&maps[VFNT_MAP_NORMAL_RIGHT])) { if (fprintf(fp, "static vfnt_map_t " "FONTMAP_NORMAL_RH_%ux%u[] = {\n", width, height) < 0) goto done; if (write_source_mappings(fp, VFNT_MAP_NORMAL_RIGHT) != 0) goto done; if (fprintf(fp, "};\n\n") < 0) goto done; } if (!TAILQ_EMPTY(&maps[VFNT_MAP_BOLD])) { if (fprintf(fp, "static vfnt_map_t " "FONTMAP_BOLD_%ux%u[] = {\n", width, height) < 0) goto done; if (write_source_mappings(fp, VFNT_MAP_BOLD) != 0) goto done; if (fprintf(fp, "};\n\n") < 0) goto done; } if (!TAILQ_EMPTY(&maps[VFNT_MAP_BOLD_RIGHT])) { if (fprintf(fp, "static vfnt_map_t " "FONTMAP_BOLD_RH_%ux%u[] = {\n", width, height) < 0) goto done; if (write_source_mappings(fp, VFNT_MAP_BOLD_RIGHT) != 0) goto done; if (fprintf(fp, "};\n\n") < 0) goto done; } /* Write struct font. */ if (fprintf(fp, "struct vt_font font_%ux%u = {\n", width, height) < 0) goto done; if (fprintf(fp, "\t.vf_map\t= {\n") < 0) goto done; if (TAILQ_EMPTY(&maps[VFNT_MAP_NORMAL])) { if (fprintf(fp, "\t\t\tNULL,\n") < 0) goto done; } else { if (fprintf(fp, "\t\t\tFONTMAP_NORMAL_%ux%u,\n", width, height) < 0) goto done; } if (TAILQ_EMPTY(&maps[VFNT_MAP_NORMAL_RIGHT])) { if (fprintf(fp, "\t\t\tNULL,\n") < 0) goto done; } else { if (fprintf(fp, "\t\t\tFONTMAP_NORMAL_RH_%ux%u,\n", width, height) < 0) goto done; } if (TAILQ_EMPTY(&maps[VFNT_MAP_BOLD])) { if (fprintf(fp, "\t\t\tNULL,\n") < 0) goto done; } else { if (fprintf(fp, "\t\t\tFONTMAP_BOLD_%ux%u,\n", width, height) < 0) goto done; } if (TAILQ_EMPTY(&maps[VFNT_MAP_BOLD_RIGHT])) { if (fprintf(fp, "\t\t\tNULL\n") < 0) goto done; } else { if (fprintf(fp, "\t\t\tFONTMAP_BOLD_RH_%ux%u\n", width, height) < 0) goto done; } if (fprintf(fp, "\t\t},\n") < 0) goto done; if (lz4 == true) { if (fprintf(fp, "\t.vf_bytes\t= NULL,\n") < 0) goto done; } else { if (fprintf(fp, "\t.vf_bytes\t= FONTDATA_%ux%u,\n", width, height) < 0) { goto done; } } if (fprintf(fp, "\t.vf_width\t= %u,\n", width) < 0) goto done; if (fprintf(fp, "\t.vf_height\t= %u,\n", height) < 0) goto done; if (fprintf(fp, "\t.vf_map_count\t= { %u, %u, %u, %u }\n", map_folded_count[0], map_folded_count[1], map_folded_count[2], map_folded_count[3]) < 0) { goto done; } if (fprintf(fp, "};\n\n") < 0) goto done; /* Write bitmap data. */ if (fprintf(fp, "vt_font_bitmap_data_t font_data_%ux%u = {\n", width, height) < 0) goto done; if (fprintf(fp, "\t.vfbd_width\t= %u,\n", width) < 0) goto done; if (fprintf(fp, "\t.vfbd_height\t= %u,\n", height) < 0) goto done; if (lz4 == true) { if (fprintf(fp, "\t.vfbd_compressed_size\t= %zu,\n", compressed_size) < 0) { goto done; } if (fprintf(fp, "\t.vfbd_uncompressed_size\t= %zu,\n", uncompressed_size) < 0) { goto done; } if (fprintf(fp, "\t.vfbd_compressed_data\t= FONTDATA_%ux%u,\n", width, height) < 0) { goto done; } } else { if (fprintf(fp, "\t.vfbd_compressed_size\t= 0,\n") < 0) goto done; if (fprintf(fp, "\t.vfbd_uncompressed_size\t= %zu,\n", uncompressed_size) < 0) { goto done; } if (fprintf(fp, "\t.vfbd_compressed_data\t= NULL,\n") < 0) goto done; } if (fprintf(fp, "\t.vfbd_font = &font_%ux%u\n", width, height) < 0) goto done; if (fprintf(fp, "};\n") < 0) goto done; rv = 0; done: if (rv != 0) perror(filename); fclose(fp); return (0); } static void print_font_info(void) { printf( "Statistics:\n" "- width: %6u\n" "- height: %6u\n" "- glyph_total: %6u\n" "- glyph_normal: %6u\n" "- glyph_normal_right: %6u\n" "- glyph_bold: %6u\n" "- glyph_bold_right: %6u\n" "- glyph_unique: %6u\n" "- glyph_dupe: %6u\n" "- mapping_total: %6u\n" "- mapping_normal: %6u\n" "- mapping_normal_folded: %6u\n" "- mapping_normal_right: %6u\n" "- mapping_normal_right_folded: %6u\n" "- mapping_bold: %6u\n" "- mapping_bold_folded: %6u\n" "- mapping_bold_right: %6u\n" "- mapping_bold_right_folded: %6u\n" "- mapping_unique: %6u\n" "- mapping_dupe: %6u\n", width, height, glyph_total, glyph_count[0], glyph_count[1], glyph_count[2], glyph_count[3], glyph_unique, glyph_dupe, mapping_total, map_count[0], map_folded_count[0], map_count[1], map_folded_count[1], map_count[2], map_folded_count[2], map_count[3], map_folded_count[3], mapping_unique, mapping_dupe); } int main(int argc, char *argv[]) { int ch, verbose = 0, rv = 0; char *outfile = NULL; assert(sizeof(struct font_header) == 32); assert(sizeof(vfnt_map_t) == 8); while ((ch = getopt(argc, argv, "nf:h:vw:o:")) != -1) { switch (ch) { case 'f': if (strcmp(optarg, "font") == 0) format = VT_FONT; else if (strcmp(optarg, "source") == 0) format = VT_C_SOURCE; else if (strcmp(optarg, "compressed-source") == 0) format = VT_C_COMPRESSED; else errx(1, "Invalid format: %s", optarg); break; case 'h': height = atoi(optarg); break; case 'n': filter = false; break; case 'o': outfile = optarg; break; case 'v': verbose = 1; break; case 'w': width = atoi(optarg); break; case '?': default: usage(); } } argc -= optind; argv += optind; if (outfile == NULL && (argc < 2 || argc > 3)) usage(); if (outfile == NULL) { outfile = argv[argc - 1]; argc--; } set_width(width); set_height(height); if (parse_file(argv[0], VFNT_MAP_NORMAL) != 0) return (1); argc--; argv++; if (argc == 1) { if (parse_file(argv[0], VFNT_MAP_BOLD) != 0) return (1); argc--; argv++; } number_glyphs(); dedup_mapping(VFNT_MAP_BOLD); dedup_mapping(VFNT_MAP_BOLD_RIGHT); fold_mappings(0); fold_mappings(1); fold_mappings(2); fold_mappings(3); switch (format) { case VT_FONT: rv = write_fnt(outfile); break; case VT_C_SOURCE: rv = write_fnt_source(false, outfile); break; case VT_C_COMPRESSED: rv = write_fnt_source(true, outfile); break; } if (verbose) print_font_info(); return (rv); }