Rewrite the ttf2font tool to generate RPL fonts

An RPL font is an RPL object containing font data.
There are two kinds of font: dense and sparse (see code for details).
Internally, there is a third kind, designed to read built-in DM42 fonts.

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2022-10-22 23:18:14 +02:00
parent a6c3d14e92
commit 42683bbe80
6 changed files with 724 additions and 527 deletions

46
src/font.h Normal file
View file

@ -0,0 +1,46 @@
#ifndef FONT_H
#define FONT_H
// ****************************************************************************
// font.h DB48X project
// ****************************************************************************
//
// File Description:
//
// RPL font objects
//
//
//
//
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the terms outlined in LICENSE.txt
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms outlined in the LICENSE.txt file
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************
#include "object.h"
struct font : object
{
};
struct sparse_font : font
{
};
struct dense_font : font
{
};
#endif // FONT_H

View file

@ -78,6 +78,11 @@ ALIASED_OP(div, "\x80", "/") // ÷
NAMED(neg, "Negate")
NAMED(inv, "Invert")
// Special objects
ID(dense_font)
ID(sparse_font)
#undef ID
#undef OP
#undef CMD

View file

@ -34,6 +34,7 @@
#include "decimal-32.h"
#include "decimal-64.h"
#include "decimal128.h"
#include "font.h"
#include "integer.h"
#include "parser.h"
#include "renderer.h"

View file

@ -27,10 +27,12 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#******************************************************************************
SOURCES=ttf2font.c
SOURCES=ttf2font.cpp
PRODUCTS=ttf2font.exe
PKGCONFIGS=freetype2
INCLUDES=../../src
MIQ=../../recorder/make-it-quick/
include $(MIQ)rules.mk

View file

@ -1,526 +0,0 @@
// ****************************************************************************
// ttf2font.c DB48X project
// ****************************************************************************
//
// File Description:
//
// This converts a TTF file to an HP48 bitmap
//
// This is freely inspired by ttf2RasterFont in the wp43s project,
// but simplified to generate the simpler data structures in newRPL
//
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the GNU General Public License v3
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with DB48X. If not, see <https://www.gnu.org/licenses/>.
// ****************************************************************************
#include <ft2build.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include FT_FREETYPE_H
typedef const char *cstring;
int verbose = 0;
const char *getErrorMessage(FT_Error err)
// ----------------------------------------------------------------------------
// Error messages from freetype2
// ----------------------------------------------------------------------------
{
#undef __FTERRORS_H__
#define FT_ERROR_START_LIST switch (err) {
#define FT_ERRORDEF(e, v, s) case e: return s;
#define FT_ERROR_END_LIST }
#include FT_ERRORS_H
return "(Unknown error)";
}
int sortCharCodes(const void *a, const void *b)
// ----------------------------------------------------------------------------
// Sort according to the char codes
// ----------------------------------------------------------------------------
{
return *(FT_ULong *) a - *(FT_ULong *) b;
}
void processFont(cstring fontName,
cstring ttfName,
cstring cSourceName,
int fontSize,
int threshold)
// ----------------------------------------------------------------------------
// Process a font and generate the C source file from it
// ----------------------------------------------------------------------------
/* Font structure
*
* 4-Bytes Prolog with size (Compatible with newRPL object format)
* 4-Bytes:
* 0xHHHHWWWW --> H=Font height, W=Total bitmap row width in bytes
* 4-Bytes:
* 2-Bytes: Offset in words from prolog to font bitmap
* 2-Bytes: Offset in words from prolog to width & offset table
* ------ Table of Unicode->glyph mapping -----
* 4-Byte ranges: 0xNNNNNOOO
* NNNNN=number of codes in this range,
* OOO=Index into width & offset table
* FFF being reserved for unmapped ranges
* ------ Table of width & offset
* 4-Bytes values: 0xWWWWOOOO
* WWWW: width in pixels of the glyph
* OOOO: Coordinate of the glyph within the bitmap
* ------ BITMAP
* Total number of bytes=height*row width in bytes
* ----- PADDING FOR WORD-ALIGNMENT
*/
{
FILE *output = fopen(cSourceName, "w");
if (!output)
{
fprintf(stderr, "Cannot open source file %s", cSourceName);
fprintf(stderr, "Error %d: %s", errno, strerror(errno));
exit(1);
}
// Initialize freetype2
FT_Library library;
FT_Error error = FT_Init_FreeType(&library);
if (error != FT_Err_Ok)
{
fprintf(stderr, "Error during freetype2 library initialisation.\n");
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
exit(1);
}
// Open the font face
FT_Face face;
error = FT_New_Face(library, ttfName, 0, &face);
if (error != FT_Err_Ok)
{
fprintf(stderr, "Error during face creation from file %s\n", ttfName);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
FT_Done_FreeType(library);
exit(1);
}
// Set font size - Formula lifted from wp43s
int unitsPerEM = face->units_per_EM;
int pixelSize = unitsPerEM == 1024 ? 32 : 50;
int baseSize = unitsPerEM / pixelSize;
int fontHeightPixels = fontSize ? fontSize : baseSize;
#define SCALED(x) ((x) * fontHeightPixels / baseSize)
error = FT_Set_Pixel_Sizes(face, 0, fontHeightPixels);
if (error != FT_Err_Ok)
{
fprintf(stderr, "Error setting pixel size from file %s\n", ttfName);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
FT_Done_FreeType(library);
exit(1);
}
// Face scaling
unsigned bitmapWidth = 0;
int ascend = face->ascender;
int descend = face->descender;
int faceHeight = ascend - descend;
int bitmapHeight = SCALED(faceHeight) / pixelSize;
int renderFlag = FT_LOAD_RENDER;
if (!threshold)
renderFlag |= FT_LOAD_TARGET_MONO;
// Count number of glyphs
unsigned numberOfGlyphs = face->num_glyphs;
FT_ULong *charCodes = calloc(numberOfGlyphs, sizeof(FT_ULong));
FT_ULong *curCharCode = charCodes;
FT_UInt glyphIndex = 0;
unsigned glyphCount = 0;
int minRowsBelow = 0;
for (FT_ULong charCode = FT_Get_First_Char(face, &glyphIndex);
glyphIndex;
charCode = FT_Get_Next_Char(face, charCode, &glyphIndex))
{
*curCharCode++ = charCode;
error = FT_Load_Glyph(face, glyphIndex, renderFlag);
if (error != FT_Err_Ok)
{
fprintf(stderr, "warning: failed to load glyph 0x%04lX\n", charCode);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
}
FT_Glyph_Metrics *m = &face->glyph->metrics;
FT_Bitmap *b = &face->glyph->bitmap;
int rowsGlyph = b->rows;
int rowsDescend = SCALED(descend) / pixelSize;
int rowsBelowGlyph = m->horiBearingY / 64 - rowsDescend - rowsGlyph;
if (rowsBelowGlyph < minRowsBelow)
minRowsBelow = rowsBelowGlyph;
bitmapWidth += face->glyph->metrics.horiAdvance / 64;
glyphCount++;
}
if (minRowsBelow < 0)
bitmapHeight -= minRowsBelow;
if (verbose || glyphCount > numberOfGlyphs)
{
fprintf(stderr, "Number of glyphs %u, glyph count %u, width %u\n",
numberOfGlyphs, glyphCount, bitmapWidth);
if (glyphCount > numberOfGlyphs)
exit(1);
}
// Sort them per unicode point
qsort(charCodes, glyphCount, sizeof(charCodes[0]), sortCharCodes);
// Round up the bitmap Width to full bytes
bitmapWidth = (bitmapWidth + 7) / 8 * 8;
// Allocate the global bitmap where we will draw glyphs and other structures
size_t bitmapSize = (bitmapWidth * bitmapHeight + 31) / 32;
uint32_t *bitmap = calloc(bitmapSize, sizeof(uint32_t));
uint32_t *offsets = calloc(numberOfGlyphs, sizeof(uint32_t));
uint32_t *ranges = calloc(numberOfGlyphs, sizeof(uint32_t));
if (verbose)
printf("Font bitmap width %u height %u size %u\n",
bitmapWidth, bitmapHeight, (unsigned) bitmapSize);
// Set the transform for the font
FT_Vector pen;
pen.x = 0;
pen.y = (fontHeightPixels - SCALED(face->ascender) / pixelSize) * 64;
FT_Set_Transform(face, NULL, &pen);
// Start on the left of the bitmap
uint32_t bitmapX = 0;
uint32_t *offset = offsets;
uint32_t *range = ranges;
uint32_t firstCode = 0;
uint32_t currentCode = 0;
uint32_t widthOffsetIndex = 0;
// Loop on all glyphs
for (unsigned g = 0; g < glyphCount; g++)
{
FT_ULong charCode = charCodes[g];
FT_UInt glyphIndex = FT_Get_Char_Index(face, charCode);
if (glyphIndex == 0)
{
fprintf(stderr, "Glyph 0x%04lX undefined\n", charCode);
continue;
}
error = FT_Load_Glyph(face, glyphIndex, renderFlag);
if (error != FT_Err_Ok)
{
fprintf(stderr, "warning: failed to load glyph 0x%04lX\n", charCode);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
continue;
}
// Get glyph metrics and bitmap
FT_Glyph_Metrics *m = &face->glyph->metrics;
FT_Bitmap *b = &face->glyph->bitmap;
// Columns in the glyph
int colsBeforeGlyph = m->horiBearingX / 64;
int colsGlyph = b->width;
int colsAfterGlyph = m->horiAdvance / 64 - colsBeforeGlyph - colsGlyph;
if (colsBeforeGlyph < 0)
{
colsGlyph += colsBeforeGlyph;
colsBeforeGlyph = 0;
}
// Rows in the glyph
int rowsAboveGlyph = SCALED(ascend) / pixelSize - m->horiBearingY / 64;
int rowsAboveSave = rowsAboveGlyph;
int rowsGlyph = b->rows;
int rowsDescend = SCALED(descend) / pixelSize;
int rowsBelowGlyph = m->horiBearingY / 64 - rowsDescend - rowsGlyph;
int rowsBelowSave = rowsBelowGlyph;
if (rowsAboveGlyph < 0)
{
rowsGlyph += rowsAboveGlyph;
rowsAboveGlyph = 0;
}
uint32_t glyphWidth = face->glyph->metrics.horiAdvance / 64;
if (verbose)
{
char utf8[4] = { 0 };
if (charCode < 0x80)
{
utf8[0] = charCode;
}
else if (charCode < 0x800)
{
utf8[0] = 0xC0 | (charCode >> 6);
utf8[1] = 0x80 | (charCode & 63);
}
else if (charCode < 0x10000)
{
utf8[0] = 0xE0 | (charCode >> 12);
utf8[1] = 0x80 | ((charCode >> 6) & 63);
utf8[2] = 0x80 | ((charCode >> 0) & 63);
}
else
{
utf8[0] = utf8[1] = utf8[2] = '-';
}
printf("Glyph %4lu '%s' width %u"
" Columns: %d %d %d"
" Rows: %d %d %d\n",
charCode,
utf8,
glyphWidth,
colsBeforeGlyph,
colsGlyph,
colsAfterGlyph,
rowsAboveSave,
rowsGlyph,
rowsBelowSave);
}
// Copy the bits from the bitmap
unsigned char *buffer = face->glyph->bitmap.buffer;
unsigned pitch = face->glyph->bitmap.pitch;
unsigned bwidth = face->glyph->bitmap.width;
for (int y = 0; y < rowsGlyph; y++)
{
int by = y + rowsAboveGlyph;
for (int x = 0; x < colsGlyph; x++)
{
int bit = 0;
if (threshold)
{
int bo = y * bwidth + x;
bit = buffer[bo] >= threshold;
}
else
{
int bo = y * pitch + x/8;
bit = (buffer[bo] >> (7 - x % 8)) & 1;
}
if (verbose)
putchar(bit ? '#' : '.');
if (bit)
{
int bx = bitmapX + x + colsBeforeGlyph;
uint32_t bitOffset = by * bitmapWidth + bx;
uint32_t wordOffset = bitOffset / 32;
if (wordOffset > bitmapSize)
{
fprintf(stderr, "Ooops, wordOffset=%u, size=%u\n"
" bx=%u by=%u bitOffset=%u\n",
wordOffset, (unsigned) bitmapSize,
bx, by, bitOffset);
exit(127);
}
bitmap[wordOffset] |= 1UL << (bitOffset % 32);
}
}
if (verbose)
putchar('\n');
}
// Fill the range table
if (charCode != currentCode)
{
// We are changing ranges, write the previous one
uint32_t numCodes = currentCode - firstCode;
if (verbose)
printf("New glyph range at %u, had %u codes in %u..%u\n",
(int) charCode, numCodes, firstCode, currentCode);
*range++ = (numCodes << 12) | widthOffsetIndex;
*range++ = ((charCode - currentCode) << 12) | 0xFFF;
currentCode = charCode;
firstCode = charCode;
widthOffsetIndex = offset - offsets;
}
// Fill the width/offset table
if (verbose)
printf("glyphWidth %u bitmapX %u offset table %08X\n",
glyphWidth, bitmapX, (glyphWidth << 16) | bitmapX);
*offset++ = (glyphWidth << 16) | bitmapX;
// Advance to next glyph in the bitmap
bitmapX += glyphWidth;
currentCode++;
}
// Fill the last range
uint32_t numCodes = currentCode - firstCode;
if (verbose)
printf("Last glyph range had %u codes in %u..%u\n",
numCodes, firstCode, currentCode);
*range++ = (numCodes << 12) | widthOffsetIndex;
*range++ = ((0xFFFFF - currentCode) << 12) | 0xFFF;
// Now time to emit the actual data
fprintf(output,
"/** Font %s, generated from %s - Do not edit manually **/\n",
fontName, ttfName);
fprintf(output,
"\n"
"#include <unifont.h>\n"
"\n"
"const UNIFONT %s=\n"
"{\n",
fontName);
#define MK_PROLOG(lib, size) \
((((lib) &0xFFF) << 20) | 0x80000 | \
((size > 0x3ffff) ? (0x40000 | ((((size) -0x40000) >> 10) & 0x3ffff)) \
: ((size) &0x3FFFF)))
#define LIB_FONTS 78
unsigned usedRanges = range - ranges;
unsigned usedData = offset - offsets;
unsigned totalSize = 2 + usedRanges + usedData + bitmapSize / 4;
uint32_t prolog = MK_PROLOG(LIB_FONTS, totalSize);
fprintf(output,
" .Prolog = 0x%X,\n"
" .BitmapWidth = %u,\n"
" .BitmapHeight = %u,\n"
" .OffsetBitmap = %u,\n"
" .OffsetTable = %u,\n"
"\n"
" .MapTable = {\n",
prolog,
bitmapWidth / 8,
bitmapHeight,
3 + usedRanges + usedData,
3 + usedRanges);
// Emit the ranges
fprintf(output, " // Ranges");
for (uint32_t *r = ranges; r < range; r++)
fprintf(output, "%s0x%08X,",
(r - ranges) % 8 == 0 ? "\n " : " ",
*r);
// Emit the width / offsets
fprintf(output, "\n\n // Width/offset");
for (uint32_t *o = offsets; o < offset; o++)
fprintf(output, "%s0x%08X,",
(o - offsets) % 8 == 0 ? "\n " : " ",
*o);
// Emit the bitmap
uint32_t *bitmapEnd = bitmap + bitmapSize;
fprintf(output, "\n\n // Bitmap");
for (uint32_t *b = bitmap; b < bitmapEnd; b++)
fprintf(output, "%s0x%08X,",
(b - bitmap) % 8 == 0 ? "\n " : " ",
*b);
// Close the data table
fprintf(output,
"\n"
" // End of bitmap data\n"
" }\n"
"};\n"
"\n");
fclose(output);
// Free memory
free(ranges);
free(offsets);
free(bitmap);
free(charCodes);
// Free the face and library ressources
FT_Done_Face(face);
FT_Done_FreeType(library);
}
void usage(cstring prog)
// ----------------------------------------------------------------------------
// Display the usage message for the program
// ----------------------------------------------------------------------------
{
printf("Usage: %s [-h] [-v] [-s <size>] <name> <ttf> <output>\n"
" name: Name of the structure in C\n"
" ttf: TrueType input font\n"
" output: C source file to be generated\n"
" -h: Display this usage message\n"
" -v: Verbose output\n"
" -s <size>: Force font size to s pixels\n", prog);
}
int main(int argc, char *argv[])
// ----------------------------------------------------------------------------
// Run the tool
// ----------------------------------------------------------------------------
{
// Process options
int opt;
int fontSize = 0;
int threshold = 0;
while ((opt = getopt(argc, argv, "hs:t:v")) != -1)
{
switch (opt)
{
case 'v':
verbose = 1;
break;
case 's':
fontSize = atoi(optarg);
break;
case 't':
threshold = atoi(optarg);
break;
case 'h':
usage(argv[0]);
exit(0);
default:
usage(argv[0]);
exit(1);
}
}
argc -= optind;
if (argc < 3)
{
usage(argv[0]);
return 1;
}
argv += optind;
// Generate the C source code
processFont(argv[0], argv[1], argv[2], fontSize, threshold);
return 0;
}

669
tools/ttf2font/ttf2font.cpp Normal file
View file

@ -0,0 +1,669 @@
// ****************************************************************************
// ttf2font.cpp DB48X project
// ****************************************************************************
//
// File Description:
//
// This converts a TTF file to a DB48X font format
//
// This is freely inspired by ttf2RasterFont in the wp43s project,
// but simplified to generate the font data structures in DB48X
//
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the GNU General Public License v3
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with DB48X. If not, see <https://www.gnu.org/licenses/>.
// ****************************************************************************
#include <ctype.h>
#include <ft2build.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include FT_FREETYPE_H
#include <vector>
typedef const char *cstring;
typedef unsigned char byte;
typedef unsigned uint;
typedef std::vector<byte> bytes;
typedef std::vector<int> ints;
int verbose = 0;
const char *getErrorMessage(FT_Error err)
// ----------------------------------------------------------------------------
// Error messages from freetype2
// ----------------------------------------------------------------------------
{
#undef __FTERRORS_H__
#define FT_ERROR_START_LIST switch (err) {
#define FT_ERRORDEF(e, v, s) case e: return s;
#define FT_ERROR_END_LIST }
#include FT_ERRORS_H
return "(Unknown error)";
}
enum id
// ----------------------------------------------------------------------------
// Get the IDs
// ----------------------------------------------------------------------------
{
#define ID(i) ID_##i,
#include "ids.tbl"
};
int sortCharCodes(const void *a, const void *b)
// ----------------------------------------------------------------------------
// Sort according to the char codes
// ----------------------------------------------------------------------------
{
return *(FT_ULong *) a - *(FT_ULong *) b;
}
template<typename Data, typename Int = uint>
inline Data *leb128(Data *p, Int value)
// ----------------------------------------------------------------------------
// Write the LEB value at pointer
// ----------------------------------------------------------------------------
{
byte *bp = (byte *) p;
do
{
*bp++ = (value & 0x7F) | 0x80;
value = Int(value >> 7);
} while (value != 0 && (Int(~0ULL) > Int(0) || value != Int(~0ULL)));
bp[-1] &= ~0x80;
return (Data *) bp;
}
template <typename Int>
void operator += (bytes &b, Int data)
// ----------------------------------------------------------------------------
// Add some data in LEB128 form to a bytes vector
// ----------------------------------------------------------------------------
{
byte buffer[16];
byte *p = buffer;
p = leb128(p, data);
b.insert(b.end(), buffer, p);
}
template <typename T>
T *calloc(size_t sz)
// ----------------------------------------------------------------------------
// Type-safe calloc
// ----------------------------------------------------------------------------
{
return (T*) calloc(sz, sizeof(T));
}
void processFont(cstring fontName,
cstring ttfName,
cstring cSourceName,
int fontSize,
int threshold)
// ----------------------------------------------------------------------------
// Process a font and generate the C source file from it
// ----------------------------------------------------------------------------
/* Font structure: There are two font formats, dense and sparse.
* The dense format has a singe bitmap
* The sparse format has one smaller bitmap per character
* Both formats share the same offsets structure, and are packed using leb128.
*
* Dense format:
* - ID_dense_font
* - Total size of font data
* - Height of bitmap
* - Width of bitmap
* - Bitmap data (Height * Width bits, padded to 8 bits)
* - Sequence of ranges, each range being:
* + First code point in range, 0 at end of font data
* + Number of codepoints in range, 0 at end of font data
* + For each code point in range
* * Width of character in the bitmap
* - Length of name
* - Bytes for font name
*
* Sparse format:
* - ID_sparse_font
* - Total size of font data
* - Height of characters
* - Sequence of ranges, each range being:
* + First code point in range, 0 marks end of font data
* + Number of code points in range, 0 at end of font data
* + For each code point in range
* * X offset of character bitmap
* * Y offset of character bitmap
* * Width of character bitmap
* * Height of character bitmap
* * Advance to next character
* * Bitmap data for character, padded to 8 bits
* - Length of name
* - Bytes for font name
*
* The formats are designed to make it possible to store it efficiently as a
* dynamic and moveable RPL object. The downside is that some linear scanning is
* required when displaying characters. This is mitigated in the text display
* code by keeping a cache for the current range, with the assumption that in
* most cases, consecutive characters tend to be in the same range.
*
* The tool computes the total size of font data in either case, to let you pick
* the representation that uses the least data.
*/
{
// Open file before doing anything else, in case it fails
FILE *output = fopen(cSourceName, "w");
if (!output)
{
fprintf(stderr, "Cannot open source file %s", cSourceName);
fprintf(stderr, "Error %d: %s", errno, strerror(errno));
exit(1);
}
// Initialize freetype2
FT_Library library;
FT_Error error = FT_Init_FreeType(&library);
if (error != FT_Err_Ok)
{
fprintf(stderr, "Error during freetype2 library initialisation.\n");
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
exit(1);
}
// Open the font face
FT_Face face;
error = FT_New_Face(library, ttfName, 0, &face);
if (error != FT_Err_Ok)
{
fprintf(stderr, "Error during face creation from file %s\n", ttfName);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
FT_Done_FreeType(library);
exit(1);
}
// Set font size - Formula lifted from wp43s
int unitsPerEM = face->units_per_EM;
int pixelSize = unitsPerEM == 1024 ? 32 : 50;
int baseSize = unitsPerEM / pixelSize;
int fontHeightPixels = fontSize ? fontSize : baseSize;
#define SCALED(x) ((x) * fontHeightPixels / baseSize)
error = FT_Set_Pixel_Sizes(face, 0, fontHeightPixels);
if (error != FT_Err_Ok)
{
fprintf(stderr, "Error setting pixel size from file %s\n", ttfName);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
FT_Done_FreeType(library);
exit(1);
}
// Face scaling
uint denseWidth = 0;
int ascend = face->ascender;
int descend = face->descender;
int faceHeight = ascend - descend;
uint denseHeight = SCALED(faceHeight) / pixelSize;
int renderFlag = FT_LOAD_RENDER;
if (!threshold)
renderFlag |= FT_LOAD_TARGET_MONO;
// Count total number of glyphs
uint numberOfGlyphs = face->num_glyphs;
FT_ULong *charCodes = calloc<FT_ULong>(numberOfGlyphs);
FT_ULong *curCharCode = charCodes;
FT_UInt glyphIndex = 0;
uint glyphCount = 0;
int minRowsBelow = 0;
for (FT_ULong charCode = FT_Get_First_Char(face, &glyphIndex);
glyphIndex;
charCode = FT_Get_Next_Char(face, charCode, &glyphIndex))
{
*curCharCode++ = charCode;
error = FT_Load_Glyph(face, glyphIndex, renderFlag);
if (error != FT_Err_Ok)
{
fprintf(stderr, "warning: failed to load glyph 0x%04lX\n", charCode);
fprintf(stderr, "Error %d : %s\n", error, getErrorMessage(error));
}
FT_Glyph_Metrics *m = &face->glyph->metrics;
FT_Bitmap *b = &face->glyph->bitmap;
int rowsGlyph = b->rows;
int rowsDescend = SCALED(descend) / pixelSize;
int rowsBelowGlyph = m->horiBearingY / 64 - rowsDescend - rowsGlyph;
if (rowsBelowGlyph < minRowsBelow)
minRowsBelow = rowsBelowGlyph;
denseWidth += face->glyph->metrics.horiAdvance / 64;
glyphCount++;
}
if (verbose || glyphCount > numberOfGlyphs)
{
fprintf(stderr, "Number of glyphs %u, glyph count %u, width %u\n",
numberOfGlyphs, glyphCount, denseWidth);
if (glyphCount > numberOfGlyphs)
exit(1);
}
// Sort the glyphs in increasing Unicode codepoint order
qsort(charCodes, glyphCount, sizeof(charCodes[0]), sortCharCodes);
// Adjustment of bitmap height if there are descenders below
if (minRowsBelow < 0)
denseHeight -= minRowsBelow;
// Round up the bitmap Width to full bytes
denseWidth = (denseWidth + 7) / 8 * 8;
// Dense format data
// Allocate the global bitmap where we will draw glyphs and other structures
size_t denseBitMapSize = (denseWidth * denseHeight + 7) / 8;
byte *denseBitMap = calloc<byte>(denseBitMapSize);
bytes dense;
// Sparse format data
bytes sparse;
sparse += denseHeight;
if (verbose)
printf("Font bitmap width %u height %u size %u\n",
denseWidth, denseHeight, (uint) denseBitMapSize);
// Set the transform for the font
FT_Vector pen;
pen.x = 0;
pen.y = (fontHeightPixels - SCALED(face->ascender) / pixelSize) * 64;
FT_Set_Transform(face, NULL, &pen);
// Start on the left of the dense bitmap
int32_t denseBitMapX = 0;
int32_t firstCode = 0;
int32_t currentCode = 0;
// Find the ranges in the font
ints rangesFirst;
ints rangesCount;
// Loop on all glyphs
for (uint g = 0; g < glyphCount; g++)
{
FT_ULong charCode = charCodes[g];
FT_UInt glyphIndex = FT_Get_Char_Index(face, charCode);
if (glyphIndex == 0)
{
fprintf(stderr, "Glyph 0x%04lX undefined\n", charCode);
continue;
}
// Check if we begin a new glyph range
if (charCode != currentCode || g+1 == glyphCount)
{
uint32_t numCodes = currentCode - firstCode;
if (numCodes)
{
if (verbose)
printf("New glyph range at %u, had %u codes in %u..%u\n",
(int) charCode, numCodes, firstCode, currentCode);
rangesFirst.push_back(firstCode);
rangesCount.push_back(numCodes);
}
currentCode = charCode;
firstCode = charCode;
}
currentCode++;
}
if (verbose)
printf("Found %zu glyph ranges\n", rangesFirst.size());
// Loop on all ranges
uint numRanges = rangesFirst.size();
uint glyph = 0;
for (uint r = 0; r < numRanges; r++)
{
uint firstCode = rangesFirst[r];
uint numCodes = rangesCount[r];
uint lastCode = firstCode + numCodes;
// Write out header for the range
dense += firstCode;
dense += numCodes;
sparse += firstCode;
sparse += numCodes;
// Loop on all glyphs in range
for (uint g = firstCode; g < lastCode; g++)
{
FT_ULong charCode = charCodes[glyph++];
FT_UInt glyphIndex = FT_Get_Char_Index(face, charCode);
if (glyphIndex == 0)
continue;
error = FT_Load_Glyph(face, glyphIndex, renderFlag);
if (error != FT_Err_Ok)
{
fprintf(stderr,
"Warning: failed to load glyph 0x%04lX\n", charCode);
fprintf(stderr,
"Error %d : %s\n", error, getErrorMessage(error));
continue;
}
// Get glyph metrics and bitmap
FT_Glyph_Metrics *m = &face->glyph->metrics;
FT_Bitmap *b = &face->glyph->bitmap;
// Columns in the glyph
int glyphWidth = m->horiAdvance / 64;
int colsBeforeGlyph = m->horiBearingX / 64;
int colsGlyph = b->width;
int colsRight = colsBeforeGlyph + colsGlyph;
int colsAfterGlyph = glyphWidth - colsRight;
// Rows in the glyph
int rowsAscend = SCALED(ascend) / pixelSize;
int rowsAboveGlyph = rowsAscend - m->horiBearingY / 64;
int rowsAboveSave = rowsAboveGlyph;
int rowsGlyph = b->rows;
int rowsDescend = SCALED(descend) / pixelSize;
int rowsBelowGlyph = m->horiBearingY / 64 - rowsDescend - rowsGlyph;
int rowsBelowSave = rowsBelowGlyph;
// Adjust positions for dense bitmaps
if (colsBeforeGlyph < 0)
{
colsGlyph += colsBeforeGlyph;
colsBeforeGlyph = 0;
}
if (false && rowsAboveGlyph < 0)
{
rowsGlyph += rowsAboveGlyph;
rowsAboveGlyph = 0;
}
// Fill sparse data header
sparse += colsBeforeGlyph;
sparse += rowsAboveGlyph;
sparse += colsGlyph;
sparse += rowsGlyph;
sparse += glyphWidth;
// Allocate the sparse bitmap
uint sparseBitmapBits = colsGlyph * rowsGlyph;
uint sparseBitmapBytes = (sparseBitmapBits + 7) / 8;
bytes sparseBits;
sparseBits.resize(sparseBitmapBytes);
// Fill sparse and dense bitmaps from glyph bitmap
byte *buffer = face->glyph->bitmap.buffer;
uint pitch = face->glyph->bitmap.pitch;
uint bwidth = face->glyph->bitmap.width;
uint rwidth = colsGlyph - 1;
for (int y = 0; y < rowsGlyph; y++)
{
int by = y + rowsAboveGlyph;
if (by < 0)
continue;
for (int x = 0; x < colsGlyph; x++)
{
int bit = 0;
if (threshold)
{
int bo = y * bwidth + x;
bit = buffer[bo] >= threshold;
}
else
{
int bo = y * pitch + x/8;
bit = (buffer[bo] >> (7 - x % 8)) & 1;
}
if (verbose)
putchar(bit ? '#' : '.');
int dbo = y * colsGlyph + (rwidth - x);
if (bit)
{
int bx = denseBitMapX + x + colsBeforeGlyph;
uint32_t bitOffset = by * denseWidth + bx;
uint32_t byteOffset = bitOffset / 8;
if (byteOffset > denseBitMapSize)
{
fprintf(stderr, "Ooops, wordOffset=%u, size=%u\n"
" bx=%u by=%u bitOffset=%u\n",
byteOffset, (uint) denseBitMapSize,
bx, by, bitOffset);
exit(127);
}
denseBitMap[byteOffset] |= 1 << (bitOffset % 8);
sparseBits[dbo / 8] |= 1 << (dbo % 8);
}
}
if (verbose)
putchar('\n');
}
// Add sparse bitmap to sparse data
sparse.insert(sparse.end(), sparseBits.begin(), sparseBits.end());
// Add X coordinate in dense bitmap to dense data
dense += glyphWidth;
denseBitMapX += glyphWidth;
// Verbose output about that glyph
if (verbose)
{
char utf8[4] = { 0 };
if (charCode < 0x80)
{
utf8[0] = charCode;
}
else if (charCode < 0x800)
{
utf8[0] = 0xC0 | (charCode >> 6);
utf8[1] = 0x80 | (charCode & 63);
}
else if (charCode < 0x10000)
{
utf8[0] = 0xE0 | (charCode >> 12);
utf8[1] = 0x80 | ((charCode >> 6) & 63);
utf8[2] = 0x80 | ((charCode >> 0) & 63);
}
else
{
utf8[0] = utf8[1] = utf8[2] = '-';
}
printf("Glyph %4lu '%s' width %u"
" Columns: %d %d %d"
" Rows: %d %d %d\n",
charCode,
utf8,
glyphWidth,
colsBeforeGlyph,
colsGlyph,
colsAfterGlyph,
rowsAboveSave,
rowsGlyph,
rowsBelowSave);
} // Verbose output
} // Loop on codes
} // Loop on ranges
// Insert terminating 0 code point to mark end of ranges
dense += 0;
dense += 0;
sparse += 0;
sparse += 0;
// Add the length of the name followed by the name itself
size_t nameLen = strlen(fontName);
dense += nameLen;
sparse += nameLen;
for (uint i = 0; i < nameLen; i++)
{
dense.push_back((byte) fontName[i]);
sparse.push_back((byte) fontName[i]);
}
// Insert bitmap data at beginning of dense data
dense.insert(dense.begin(), denseBitMap, denseBitMap + denseBitMapSize);
bytes denseInfo;
denseInfo += denseHeight;
denseInfo += denseWidth;
dense.insert(dense.begin(), denseInfo.begin(), denseInfo.end());
// Emit the headers
bytes sparseHeader;
sparseHeader += ID_sparse_font;
sparseHeader += sparse.size();
sparse.insert(sparse.begin(), sparseHeader.begin(), sparseHeader.end());
bytes denseHeader;
denseHeader.clear();
denseHeader += ID_dense_font;
denseHeader += dense.size();
dense.insert(dense.begin(), denseHeader.begin(), denseHeader.end());
size_t denseSize = dense.size();
size_t sparseSize = sparse.size();
if (verbose)
printf("Sizes: dense %zu, sparse %zu\n", denseSize, sparseSize);
// Now time to emit the actual data
fprintf(output,
"/** Font %s, generated from %s - Do not edit manually **/\n"
"\n"
"#include \"font.h\"\n"
"\n",
fontName, ttfName);
if (denseSize < sparseSize || verbose)
{
fprintf(output,
"static const unsigned char %s_dense_data[%zu] =\n"
"{\n",
fontName, denseSize);
for (uint b = 0; b < denseSize; b++)
fprintf(output, "%s0x%02X,",
b % 16 == 0 ? "\n " : " ",
dense[b]);
fprintf(output, "\n};\n");
fprintf(output,
"const dense_font *%s = (dense_font *) %s_dense_data;\n",
fontName, fontName);
}
if (sparseSize <= denseSize || verbose)
{
fprintf(output,
"static const unsigned char %s_sparse_data[%zu] =\n"
"{\n",
fontName, sparseSize);
for (uint b = 0; b < sparseSize; b++)
fprintf(output, "%s0x%02X,",
b % 16 == 0 ? "\n " : " ",
sparse[b]);
fprintf(output, "\n};\n");
fprintf(output,
"const sparse_font *%s = (sparse_font *) %s_dense_data;\n",
fontName, fontName);
}
fclose(output);
// Free memory
free(denseBitMap);
free(charCodes);
// Free the face and library ressources
FT_Done_Face(face);
FT_Done_FreeType(library);
}
void usage(cstring prog)
// ----------------------------------------------------------------------------
// Display the usage message for the program
// ----------------------------------------------------------------------------
{
printf("Usage: %s [-h] [-v] [-s <size>] <name> <ttf> <output>\n"
" name: Name of the structure in C\n"
" ttf: TrueType input font\n"
" output: C source file to be generated\n"
" -h: Display this usage message\n"
" -v: Verbose output\n"
" -s <size>: Force font size to s pixels\n", prog);
}
int main(int argc, char *argv[])
// ----------------------------------------------------------------------------
// Run the tool
// ----------------------------------------------------------------------------
{
// Process options
int opt;
int fontSize = 0;
int threshold = 0;
while ((opt = getopt(argc, argv, "hs:t:v")) != -1)
{
switch (opt)
{
case 'v':
verbose = 1;
break;
case 's':
fontSize = atoi(optarg);
break;
case 't':
threshold = atoi(optarg);
break;
case 'h':
usage(argv[0]);
exit(0);
default:
usage(argv[0]);
exit(1);
}
}
argc -= optind;
if (argc < 3)
{
usage(argv[0]);
return 1;
}
argv += optind;
// Generate the C source code
processFont(argv[0], argv[1], argv[2], fontSize, threshold);
return 0;
}