Files
crosspoint-reader-mod/lib/CrossPointFont/fontconvert/main.c

554 lines
18 KiB
C
Raw Normal View History

//
// TrueType to CrossPoint font converter
// Copyright (c) 2024 BitBank Software, inc.
// Written by Larry Bank, adapted by Dave Allie for CrossPoint
// August 31, 2024
// The CrossPoint font format is a losslessly compressed bitmap font of a single point size with multiple variants
// This was built entirely on the back of Larry Bank's bb_font format.
// The data is compressed with a compression scheme based on CCITT T.6
// The font structure includes overall size, per-character glyph info and then the
// compressed image data at the end.
// The font file format is designed to allow both dynamic loading of font data from
// external memory/disk or compiling the data as const into a progarm.
//
// Example usage:
// ./fontconvert <regular.ttf> [-b <bold.ttf>] [-i <italic.ttf>] [-bi <bold-italic.ttf>] -p <pt size> -o <out.cpf>
// ./fontconvert <regular.ttf> [-b <bold.ttf>] [-i <italic.ttf>] [-bi <bold-italic.ttf>] -p <pt size> -o <out.h>
//
// This code requires the freetype library
// found here: www.freetype.org
//
#ifndef ARDUINO
#include <ctype.h>
#include <ft2build.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../CrossPointFontFormat.h"
#include FT_GLYPH_H
#include FT_MODULE_H
#include FT_TRUETYPE_DRIVER_H
#include "../Group5/g5enc.inl" // Group5 image compression library
G5ENCIMAGE g5enc; // Group5 encoder state
#define DPI 150 // Approximate resolution of common displays
#define OUTBUF_SIZE 1048576 // 1MB
#define MAX_INTERVALS 65536
#define FONT_SCALE_FACTOR 2
// TODO: Re-enable small font
// Disabled small font generation to get this working, but want to re-enable
#define SMALL_FONT_ENABLED 0
uint32_t raw_intervals[][2] = {
/* Basic Latin */
// ASCII letters, digits, punctuation, control characters
{0x0000, 0x007F},
/* Latin-1 Supplement */
// Accented characters for Western European languages
{0x0080, 0x00FF},
/* Latin Extended-A */
// Eastern European and Baltic languages
{0x0100, 0x017F},
/* General Punctuation (core subset) */
// Smart quotes, en dash, em dash, ellipsis, NO-BREAK SPACE
{0x2000, 0x206F},
/* Basic Symbols From "Latin-1 + Misc" */
// dashes, quotes, prime marks
{0x2010, 0x203A},
// misc punctuation
{0x2040, 0x205F},
// common currency symbols
{0x20A0, 0x20CF},
/* Combining Diacritical Marks (minimal subset) */
// Needed for proper rendering of many extended Latin languages
{0x0300, 0x036F},
/* Greek & Coptic */
// Used in science, maths, philosophy, some academic texts
// {0x0370, 0x03FF},
/* Cyrillic */
// Russian, Ukrainian, Bulgarian, etc.
{0x0400, 0x04FF},
/* Math Symbols (common subset) */
// Superscripts and Subscripts
{0x2070, 0x209F},
// General math operators
{0x2200, 0x22FF},
// Arrows
{0x2190, 0x21FF},
/* CJK */
// Core Unified Ideographs
// {0x4E00, 0x9FFF},
// Extension A
// {0x3400, 0x4DBF},
// Extension B
// {0x20000, 0x2A6DF},
// Extension CF
// {0x2A700, 0x2EBEF},
// Extension G
// {0x30000, 0x3134F},
// Hiragana
// {0x3040, 0x309F},
// Katakana
// {0x30A0, 0x30FF},
// Katakana Phonetic Extensions
// {0x31F0, 0x31FF},
// Halfwidth Katakana
// {0xFF60, 0xFF9F},
// Hangul Syllables
// {0xAC00, 0xD7AF},
// Hangul Jamo
// {0x1100, 0x11FF},
// Hangul Compatibility Jamo
// {0x3130, 0x318F},
// Hangul Jamo Extended-A
// {0xA960, 0xA97F},
// Hangul Jamo Extended-B
// {0xD7B0, 0xD7FF},
// CJK Radicals Supplement
// {0x2E80, 0x2EFF},
// Kangxi Radicals
// {0x2F00, 0x2FDF},
// CJK Symbols and Punctuation
// {0x3000, 0x303F},
// CJK Compatibility Forms
// {0xFE30, 0xFE4F},
// CJK Compatibility Ideographs
// {0xF900, 0xFAFF},
/* Specials */
// Replacement Character
{0xFFFD, 0xFFFD},
};
//
// Comparison function for qsort
//
int compareIntervals(const void* a, const void* b) {
const uint32_t* ia = (uint32_t*)a;
const uint32_t* ib = (uint32_t*)b;
if (ia[0] < ib[0]) return -1;
if (ia[0] > ib[0]) return 1;
return 0;
}
//
// Sort and merge adjacent intervals
// Returns the number of intervals after merging
//
int sortAndMergeIntervals(uint32_t intervals[][2], const int count) {
int merged_count = 0;
// Sort intervals by start value
qsort(intervals, count, sizeof(uint32_t) * 2, compareIntervals);
// Merge overlapping/adjacent intervals
for (int i = 0; i < count; i++) {
if (merged_count > 0 && intervals[i][0] <= intervals[merged_count - 1][1] + 1) {
// Merge with previous interval
if (intervals[i][1] > intervals[merged_count - 1][1]) {
intervals[merged_count - 1][1] = intervals[i][1];
}
} else {
// Add as new interval
if (merged_count != i) {
intervals[merged_count][0] = intervals[i][0];
intervals[merged_count][1] = intervals[i][1];
}
merged_count++;
}
}
return merged_count;
}
//
// Create the comments and const array boilerplate for the hex data bytes
//
void StartHexFile(FILE* f, int iLen, const char* fname, int size) {
int i, j;
char szTemp[256];
fprintf(f, "#pragma once\n\n");
fprintf(f, "//\n// Created with fontconvert, written by Larry Bank, updated for CrossPoint by Dave Allie\n");
fprintf(f, "// Point size: %d (scaled %dx)\n", size, FONT_SCALE_FACTOR);
fprintf(f, "// compressed font data size = %d bytes\n//\n", iLen);
strcpy(szTemp, fname);
i = strlen(szTemp);
if (szTemp[i - 2] == '.') szTemp[i - 2] = 0; // get the leaf name for the data
j = i;
// go backwards to get rid trim off just the leaf name
while (j > 0 && szTemp[j] != '/') {
j--;
}
if (szTemp[j] == '/') j++;
fprintf(f, "static const uint8_t %s[] = {\n", &szTemp[j]);
} /* StartHexFile() */
//
// Add N bytes of hex data to the output
// The data will be arranged in rows of 16 bytes each
//
void AddHexBytes(FILE* f, void* pData, int iLen, int bLast) {
static int iCount = 0; // number of bytes processed so far
int i;
uint8_t* s = (uint8_t*)pData;
for (i = 0; i < iLen; i++) { // process the given data
fprintf(f, "0x%02x", *s++);
iCount++;
if (i < iLen - 1 || !bLast) fprintf(f, ",");
if ((iCount & 15) == 0) fprintf(f, "\n"); // next row of 16
}
if (bLast) {
fprintf(f, "};\n");
}
} /* AddHexBytes() */
int loadCodePoint(FT_Face face, uint32_t code_point, CrossPointFontGlyph* pGlyphs, uint8_t* pBitmap,
uint32_t* glyph_index, uint32_t* iOffset) {
uint8_t* s;
int iPitch, err;
FT_Glyph glyph;
// MONO renderer provides clean image with perfect crop
// (no wasted pixels) via bitmap struct.
if ((err = FT_Load_Char(face, code_point, FT_LOAD_TARGET_MONO))) {
printf("Error %d loading char U+%04X\n", err, code_point);
(*glyph_index)++;
return 0;
}
if ((err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO))) {
printf("Error %d rendering char U+%04X\n", err, code_point);
(*glyph_index)++;
return 0;
}
if ((err = FT_Get_Glyph(face->glyph, &glyph))) {
printf("Error %d getting glyph U+%04X\n", err, code_point);
(*glyph_index)++;
return 0;
}
FT_Bitmap* bitmap = &face->glyph->bitmap;
FT_BitmapGlyphRec* g = (FT_BitmapGlyphRec*)glyph;
// TODO: Restore small font
if (0 /* bSmallFont */) {
#if SMALL_FONT_ENABLED == 1
printf("Stubbed\n");
return 1;
#else
printf("Small font has been disabled\n");
return 1;
#endif
} else {
pGlyphs[*glyph_index].bitmapOffset = *iOffset;
pGlyphs[*glyph_index].width = bitmap->width;
pGlyphs[*glyph_index].height = bitmap->rows;
pGlyphs[*glyph_index].xAdvance = (face->glyph->advance.x >> 6);
pGlyphs[*glyph_index].xOffset = g->left;
pGlyphs[*glyph_index].yOffset = g->top;
}
s = bitmap->buffer;
iPitch = bitmap->pitch;
g5_encode_init(&g5enc, bitmap->width, bitmap->rows, &pBitmap[*iOffset], OUTBUF_SIZE - *iOffset);
for (int y = 0; y < bitmap->rows; y++) {
g5_encode_encodeLine(&g5enc, &s[y * iPitch]);
} // for y
int iLen = g5_encode_getOutSize(&g5enc);
*iOffset += iLen;
FT_Done_Glyph(glyph);
(*glyph_index)++;
return 0;
}
int main(int argc, char* argv[]) {
int i, err, size = 0;
uint32_t iLen, iOffset = 0;
FILE* fOut;
// TrueType library structures
FT_Library library;
char* regularFaceFile;
char* boldFaceFile = NULL;
char* italicFaceFile = NULL;
char* boldItalicFaceFile = NULL;
const char* outputFile = NULL;
FT_Face faceRegular;
FT_Face faceBold;
FT_Face faceItalic;
FT_Face faceBoldItalic;
int bSmallFont = 0; // indicates if we're creating a normal or small font file
CrossPointFontUnicodeInterval* pIntervals;
CrossPointFontGlyph* pGlyphs;
#if SMALL_FONT_ENABLED == 1
CrossPointFontSmallGlyph* pSmallGlyphs;
#endif
uint8_t* pBitmap;
CrossPointFontHeader epdFontHeader;
int bHFile; // flag indicating if the output will be a .H file of hex data
// Process intervals
uint32_t intervals[MAX_INTERVALS][2];
int intervalCount = sizeof(raw_intervals) / sizeof(raw_intervals[0]);
uint32_t totalGlyphs = 0;
if (argc < 6 || argc % 2 == 1) {
printf(
"Usage: %s <regular.ttf> [-b <bold.ttf>] [-i <italic.ttf>] [-bi <bold-italic.ttf>] -p point_size -o <out.cpf "
"or out.h>\n",
argv[0]);
return 1;
}
regularFaceFile = argv[1];
for (int i = 2; i < argc; i += 2) {
if (strcmp(argv[i], "-b") == 0) {
// Bold font
boldFaceFile = argv[i + 1];
} else if (strcmp(argv[i], "-i") == 0) {
// Italic font
italicFaceFile = argv[i + 1];
} else if (strcmp(argv[i], "-bi") == 0) {
// Bold-Italic font
boldItalicFaceFile = argv[i + 1];
} else if (strcmp(argv[i], "-p") == 0) {
// Point size
size = atoi(argv[i + 1]);
} else if (strcmp(argv[i], "-o") == 0) {
// Output file
outputFile = argv[i + 1];
// output an H file?
bHFile = outputFile[strlen(outputFile) - 1] == 'H' || outputFile[strlen(outputFile) - 1] == 'h';
} else {
printf("Unknown argument: %s\n", argv[i]);
return 1;
}
}
if (!outputFile) {
printf("No output file specified\n");
return 1;
}
if (size <= 0) {
printf("Invalid point size: %d\n", size);
return 1;
}
size = size * FONT_SCALE_FACTOR;
bSmallFont = (size < 60) && SMALL_FONT_ENABLED == 1; // Glyph info can fit in signed 8-bit values
int fontVariants = 1; // Always at least one variant we treat as regular
if (boldFaceFile) fontVariants += 1;
if (italicFaceFile) fontVariants += 1;
if (boldItalicFaceFile) fontVariants += 1;
// Copy and sort/merge intervals
if (intervalCount > MAX_INTERVALS) {
printf("Error: too many intervals (max %d)\n", MAX_INTERVALS);
return 1;
}
for (i = 0; i < intervalCount; i++) {
intervals[i][0] = raw_intervals[i][0];
intervals[i][1] = raw_intervals[i][1];
}
intervalCount = sortAndMergeIntervals(intervals, intervalCount);
// Calculate total number of glyphs
for (i = 0; i < intervalCount; i++) {
totalGlyphs += intervals[i][1] - intervals[i][0] + 1;
}
totalGlyphs *= fontVariants;
printf("Processed intervals: %d, total glyphs: %u\n", intervalCount, totalGlyphs);
// Allocate memory for intervals
pIntervals = (CrossPointFontUnicodeInterval*)malloc(intervalCount * sizeof(CrossPointFontUnicodeInterval));
if (!pIntervals) {
printf("Error allocating memory for interval data\n");
return 1;
}
// Allocate memory for glyphs
if (bSmallFont) {
#if SMALL_FONT_ENABLED == 1
pSmallGlyphs = (CrossPointFontSmallGlyph*)malloc(totalGlyphs * sizeof(CrossPointFontSmallGlyph));
if (!pSmallGlyphs) {
printf("Error allocating memory for glyph data\n");
return 1;
}
#else
printf("Small font has been disabled\n");
return 1;
#endif
} else {
pGlyphs = (CrossPointFontGlyph*)malloc(totalGlyphs * sizeof(CrossPointFontGlyph));
if (!pGlyphs) {
printf("Error allocating memory for glyph data\n");
return 1;
}
}
pBitmap = (uint8_t*)malloc(OUTBUF_SIZE); // Enough to hold the output
if (!pBitmap) {
printf("Error allocating memory for bitmap data\n");
return 1;
}
// Init FreeType lib, load font
if ((err = FT_Init_FreeType(&library))) {
printf("FreeType init error: %d", err);
return err;
}
// Use TrueType engine version 35, without subpixel rendering.
// This improves clarity of fonts since this library does not
// support rendering multiple levels of gray in a glyph.
// See https://github.com/adafruit/Adafruit-GFX-Library/issues/103
FT_UInt interpreter_version = TT_INTERPRETER_VERSION_35;
FT_Property_Set(library, "truetype", "interpreter-version", &interpreter_version);
if ((err = FT_New_Face(library, regularFaceFile, 0, &faceRegular))) {
printf("Font load error: %d\n", err);
FT_Done_FreeType(library);
return err;
}
if (italicFaceFile && (err = FT_New_Face(library, italicFaceFile, 0, &faceItalic))) {
printf("Font load error: %d\n", err);
FT_Done_FreeType(library);
return err;
}
if (boldFaceFile && (err = FT_New_Face(library, boldFaceFile, 0, &faceBold))) {
printf("Font load error: %d\n", err);
FT_Done_FreeType(library);
return err;
}
if (boldItalicFaceFile && (err = FT_New_Face(library, boldItalicFaceFile, 0, &faceBoldItalic))) {
printf("Font load error: %d\n", err);
FT_Done_FreeType(library);
return err;
}
// Shift the size left by 6 because the library uses '26dot6' fixed-point format
FT_Set_Char_Size(faceRegular, size << 6, 0, DPI, 0);
if (boldFaceFile) FT_Set_Char_Size(faceBold, size << 6, 0, DPI, 0);
if (italicFaceFile) FT_Set_Char_Size(faceItalic, size << 6, 0, DPI, 0);
if (boldItalicFaceFile) FT_Set_Char_Size(faceBoldItalic, size << 6, 0, DPI, 0);
// Build intervals with offsets and process glyphs
uint32_t glyph_index = 0;
for (int iInterval = 0; iInterval < intervalCount; iInterval++) {
const uint32_t intervalStart = intervals[iInterval][0];
const uint32_t intervalEnd = intervals[iInterval][1];
// Store interval with offset
pIntervals[iInterval].first = intervalStart;
pIntervals[iInterval].last = intervalEnd;
pIntervals[iInterval].offset = glyph_index;
// Process each glyph in this interval
// Load the codepoint for each style variant
for (uint32_t codePoint = intervalStart; codePoint <= intervalEnd; codePoint++) {
loadCodePoint(faceRegular, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
if (boldFaceFile) loadCodePoint(faceBold, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
if (italicFaceFile) loadCodePoint(faceItalic, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
if (boldItalicFaceFile) loadCodePoint(faceBoldItalic, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
} // for each code point in interval
} // for each interval
// Try to create the output file
fOut = fopen(outputFile, "w+b");
if (!fOut) {
printf("Error creating output file: %s\n", outputFile);
return 1;
}
epdFontHeader.height = faceRegular->size->metrics.height >> 6;
epdFontHeader.ascender = faceRegular->size->metrics.ascender >> 6;
epdFontHeader.styles = 0b0001;
if (boldFaceFile) epdFontHeader.styles |= 0b0010;
if (italicFaceFile) epdFontHeader.styles |= 0b0100;
if (boldItalicFaceFile) epdFontHeader.styles |= 0b1000;
epdFontHeader.intervalCount = intervalCount;
epdFontHeader.glyphCount = totalGlyphs;
// Write the file header
if (bSmallFont) {
#if SMALL_FONT_ENABLED == 1
epdFontHeader.u16Marker = CPF_FONT_MARKER_SMALL;
if (faceRegular->size->metrics.height == 0) {
// No face height info, assume fixed width and get from a glyph.
epdFontHeader.height = pSmallGlyphs[0].height;
}
iLen = sizeof(CrossPointFontHeader) + intervalCount * sizeof(CrossPointFontUnicodeInterval) +
totalGlyphs * sizeof(CrossPointFontSmallGlyph) + iOffset;
if (bHFile) { // create an H file of hex values
StartHexFile(fOut, iLen, outputFile, size);
AddHexBytes(fOut, &epdFontHeader, sizeof(CrossPointFontHeader), 0);
// Write the intervals
AddHexBytes(fOut, pIntervals, sizeof(CrossPointFontUnicodeInterval) * intervalCount, 0);
// Write the glyph table
AddHexBytes(fOut, pSmallGlyphs, sizeof(CrossPointFontSmallGlyph) * totalGlyphs, 0);
// Write the compressed bitmap data
AddHexBytes(fOut, pBitmap, iOffset, 1);
} else {
fwrite(&epdFontHeader, 1, sizeof(CrossPointFontHeader), fOut);
// Write the intervals
fwrite(pIntervals, 1, intervalCount * sizeof(CrossPointFontUnicodeInterval), fOut);
// Write the glyph table
fwrite(pSmallGlyphs, 1, totalGlyphs * sizeof(CrossPointFontSmallGlyph), fOut);
// Write the compressed bitmap data
fwrite(pBitmap, 1, iOffset, fOut);
}
#else
printf("Small font has been disabled\n");
return 1;
#endif
} else {
epdFontHeader.u16Marker = CPF_FONT_MARKER;
if (faceRegular->size->metrics.height == 0) {
// No face height info, assume fixed width and get from a glyph.
epdFontHeader.height = pGlyphs[0].height;
}
iLen = sizeof(CrossPointFontHeader) + intervalCount * sizeof(CrossPointFontUnicodeInterval) +
totalGlyphs * sizeof(CrossPointFontGlyph) + iOffset;
if (bHFile) { // create an H file of hex values
StartHexFile(fOut, iLen, outputFile, size);
AddHexBytes(fOut, &epdFontHeader, sizeof(CrossPointFontHeader), 0);
// Write the intervals
AddHexBytes(fOut, pIntervals, sizeof(CrossPointFontUnicodeInterval) * intervalCount, 0);
// Write the glyph table
AddHexBytes(fOut, pGlyphs, sizeof(CrossPointFontGlyph) * totalGlyphs, 0);
// Write the compressed bitmap data
AddHexBytes(fOut, pBitmap, iOffset, 1);
} else {
fwrite(&epdFontHeader, 1, sizeof(CrossPointFontHeader), fOut);
// Write the intervals
fwrite(pIntervals, 1, intervalCount * sizeof(CrossPointFontUnicodeInterval), fOut);
// Write the glyph table
fwrite(pGlyphs, 1, totalGlyphs * sizeof(CrossPointFontGlyph), fOut);
// Write the compressed bitmap data
fwrite(pBitmap, 1, iOffset, fOut);
}
} // large fonts
fflush(fOut);
fclose(fOut); // done!
FT_Done_FreeType(library);
printf("Success!\nFont file size: %d bytes (%d glyphs)\n", iLen, totalGlyphs);
return 0;
} /* main() */
#endif