]> git.bts.cx Git - ttf2pdfnt.git/blob - ttf2pdfnt.c
Initial release
[ttf2pdfnt.git] / ttf2pdfnt.c
1 /* TTF 2 Playdate FNT */
2 /* Ben Sherratt, 2024 */
3 /* Donationware: https://ko-fi.com/btscx */
4
5 /* With thanks to Sean Barrett and friends for stb_image_write and stb_truetype */
6
7 /* To use: compile this single file with a C compiler of your choice! */
8 /* clang -o ttf2pdfnt ttf2pdfnt.c && ./ttf2pdfnt */
9
10 #define STB_IMAGE_WRITE_IMPLEMENTATION
11 #include "stb_image_write.h"
12
13 #define STB_TRUETYPE_IMPLEMENTATION
14 #include "stb_truetype.h"
15
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <libgen.h>
19
20 struct Parameters {
21 char *inputFilename;
22 char *outputDirectory;
23 char *outputBase;
24 unsigned long pixelSize;
25 int alphaThreshold;
26 char *characters;
27 size_t characterCount;
28 };
29 typedef struct Parameters Parameters;
30
31 bool parseParameters(Parameters *parameters, int argc, char *argv[]) {
32 *parameters = (Parameters) {
33 .inputFilename = NULL,
34 .outputDirectory = NULL,
35 .outputBase = NULL,
36 .pixelSize = 16,
37 .alphaThreshold = 40,
38 .characters = "ABCDEFGHIJKLMNOPQRSTUWVXYZabcdefghijklmnopqrstuvwxyz 0123456789,./?!",
39 };
40
41 parameters->characterCount = strlen(parameters->characters);
42
43 bool error = false;
44 for (size_t i = 1; i < argc; ++i) {
45 char *parameter = argv[i];
46 if (strcmp(parameter, "-d") == 0) {
47 if (i + 1 < argc && argv[i + 1][0] != '-') {
48 parameters->outputDirectory = argv[++i];
49 } else {
50 error = true;
51 }
52 } else if (strcmp(parameter, "-b") == 0) {
53 if (i + 1 < argc && argv[i + 1][0] != '-') {
54 parameters->outputBase = argv[++i];
55 } else {
56 error = true;
57 }
58 } else if (strcmp(parameter, "-p") == 0) {
59 if (i + 1 < argc && argv[i + 1][0] != '-') {
60 parameters->pixelSize = atoi(argv[++i]);
61 } else {
62 error = true;
63 }
64 } else if (strcmp(parameter, "-a") == 0) {
65 if (i + 1 < argc && argv[i + 1][0] != '-') {
66 parameters->alphaThreshold = atoi(argv[++i]);
67 } else {
68 error = true;
69 }
70 } else if (strcmp(parameter, "-cf") == 0) {
71 if (i + 1 < argc && argv[i + 1][0] != '-') {
72 char *charactersFilename = argv[++i];
73 FILE *charactersFile = fopen(charactersFilename, "r");
74 if (charactersFile != NULL) {
75 fseek(charactersFile, 0, SEEK_END);
76 size_t characterCount = ftell(charactersFile);
77 if (characterCount > 0) {
78 parameters->characterCount = characterCount;
79 parameters->characters = (char *)malloc(characterCount);
80
81 fseek(charactersFile, 0, SEEK_SET);
82 fread((void *)parameters->characters, 1, characterCount, charactersFile);
83 }
84 fclose(charactersFile);
85 }
86 } else {
87 error = true;
88 }
89 } else if (strcmp(parameter, "-c") == 0) {
90 if (i + 1 < argc && argv[i + 1][0] != '-') {
91 const char *characters = argv[++i];
92 size_t characterCount = strlen(characters);
93 if (characterCount > 0) {
94 parameters->characterCount = characterCount;
95 parameters->characters = (char *)characters;
96 }
97 } else {
98 error = true;
99 }
100 } else {
101 parameters->inputFilename = argv[i];
102 }
103 }
104
105 if (error == false && parameters->inputFilename != NULL) {
106 if (parameters->outputBase == NULL) {
107 parameters->outputBase = basename(parameters->inputFilename);
108 }
109
110 if (parameters->outputDirectory == NULL) {
111 parameters->outputDirectory = ".";
112 }
113
114 return true;
115 } else {
116 return false;
117 }
118 }
119
120 int main(int argc, char *argv[]) {
121 Parameters params;
122 bool success = parseParameters(&params, argc, argv);
123
124 if (success) {
125 FILE *fontFile = fopen(params.inputFilename, "rb");
126
127 if (fontFile != NULL) {
128 fseek(fontFile, 0, SEEK_END);
129 size_t fontFileSize = ftell(fontFile);
130 fseek(fontFile, 0, SEEK_SET);
131
132 uint8_t *fontData = (uint8_t *)malloc(fontFileSize);
133 fread(fontData, 1, fontFileSize, fontFile);
134 fclose(fontFile);
135
136 stbtt_fontinfo ttfFontInfo;
137 if (stbtt_InitFont(&ttfFontInfo, fontData, stbtt_GetFontOffsetForIndex(fontData, 0)) != 0) {
138 size_t pixelSize = sizeof(uint8_t);
139 size_t imageStride = params.characterCount * params.pixelSize * pixelSize;
140 uint8_t *imageData = (uint8_t *)malloc(params.pixelSize * imageStride);
141
142 float scale = stbtt_ScaleForPixelHeight(&ttfFontInfo, params.pixelSize);
143
144 int ascent, descent, lineGap;
145 stbtt_GetFontVMetrics(&ttfFontInfo, &ascent, &descent, &lineGap);
146
147 int baseline = (float)ascent * scale;
148
149 char metricsFilename[1024];
150 sprintf(metricsFilename, "%s/%s.fnt", params.outputDirectory, params.outputBase);
151
152 FILE *metricsFile = fopen(metricsFilename, "w");
153 if (metricsFile != NULL) {
154 fprintf(metricsFile, "tracking=1\n\n");
155
156 for (int c = 0; c < params.characterCount; ++c) {
157 int codepoint = params.characters[c];
158
159 int x0, y0, x1, y1;
160 stbtt_GetCodepointBitmapBox(&ttfFontInfo, codepoint, scale, scale, &x0, &y0, &x1, &y1);
161
162 int width = params.pixelSize - x0;
163 int height = params.pixelSize - y0 - baseline;
164 stbtt_MakeCodepointBitmap(&ttfFontInfo, &imageData[(y0 + baseline) * imageStride + (c * params.pixelSize + x0) * pixelSize], width, height, imageStride, scale, scale, codepoint);
165
166 int advanceWidth, leftSideBearing;
167 stbtt_GetCodepointHMetrics(&ttfFontInfo, codepoint, &advanceWidth, &leftSideBearing);
168 advanceWidth *= scale;
169
170 switch (codepoint) {
171 case ' ':
172 fprintf(metricsFile, "space\t%u\n", advanceWidth);
173 break;
174
175 default:
176 fprintf(metricsFile, "%c\t%u\n", codepoint, advanceWidth);
177 break;
178 }
179 }
180
181 fclose(metricsFile);
182
183 size_t alphaPixelSize = sizeof(uint16_t);
184 size_t alphaImageStride = params.characterCount * params.pixelSize * alphaPixelSize;
185 uint8_t *alphaImageData = (uint8_t *)malloc(params.pixelSize * alphaImageStride);
186
187 for (size_t i = 0, j = 0; i < params.pixelSize * imageStride; i+=1, j+=2) {
188 alphaImageData[j + 0] = 0; // color
189 alphaImageData[j + 1] = (imageData[i] > params.alphaThreshold) ? 255 : 0; // alpha
190 }
191
192 char tableFilename[1024];
193 sprintf(tableFilename, "%s/%s-table-%lu-%lu.png", params.outputDirectory, params.outputBase, params.pixelSize, params.pixelSize);
194
195 if (stbi_write_png(tableFilename, params.characterCount * params.pixelSize, params.pixelSize, 2, alphaImageData, alphaImageStride) != 0) {
196
197 return 0;
198 } else {
199 fprintf(stderr, "Unable to open output .png file: '%s'\n", tableFilename);
200 return 5;
201 }
202 } else {
203 fprintf(stderr, "Unable to open output .fnt file: '%s'\n", metricsFilename);
204 return 4;
205 }
206 } else {
207 fprintf(stderr, "Unable to parse font file: '%s'\n", params.inputFilename);
208 return 3;
209 }
210
211 } else {
212 fprintf(stderr, "Unable to find font file: '%s'\n", params.inputFilename);
213 return 2;
214 }
215 } else {
216 char *exeName = basename(argv[0]);
217 fprintf(stderr, "Usage: %s [-d output_directory] [-b output_base] [-p pixel_size] [-a alpha_threshold] [-cf character_file.txt] [-c characters] <font_file.ttf>\n", exeName);
218 return 1;
219 }
220 }