1 /* TTF 2 Playdate FNT */
2 /* Ben Sherratt, 2024 */
3 /* Donationware: https://ko-fi.com/btscx */
5 /* With thanks to Sean Barrett and friends for stb_image_write and stb_truetype */
7 /* To use: compile this single file with a C compiler of your choice! */
8 /* clang -o ttf2pdfnt ttf2pdfnt.c && ./ttf2pdfnt */
10 #define STB_IMAGE_WRITE_IMPLEMENTATION
11 #include "stb_image_write.h"
13 #define STB_TRUETYPE_IMPLEMENTATION
14 #include "stb_truetype.h"
22 char *outputDirectory
;
24 unsigned long pixelSize
;
27 size_t characterCount
;
29 typedef struct Parameters Parameters
;
31 bool parseParameters(Parameters
*parameters
, int argc
, char *argv
[]) {
32 *parameters
= (Parameters
) {
33 .inputFilename
= NULL
,
34 .outputDirectory
= NULL
,
38 .characters
= "ABCDEFGHIJKLMNOPQRSTUWVXYZabcdefghijklmnopqrstuvwxyz 0123456789,./?!",
41 parameters
->characterCount
= strlen(parameters
->characters
);
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
];
52 } else if (strcmp(parameter
, "-b") == 0) {
53 if (i
+ 1 < argc
&& argv
[i
+ 1][0] != '-') {
54 parameters
->outputBase
= argv
[++i
];
58 } else if (strcmp(parameter
, "-p") == 0) {
59 if (i
+ 1 < argc
&& argv
[i
+ 1][0] != '-') {
60 parameters
->pixelSize
= atoi(argv
[++i
]);
64 } else if (strcmp(parameter
, "-a") == 0) {
65 if (i
+ 1 < argc
&& argv
[i
+ 1][0] != '-') {
66 parameters
->alphaThreshold
= atoi(argv
[++i
]);
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
);
81 fseek(charactersFile
, 0, SEEK_SET
);
82 fread((void *)parameters
->characters
, 1, characterCount
, charactersFile
);
84 fclose(charactersFile
);
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
;
101 parameters
->inputFilename
= argv
[i
];
105 if (error
== false && parameters
->inputFilename
!= NULL
) {
106 if (parameters
->outputBase
== NULL
) {
107 parameters
->outputBase
= basename(parameters
->inputFilename
);
110 if (parameters
->outputDirectory
== NULL
) {
111 parameters
->outputDirectory
= ".";
120 int main(int argc
, char *argv
[]) {
122 bool success
= parseParameters(¶ms
, argc
, argv
);
125 FILE *fontFile
= fopen(params
.inputFilename
, "rb");
127 if (fontFile
!= NULL
) {
128 fseek(fontFile
, 0, SEEK_END
);
129 size_t fontFileSize
= ftell(fontFile
);
130 fseek(fontFile
, 0, SEEK_SET
);
132 uint8_t *fontData
= (uint8_t *)malloc(fontFileSize
);
133 fread(fontData
, 1, fontFileSize
, fontFile
);
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
);
142 float scale
= stbtt_ScaleForPixelHeight(&ttfFontInfo
, params
.pixelSize
);
144 int ascent
, descent
, lineGap
;
145 stbtt_GetFontVMetrics(&ttfFontInfo
, &ascent
, &descent
, &lineGap
);
147 int baseline
= (float)ascent
* scale
;
149 char metricsFilename
[1024];
150 sprintf(metricsFilename
, "%s/%s.fnt", params
.outputDirectory
, params
.outputBase
);
152 FILE *metricsFile
= fopen(metricsFilename
, "w");
153 if (metricsFile
!= NULL
) {
154 fprintf(metricsFile
, "tracking=1\n\n");
156 for (int c
= 0; c
< params
.characterCount
; ++c
) {
157 int codepoint
= params
.characters
[c
];
160 stbtt_GetCodepointBitmapBox(&ttfFontInfo
, codepoint
, scale
, scale
, &x0
, &y0
, &x1
, &y1
);
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
);
166 int advanceWidth
, leftSideBearing
;
167 stbtt_GetCodepointHMetrics(&ttfFontInfo
, codepoint
, &advanceWidth
, &leftSideBearing
);
168 advanceWidth
*= scale
;
172 fprintf(metricsFile
, "space\t%u\n", advanceWidth
);
176 fprintf(metricsFile
, "%c\t%u\n", codepoint
, advanceWidth
);
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
);
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
192 char tableFilename
[1024];
193 sprintf(tableFilename
, "%s/%s-table-%lu-%lu.png", params
.outputDirectory
, params
.outputBase
, params
.pixelSize
, params
.pixelSize
);
195 if (stbi_write_png(tableFilename
, params
.characterCount
* params
.pixelSize
, params
.pixelSize
, 2, alphaImageData
, alphaImageStride
) != 0) {
199 fprintf(stderr
, "Unable to open output .png file: '%s'\n", tableFilename
);
203 fprintf(stderr
, "Unable to open output .fnt file: '%s'\n", metricsFilename
);
207 fprintf(stderr
, "Unable to parse font file: '%s'\n", params
.inputFilename
);
212 fprintf(stderr
, "Unable to find font file: '%s'\n", params
.inputFilename
);
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
);