// stb_truetype.h - v0.3 - public domain - 2009 Sean Barrett / RAD Game Tools // // This library processes TrueType files: // parse files // extract glyph metrics // extract glyph shapes // render glyphs to one-channel bitmaps with antialiasing (box filter) // // Todo: // non-MS cmaps // crashproof on bad data // hinting // subpixel positioning when rendering bitmap // cleartype-style AA // // ADDITIONAL CONTRIBUTORS // // Mikko Mononen: compound shape support, more cmap formats // // VERSIONS // // 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) // userdata, malloc-from-userdata, non-zero fill (STB) // 0.2 (2009-03-11) Fix unsigned/signed char warnings // 0.1 (2009-03-09) First public release // // USAGE // // Include this file in whatever places neeed to refer to it. In ONE C/C++ // file, write: // #define STB_TRUETYPE_IMPLEMENTATION // before the #include of this file. This expands out the actual // implementation into that C/C++ file. // // Look at the header-file sections below for the API, but here's a quick skim: // // Simple 3D API (don't ship this, but it's fine for tools and quick start, // and you can cut and paste from it to move to more advanced) // stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture // stbtt_GetBakedQuad() -- compute quad to draw for a given char // // "Load" a font file from a memory buffer (you have to keep the buffer loaded) // stbtt_InitFont() // stbtt_GetFontOffsetForIndex() -- use for TTC font collections // // Render a unicode codepoint to a bitmap // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be // // Character advance/positioning // stbtt_GetCodepointHMetrics() // stbtt_GetFontVMetrics() // // NOTES // // The system uses the raw data found in the .ttf file without changing it // and without building auxiliary data structures. This is a bit inefficient // on little-endian systems (the data is big-endian), but assuming you're // caching the bitmaps or glyph shapes this shouldn't be a big deal. // // It appears to be very hard to programmatically determine what font a // given file is in a general way. I provide an API for this, but I don't // recommend it. // // // SOURCE STATISTICS (based on v0.3, 1800 LOC) // // Documentation & header file 350 LOC \___ 500 LOC documentation // Sample code 140 LOC / // Truetype parsing 580 LOC ---- 600 LOC TrueType // Software rasterization 240 LOC \ . // Curve tesselation 120 LOC \__ 500 LOC Bitmap creation // Bitmap management 70 LOC / // Baked bitmap interface 70 LOC / // Font name matching & access 150 LOC ---- 150 // C runtime library abstraction 60 LOC ---- 60 ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// //// //// SAMPLE PROGRAMS //// // // Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless // #if 0 #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "stb_truetype.h" char ttf_buffer[1 << 20]; unsigned char temp_bitmap[512 * 512]; stbtt_chardata cdata[96]; // ASCII 32..126 is 95 glyphs GLstbtt_uint ftex; void my_stbtt_initfont(void) { fread(ttf_buffer, 1, 1 << 20, fopen("c:/windows/fonts/times.ttf", "rb")); stbtt_BakeFontBitmap(data, 0, 32.0, temp_bitmap, 512, 512, 32, 96, cdata); // no guarantee this fits! // can free ttf_buffer at this point glGenTextures(1, &ftex); glBindTexture(GL_TEXTURE_2D, ftex); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512, 512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); // can free temp_bitmap at this point glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } void my_stbtt_print(float x, float y, char *text) { // assume orthographic projection with units = screen pixels, origin at top left glBindTexture(GL_TEXTURE_2D, ftex); glBegin(GL_QUADS); while (*text) { if (*text >= 32 && *text < 128) { stbtt_aligned_quad q; stbtt_GetBakedQuad(cdata, 512, 512, *text - 32, &x, &y, &q, 1);//1=opengl,0=old d3d glTexCoord2f(q.s0, q.t1); glVertex2f(q.x0, q.y0); glTexCoord2f(q.s1, q.t1); glVertex2f(q.x1, q.y0); glTexCoord2f(q.s1, q.t0); glVertex2f(q.x1, q.y1); glTexCoord2f(q.s0, q.t0); glVertex2f(q.x0, q.y1); } ++text; } glEnd(); } #endif // // ////////////////////////////////////////////////////////////////////////////// // // Complete program (this compiles): get a single bitmap, print as ASCII art // #if 0 #include #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "stb_truetype.h" char ttf_buffer[1 << 25]; int main(int argc, char **argv) { stbtt_fontinfo font; unsigned char *bitmap; int w, h, i, j, c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); fread(ttf_buffer, 1, 1 << 25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0)); bitmap = stbtt_GetCodepointBitmap(&font, 0, stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0, 0); for (j = 0; j < h; ++j) { for (i = 0; i < w; ++i) putchar(" .:ioVM@"[bitmap[j*w + i] >> 5]); putchar('\n'); } return 0; } #endif // // Output: // // .ii. // @@@@@@. // V@Mio@@o // :i. V@V // :oM@@M // :@@@MM@M // @@o o@M // :@@. M@M // @@@o@@@@ // :M@@V:@@. // ////////////////////////////////////////////////////////////////////////////// // // Complete program: print "Hello World!" banner, with bugs // #if 0 int main(int arg, char **argv) { unsigned char screen[20][79]; int i, j, pos = 0; float scale; char *text = "Heljo World!"; fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); stbtt_InitFont(&font, buffer, 0); scale = stbtt_ScaleForPixelHeight(&font, 16); memset(screen, 0, sizeof(screen)); while (*text) { int advance, lsb, x0, y0, x1, y1, newpos, baseline = 13; stbtt_GetCodepointHMetrics(&font, *text, &advance, &lsb); stbtt_GetCodepointBitmapBox(&font, *text, scale, scale, &x0, &y0, &x1, &y1); newpos = pos + (int)(lsb * scale) + x0; stbtt_MakeCodepointBitmap(&font, &screen[baseline + y0][newpos], x1 - x0, y1 - y0, 79, scale, scale, *text); // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong // because this API is really for baking character bitmaps into textures pos += (int)(advance * scale); ++text; } for (j = 0; j < 20; ++j) { for (i = 0; i < 79; ++i) putchar(" .:ioVM@"[screen[j][i] >> 5]); putchar('\n'); } return 0; } #endif ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// //// //// INTEGRATION WITH RUNTIME LIBRARIES //// #ifdef STB_TRUETYPE_IMPLEMENTATION // #define your own (u)stbtt_int8/16/32 before including to override this #ifndef stbtt_uint8 typedef unsigned char stbtt_uint8; typedef signed char stbtt_int8; typedef unsigned short stbtt_uint16; typedef signed short stbtt_int16; typedef unsigned int stbtt_uint32; typedef signed int stbtt_int32; #endif typedef char stbtt__check_size32[sizeof(stbtt_int32) == 4 ? 1 : -1]; typedef char stbtt__check_size16[sizeof(stbtt_int16) == 2 ? 1 : -1]; // #define your own STBTT_sort() to override this to avoid qsort #ifndef STBTT_sort #include #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) #endif // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h #ifndef STBTT_ifloor #include #define STBTT_ifloor(x) ((int) floor(x)) #define STBTT_iceil(x) ((int) ceil(x)) #endif // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h #ifndef STBTT_malloc #include #define STBTT_malloc(x,u) malloc(x) #define STBTT_free(x,u) free(x) #endif #ifndef STBTT_assert #include #define STBTT_assert(x) assert(x) #endif #ifndef STBTT_strlen #include #define STBTT_strlen(x) strlen(x) #endif #ifndef STBTT_memcpy #include #define STBTT_memcpy memcpy #define STBTT_memset memset #endif #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// //// INTERFACE //// //// #ifndef __STB_INCLUDE_STB_TRUETYPE_H__ #define __STB_INCLUDE_STB_TRUETYPE_H__ #ifdef __cplusplus extern "C" { #endif ////////////////////////////////////////////////////////////////////////////// // // TEXTURE BAKING API // // If you use this API, you only have to call two functions ever. // typedef struct { unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap float xoff, yoff, xadvance; } stbtt_bakedchar; extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) float pixel_height, // height of font in pixels unsigned char *pixels, int pw, int ph, // bitmap to be filled in int first_char, int num_chars, // characters to bake stbtt_bakedchar *chardata); // you allocate this, it's num_chars long // if return is positive, the first unused row of the bitmap // if return is negative, returns the negative of the number of characters that fit // if return is 0, no characters fit and no rows were used // This uses a very crappy packing. typedef struct { float x0, y0, s0, t0; // top-left float x1, y1, s1, t1; // bottom-right } stbtt_aligned_quad; extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above int char_index, // character to display float *xpos, float *ypos, // pointers to current position in screen pixel space stbtt_aligned_quad *q, // output: quad to draw int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier // Call GetBakedQuad with char_index = 'character - first_char', and it // creates the quad you need to draw and advances the current position. // It's inefficient; you might want to c&p it and optimize it. ////////////////////////////////////////////////////////////////////////////// // // FONT LOADING // // extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); // Each .ttf file may have more than one font. Each has a sequential index // number starting from 0. Call this function to get the font offset for a // given index; it returns -1 if the index is out of range. A regular .ttf // file will only define one font and it always be at offset 0, so it will // return '0' for index 0, and -1 for all other indices. You can just skip // this step if you know it's that kind of font. // The following structure is defined publically so you can declare one on // the stack or as a global or etc. typedef struct { void *userdata; unsigned char *data; // pointer to .ttf file int fontstart; // offset of start of font int numGlyphs; // number of glyphs, needed for range checking int loca, head, glyf, hhea, hmtx; // table locations as offset from start of .ttf int index_map; // a cmap mapping for our chosen character encoding int indexToLocFormat; // format needed to map from glyph index to glyph } stbtt_fontinfo; extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); // Given an offset into the file that defines a font, this function builds // the necessary cached info for the rest of the system. You must allocate // the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't // need to do anything special to free it, because the contents are a pure // cache with no additional data structures. Returns 0 on failure. ////////////////////////////////////////////////////////////////////////////// // // CHARACTER TO GLYPH-INDEX CONVERSIOn int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); // If you're going to perform multiple operations on the same character // and you want a speed-up, call this function with the character you're // going to process, then use glyph-based functions instead of the // codepoint-based functions. ////////////////////////////////////////////////////////////////////////////// // // CHARACTER PROPERTIES // extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); // computes a scale factor to produce a font whose "height" is 'pixels' tall. // Height is measured as the distance from the highest ascender to the lowest // descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics // and computing: // scale = pixels / (ascent - descent) // so if you prefer to measure height by the ascent only, use a similar calculation. extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); // ascent is the coordinate above the baseline the font extends; descent // is the coordinate below the baseline the font extends (i.e. it is typically negative) // lineGap is the spacing between one row's descent and the next row's ascent... // so you should advance the vertical position by "*ascent - *descent + *lineGap" // these are expressed in unscaled coordinates extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); // leftSideBearing is the offset from the current horizontal position to the left edge of the character // advanceWidth is the offset from the current horizontal position to the next horizontal position // these are expressed in unscaled coordinates extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); // an additional amount to add to the 'advance' value between ch1 and ch2 // @TODO; for now always returns 0! extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); // Gets the bounding box of the visible part of the glyph, in unscaled coordinates extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); // as above, but takes one or more glyph indices for greater efficiency ////////////////////////////////////////////////////////////////////////////// // // GLYPH SHAPES (you probably don't need these, but they have to go before // the bitmaps for C declaration-order reasons) // #ifndef STBTT_vmove // you can predefine these to use different values (but why?) enum { STBTT_vmove = 1, STBTT_vline, STBTT_vcurve }; #endif #ifndef stbtt_vertex // you can predefine this to use different values // (we share this with other code at RAD) #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file typedef struct { stbtt_vertex_type x, y, cx, cy; unsigned char type, padding; } stbtt_vertex; #endif extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); // returns # of vertices and fills *vertices with the pointer to them // these are expressed in "unscaled" coordinates extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); // frees the data allocated above ////////////////////////////////////////////////////////////////////////////// // // BITMAP RENDERING // extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); // frees the bitmap allocated below extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); // allocates a large-enough single-channel 8bpp bitmap and renders the // specified character/glyph at the specified scale into it, with // antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). // *width & *height are filled out with the width & height of the bitmap, // which is stored left-to-right, top-to-bottom. // // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); // the same as above, but you pass in storage for the bitmap in the form // of 'output', with row spacing of 'out_stride' bytes. the bitmap is // clipped to out_w/out_h bytes. call the next function to get the // height and width and positioning info extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); // get the bbox of the bitmap centered around the glyph origin; so the // bitmap width is ix1-ix0, height is iy1-iy0, and location to place // the bitmap top left is (leftSideBearing*scale,iy0). // (Note that the bitmap uses y-increases-down, but the shape uses // y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); //extern void stbtt_get_true_bbox(stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); // @TODO: don't expose this structure typedef struct { int w, h, stride; unsigned char *pixels; } stbtt__bitmap; extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, int x_off, int y_off, int invert, void *userdata); ////////////////////////////////////////////////////////////////////////////// // // Finding the right font... // // You should really just solve this offline, keep your own tables // of what font is what, and don't try to get it out of the .ttf file. // That's because getting it out of the .ttf file is really hard, because // the names in the file can appear in many possible encodings, in many // possible languages, and e.g. if you need a case-insensitive comparison, // the details of that depend on the encoding & language in a complex way // (actually underspecified in truetype, but also gigantic). // // But you can use the provided functions in two possible ways: // stbtt_FindMatchingFont() will use *case-sensitive* comparisons on // unicode-encoded names to try to find the font you want; // you can run this before calling stbtt_InitFont() // // stbtt_GetFontNameString() lets you get any of the various strings // from the file yourself and do your own comparisons on them. // You have to have called stbtt_InitFont() first. extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); // returns the offset (not index) of the font that matches, or -1 if none // if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". // if you use any other flag, use a font name like "Arial"; this checks // the 'macStyle' header field; i don't know if fonts set this consistently #define STBTT_MACSTYLE_DONTCARE 0 #define STBTT_MACSTYLE_BOLD 1 #define STBTT_MACSTYLE_ITALIC 2 #define STBTT_MACSTYLE_UNDERSCORE 4 #define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); // returns 1/0 whether the first string interpreted as utf8 is identical to // the second string interpreted as big-endian utf16... useful for strings from next func extern char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); // returns the string (which may be big-endian double byte, e.g. for unicode) // and puts the length in bytes in *length. // // some of the values for the IDs are below; for more see the truetype spec: // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html // http://www.microsoft.com/typography/otspec/name.htm enum { // platformID STBTT_PLATFORM_ID_UNICODE = 0, STBTT_PLATFORM_ID_MAC = 1, STBTT_PLATFORM_ID_ISO = 2, STBTT_PLATFORM_ID_MICROSOFT = 3 }; enum { // encodingID for STBTT_PLATFORM_ID_UNICODE STBTT_UNICODE_EID_UNICODE_1_0 = 0, STBTT_UNICODE_EID_UNICODE_1_1 = 1, STBTT_UNICODE_EID_ISO_10646 = 2, STBTT_UNICODE_EID_UNICODE_2_0_BMP = 3, STBTT_UNICODE_EID_UNICODE_2_0_FULL = 4, }; enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT STBTT_MS_EID_SYMBOL = 0, STBTT_MS_EID_UNICODE_BMP = 1, STBTT_MS_EID_SHIFTJIS = 2, STBTT_MS_EID_UNICODE_FULL = 10, }; enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes STBTT_MAC_EID_ROMAN = 0, STBTT_MAC_EID_ARABIC = 4, STBTT_MAC_EID_JAPANESE = 1, STBTT_MAC_EID_HEBREW = 5, STBTT_MAC_EID_CHINESE_TRAD = 2, STBTT_MAC_EID_GREEK = 6, STBTT_MAC_EID_KOREAN = 3, STBTT_MAC_EID_RUSSIAN = 7, }; enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs STBTT_MS_LANG_ENGLISH = 0x0409, STBTT_MS_LANG_ITALIAN = 0x0410, STBTT_MS_LANG_CHINESE = 0x0804, STBTT_MS_LANG_JAPANESE = 0x0411, STBTT_MS_LANG_DUTCH = 0x0413, STBTT_MS_LANG_KOREAN = 0x0412, STBTT_MS_LANG_FRENCH = 0x040c, STBTT_MS_LANG_RUSSIAN = 0x0419, STBTT_MS_LANG_GERMAN = 0x0407, STBTT_MS_LANG_SPANISH = 0x0409, STBTT_MS_LANG_HEBREW = 0x040d, STBTT_MS_LANG_SWEDISH = 0x041D, }; enum { // languageID for STBTT_PLATFORM_ID_MAC STBTT_MAC_LANG_ENGLISH = 0, STBTT_MAC_LANG_JAPANESE = 11, STBTT_MAC_LANG_ARABIC = 12, STBTT_MAC_LANG_KOREAN = 23, STBTT_MAC_LANG_DUTCH = 4, STBTT_MAC_LANG_RUSSIAN = 32, STBTT_MAC_LANG_FRENCH = 1, STBTT_MAC_LANG_SPANISH = 6, STBTT_MAC_LANG_GERMAN = 2, STBTT_MAC_LANG_SWEDISH = 5, STBTT_MAC_LANG_HEBREW = 10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED = 33, STBTT_MAC_LANG_ITALIAN = 3, STBTT_MAC_LANG_CHINESE_TRAD = 19, }; #ifdef __cplusplus } #endif #endif // __STB_INCLUDE_STB_TRUETYPE_H__ /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// //// IMPLEMENTATION //// //// #ifdef STB_TRUETYPE_IMPLEMENTATION ////////////////////////////////////////////////////////////////////////// // // accessors to parse data from file // // on platforms that don't allow misaligned reads, if we want to allow // truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE #define ttBYTE(p) (* (stbtt_uint8 *) (p)) #define ttCHAR(p) (* (stbtt_int8 *) (p)) #define ttFixed(p) ttLONG(p) #if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) #define ttSHORT(p) (* (stbtt_int16 *) (p)) #define ttULONG(p) (* (stbtt_uint32 *) (p)) #define ttLONG(p) (* (stbtt_int32 *) (p)) #else stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0] * 256 + p[1]; } stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0] * 256 + p[1]; } stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; } stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; } #endif #define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) #define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) static int stbtt__isfont(const stbtt_uint8 *font) { // check the version number if (stbtt_tag(font, "1")) return 1; // TrueType 1 if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF if (stbtt_tag4(font, 0, 1, 0, 0)) return 1; // OpenType 1.0 return 0; } // @OPTIMIZE: binary search static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) { stbtt_int32 num_tables = ttUSHORT(data + fontstart + 4); stbtt_uint32 tabledir = fontstart + 12; stbtt_int32 i; for (i = 0; i < num_tables; ++i) { stbtt_uint32 loc = tabledir + 16 * i; if (stbtt_tag(data + loc + 0, tag)) return ttULONG(data + loc + 8); } return 0; } int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) { // if it's just a font, there's only one valid index if (stbtt__isfont(font_collection)) return index == 0 ? 0 : -1; // check if it's a TTC if (stbtt_tag(font_collection, "ttcf")) { // version 1? if (ttULONG(font_collection + 4) == 0x00010000 || ttULONG(font_collection + 4) == 0x00020000) { stbtt_int32 n = ttLONG(font_collection + 8); if (index >= n) return -1; return ttULONG(font_collection + 12 + index * 14); } } return -1; } int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) { stbtt_uint8 *data = (stbtt_uint8 *)data2; stbtt_uint32 cmap, t; stbtt_int32 i, numTables; info->data = data; info->fontstart = fontstart; cmap = stbtt__find_table(data, fontstart, "cmap"); info->loca = stbtt__find_table(data, fontstart, "loca"); info->head = stbtt__find_table(data, fontstart, "head"); info->glyf = stbtt__find_table(data, fontstart, "glyf"); info->hhea = stbtt__find_table(data, fontstart, "hhea"); info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) return 0; t = stbtt__find_table(data, fontstart, "maxp"); if (t) info->numGlyphs = ttUSHORT(data + t + 4); else info->numGlyphs = 0xffff; // find a cmap encoding table we understand *now* to avoid searching // later. (todo: could make this installable) // the same regardless of glyph. numTables = ttUSHORT(data + cmap + 2); info->index_map = 0; for (i = 0; i < numTables; ++i) { stbtt_uint32 encoding_record = cmap + 4 + 8 * i; // find an encoding we understand: switch (ttUSHORT(data + encoding_record)) { case STBTT_PLATFORM_ID_MICROSOFT: switch (ttUSHORT(data + encoding_record + 2)) { case STBTT_MS_EID_UNICODE_BMP: case STBTT_MS_EID_UNICODE_FULL: // MS/Unicode info->index_map = cmap + ttULONG(data + encoding_record + 4); break; } break; } } if (info->index_map == 0) return 0; info->indexToLocFormat = ttUSHORT(data + info->head + 50); return 1; } int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) { stbtt_uint8 *data = info->data; stbtt_uint32 index_map = info->index_map; stbtt_uint16 format = ttUSHORT(data + index_map + 0); if (format == 0) { // apple byte encoding stbtt_int32 bytes = ttUSHORT(data + index_map + 2); if (unicode_codepoint < bytes - 6) return ttBYTE(data + index_map + 6 + unicode_codepoint); return 0; } else if (format == 6) { stbtt_uint32 first = ttUSHORT(data + index_map + 6); stbtt_uint32 count = ttUSHORT(data + index_map + 8); if ((stbtt_uint32)unicode_codepoint >= first && (stbtt_uint32)unicode_codepoint < first + count) return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first) * 2); return 0; } else if (format == 2) { STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean return 0; } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges stbtt_uint16 segcount = ttUSHORT(data + index_map + 6) >> 1; stbtt_uint16 searchRange = ttUSHORT(data + index_map + 8) >> 1; stbtt_uint16 entrySelector = ttUSHORT(data + index_map + 10); stbtt_uint16 rangeShift = ttUSHORT(data + index_map + 12) >> 1; stbtt_uint16 item, offset, start, end; (void)end; // do a binary search of the segments stbtt_uint32 endCount = index_map + 14; stbtt_uint32 search = endCount; if (unicode_codepoint > 0xffff) return 0; // they lie from endCount .. endCount + segCount // but searchRange is the nearest power of two, so... if (unicode_codepoint >= ttUSHORT(data + search + rangeShift * 2)) search += rangeShift * 2; // now decrement to bias correctly to find smallest search -= 2; while (entrySelector) { stbtt_uint16 start, end; searchRange >>= 1; start = ttUSHORT(data + search + 2 + segcount * 2 + 2); end = ttUSHORT(data + search + 2); start = ttUSHORT(data + search + searchRange * 2 + segcount * 2 + 2); end = ttUSHORT(data + search + searchRange * 2); if (unicode_codepoint > end) search += searchRange * 2; --entrySelector; (void)start; } search += 2; item = (stbtt_uint16)((search - endCount) >> 1); STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2 * item)); start = ttUSHORT(data + index_map + 14 + segcount * 2 + 2 + 2 * item); end = ttUSHORT(data + index_map + 14 + 2 + 2 * item); if (unicode_codepoint < start) return 0; offset = ttUSHORT(data + index_map + 14 + segcount * 6 + 2 + 2 * item); if (offset == 0) return unicode_codepoint + ttSHORT(data + index_map + 14 + segcount * 4 + 2 + 2 * item); return ttUSHORT(data + offset + (unicode_codepoint - start) * 2 + index_map + 14 + segcount * 6 + 2 + 2 * item); } else if (format == 12) { stbtt_uint16 ngroups = ttUSHORT(data + index_map + 6); stbtt_int32 low, high; low = 0; high = (stbtt_int32)ngroups; // Binary search the right group. while (low <= high) { stbtt_int32 mid = low + ((high - low) >> 1); // rounds down, so low <= mid < high stbtt_uint32 start_char = ttULONG(data + index_map + 16 + mid * 12); stbtt_uint32 end_char = ttULONG(data + index_map + 16 + mid * 12 + 4); if ((stbtt_uint32)unicode_codepoint < start_char) high = mid - 1; else if ((stbtt_uint32)unicode_codepoint > end_char) low = mid + 1; else { stbtt_uint32 start_glyph = ttULONG(data + index_map + 16 + mid * 12 + 8); return start_glyph + unicode_codepoint - start_char; } } return 0; // not found } // @TODO STBTT_assert(0); return 0; } int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) { return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); } static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int16 x, stbtt_int16 y, stbtt_int16 cx, stbtt_int16 cy) { v->type = type; v->x = x; v->y = y; v->cx = cx; v->cy = cy; } static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) { int g1, g2; if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format if (info->indexToLocFormat == 0) { g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; } else { g1 = info->glyf + ttULONG(info->data + info->loca + glyph_index * 4); g2 = info->glyf + ttULONG(info->data + info->loca + glyph_index * 4 + 4); } return g1 == g2 ? -1 : g1; // if length is 0, return -1 } int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { int g = stbtt__GetGlyfOffset(info, glyph_index); if (g < 0) return 0; if (x0) *x0 = ttSHORT(info->data + g + 2); if (y0) *y0 = ttSHORT(info->data + g + 4); if (x1) *x1 = ttSHORT(info->data + g + 6); if (y1) *y1 = ttSHORT(info->data + g + 8); return 1; } int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) { return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info, codepoint), x0, y0, x1, y1); } int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { stbtt_int16 numberOfContours; stbtt_uint8 *endPtsOfContours; stbtt_uint8 *data = info->data; stbtt_vertex *vertices = 0; int num_vertices = 0; int g = stbtt__GetGlyfOffset(info, glyph_index); *pvertices = NULL; if (g < 0) return 0; numberOfContours = ttSHORT(data + g); if (numberOfContours > 0) { stbtt_uint8 flags = 0, flagcount; stbtt_int32 ins, i, j = 0, m, n, next_move, was_off = 0, off; stbtt_int16 x, y, cx, cy, sx, sy; stbtt_uint8 *points; endPtsOfContours = (data + g + 10); ins = ttUSHORT(data + g + 10 + numberOfContours * 2); points = data + g + 10 + numberOfContours * 2 + 2 + ins; n = 1 + ttUSHORT(endPtsOfContours + numberOfContours * 2 - 2); m = n + numberOfContours; // a loose bound on how many vertices we might need vertices = (stbtt_vertex *)STBTT_malloc(m * sizeof(vertices[0]), info->userdata); if (vertices == 0) return 0; next_move = 0; flagcount = 0; // in first pass, we load uninterpreted data into the allocated array // above, shifted to the end of the array so we won't overwrite it when // we create our final data starting from the front off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated // first load flags for (i = 0; i < n; ++i) { if (flagcount == 0) { flags = *points++; if (flags & 8) flagcount = *points++; } else --flagcount; vertices[off + i].type = flags; } // now load x coordinates x = 0; for (i = 0; i < n; ++i) { flags = vertices[off + i].type; if (flags & 2) { stbtt_int16 dx = *points++; x += (flags & 16) ? dx : -dx; // ??? } else { if (!(flags & 16)) { x = x + (stbtt_int16)(points[0] * 256 + points[1]); points += 2; } } vertices[off + i].x = x; } // now load y coordinates y = 0; for (i = 0; i < n; ++i) { flags = vertices[off + i].type; if (flags & 4) { stbtt_int16 dy = *points++; y += (flags & 32) ? dy : -dy; // ??? } else { if (!(flags & 32)) { y = y + (stbtt_int16)(points[0] * 256 + points[1]); points += 2; } } vertices[off + i].y = y; } // now convert them to our format num_vertices = 0; sx = sy = cx = cy = 0; for (i = 0; i < n; ++i) { flags = vertices[off + i].type; x = (stbtt_int16)vertices[off + i].x; y = (stbtt_int16)vertices[off + i].y; if (next_move == i) { // when we get to the end, we have to close the shape explicitly if (i != 0) { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, cx, cy); else stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, sx, sy, 0, 0); } // now start the new one stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove, x, y, 0, 0); next_move = 1 + ttUSHORT(endPtsOfContours + j * 2); ++j; was_off = 0; sx = x; sy = y; } else { if (!(flags & 1)) { // if it's a curve if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx + x) >> 1, (cy + y) >> 1, cx, cy); cx = x; cy = y; was_off = 1; } else { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x, y, cx, cy); else stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x, y, 0, 0); was_off = 0; } } } if (i != 0) { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, cx, cy); else stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, sx, sy, 0, 0); } } else if (numberOfContours == -1) { // Compound shapes. int more = 1; stbtt_uint8 *comp = data + g + 10; num_vertices = 0; vertices = 0; while (more) { stbtt_uint16 flags, gidx; int comp_num_verts = 0, i; stbtt_vertex *comp_verts = 0, *tmp = 0; float mtx[6] = { 1,0,0,1,0,0 }, m, n; flags = ttSHORT(comp); comp += 2; gidx = ttSHORT(comp); comp += 2; if (flags & 2) { // XY values if (flags & 1) { // shorts mtx[4] = ttSHORT(comp); comp += 2; mtx[5] = ttSHORT(comp); comp += 2; } else { mtx[4] = ttCHAR(comp); comp += 1; mtx[5] = ttCHAR(comp); comp += 1; } } else { // @TODO handle matching point STBTT_assert(0); } if (flags & (1 << 3)) { // WE_HAVE_A_SCALE mtx[0] = mtx[3] = ttSHORT(comp) / 16384.0f; comp += 2; mtx[1] = mtx[2] = 0; } else if (flags & (1 << 6)) { // WE_HAVE_AN_X_AND_YSCALE mtx[0] = ttSHORT(comp) / 16384.0f; comp += 2; mtx[1] = mtx[2] = 0; mtx[3] = ttSHORT(comp) / 16384.0f; comp += 2; } else if (flags & (1 << 7)) { // WE_HAVE_A_TWO_BY_TWO mtx[0] = ttSHORT(comp) / 16384.0f; comp += 2; mtx[1] = ttSHORT(comp) / 16384.0f; comp += 2; mtx[2] = ttSHORT(comp) / 16384.0f; comp += 2; mtx[3] = ttSHORT(comp) / 16384.0f; comp += 2; } // Find transformation scales. m = (float)sqrt(mtx[0] * mtx[0] + mtx[1] * mtx[1]); n = (float)sqrt(mtx[2] * mtx[2] + mtx[3] * mtx[3]); // Get indexed glyph. comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); if (comp_num_verts > 0) { // Transform vertices. for (i = 0; i < comp_num_verts; ++i) { stbtt_vertex* v = &comp_verts[i]; stbtt_vertex_type x, y; x = v->x; y = v->y; v->x = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); v->y = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); x = v->cx; y = v->cy; v->cx = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); v->cy = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); } // Append vertices. tmp = (stbtt_vertex*)STBTT_malloc((num_vertices + comp_num_verts)*sizeof(stbtt_vertex), info->userdata); if (!tmp) { if (vertices) STBTT_free(vertices, info->userdata); if (comp_verts) STBTT_free(comp_verts, info->userdata); return 0; } if (num_vertices > 0) memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); memcpy(tmp + num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); if (vertices) STBTT_free(vertices, info->userdata); vertices = tmp; STBTT_free(comp_verts, info->userdata); num_vertices += comp_num_verts; } // More components ? more = flags & (1 << 5); } } else if (numberOfContours < 0) { // @TODO other compound variations? STBTT_assert(0); } else { // numberOfCounters == 0, do nothing } *pvertices = vertices; return num_vertices; } void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) { stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data + info->hhea + 34); if (glyph_index < numOfLongHorMetrics) { if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4 * glyph_index); if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4 * glyph_index + 2); } else { if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4 * (numOfLongHorMetrics - 1)); if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4 * numOfLongHorMetrics + 2 * (glyph_index - numOfLongHorMetrics)); } } int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo * /*info*/, int /*glyph1*/, int /*glyph2*/) { return 0; } int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo * /*info*/, int /*ch1*/, int /*ch2*/) { return 0; } void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) { stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info, codepoint), advanceWidth, leftSideBearing); } void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) { if (ascent) *ascent = ttSHORT(info->data + info->hhea + 4); if (descent) *descent = ttSHORT(info->data + info->hhea + 6); if (lineGap) *lineGap = ttSHORT(info->data + info->hhea + 8); } float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) { int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); return (float)height / fheight; } void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) { STBTT_free(v, info->userdata); } ////////////////////////////////////////////////////////////////////////////// // // antialiasing software rasterizer // void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) { int x0, y0, x1, y1; if (!stbtt_GetGlyphBox(font, glyph, &x0, &y0, &x1, &y1)) x0 = y0 = x1 = y1 = 0; // e.g. space character // now move to integral bboxes (treating pixels as little squares, what pixels get touched)? if (ix0) *ix0 = STBTT_ifloor(x0 * scale_x); if (iy0) *iy0 = -STBTT_iceil(y1 * scale_y); if (ix1) *ix1 = STBTT_iceil(x1 * scale_x); if (iy1) *iy1 = -STBTT_ifloor(y0 * scale_y); } void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetGlyphBitmapBox(font, stbtt_FindGlyphIndex(font, codepoint), scale_x, scale_y, ix0, iy0, ix1, iy1); } typedef struct stbtt__edge { float x0, y0, x1, y1; int invert; } stbtt__edge; typedef struct stbtt__active_edge { int x, dx; float ey; struct stbtt__active_edge *next; int valid; } stbtt__active_edge; #define FIXSHIFT 10 #define FIX (1 << FIXSHIFT) #define FIXMASK (FIX-1) static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) { stbtt__active_edge *z = (stbtt__active_edge *)STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); STBTT_assert(e->y0 <= start_point); if (!z) return z; // round dx down to avoid going too far if (dxdy < 0) z->dx = -STBTT_ifloor(FIX * -dxdy); else z->dx = STBTT_ifloor(FIX * dxdy); z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); z->x -= off_x * FIX; z->ey = e->y1; z->next = 0; z->valid = e->invert ? 1 : -1; return z; } // note: this routine clips fills that extend off the edges... ideally this // wouldn't happen, but it could happen if the truetype glyph bounding boxes // are wrong, or if the user supplies a too-small bitmap static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) { // non-zero winding fill int x0 = 0, w = 0; while (e) { if (w == 0) { // if we're currently at zero, we need to record the edge start point x0 = e->x; w += e->valid; } else { int x1 = e->x; w += e->valid; // if we went to zero, we need to draw if (w == 0) { int i = x0 >> FIXSHIFT; int j = x1 >> FIXSHIFT; if (i < len && j >= 0) { if (i == j) { // x0,x1 are the same pixel, so compute combined coverage scanline[i] = scanline[i] + (stbtt_uint8)((x1 - x0) * max_weight >> FIXSHIFT); } else { if (i >= 0) // add antialiasing for x0 scanline[i] = scanline[i] + (stbtt_uint8)(((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); else i = -1; // clip if (j < len) // add antialiasing for x1 scanline[j] = scanline[j] + (stbtt_uint8)(((x1 & FIXMASK) * max_weight) >> FIXSHIFT); else j = len; // clip for (++i; i < j; ++i) // fill pixels between x0 and x1 scanline[i] = scanline[i] + (stbtt_uint8)max_weight; } } } } e = e->next; } } static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { stbtt__active_edge *active = NULL; int y, j = 0; int max_weight = (255 / vsubsample); // weight per vertical scanline int s; // vertical subsample index unsigned char scanline_data[512], *scanline; if (result->w > 512) scanline = (unsigned char *)STBTT_malloc(result->w, userdata); else scanline = scanline_data; y = off_y * vsubsample; e[n].y0 = (off_y + result->h) * (float)vsubsample + 1; while (j < result->h) { STBTT_memset(scanline, 0, result->w); for (s = 0; s < vsubsample; ++s) { // find center of pixel for this scanline float scan_y = y + 0.5f; stbtt__active_edge **step = &active; // update all active edges; // remove all active edges that terminate before the center of this scanline while (*step) { stbtt__active_edge * z = *step; if (z->ey <= scan_y) { *step = z->next; // delete from list STBTT_assert(z->valid); z->valid = 0; STBTT_free(z, userdata); } else { z->x += z->dx; // advance to position for current scanline step = &((*step)->next); // advance through list } } // resort the list if needed for (;;) { int changed = 0; step = &active; while (*step && (*step)->next) { if ((*step)->x > (*step)->next->x) { stbtt__active_edge *t = *step; stbtt__active_edge *q = t->next; t->next = q->next; q->next = t; *step = q; changed = 1; } step = &(*step)->next; } if (!changed) break; } // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline while (e->y0 <= scan_y) { if (e->y1 > scan_y) { stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); // find insertion point if (active == NULL) active = z; else if (z->x < active->x) { // insert at front z->next = active; active = z; } else { // find thing to insert AFTER stbtt__active_edge *p = active; while (p->next && p->next->x < z->x) p = p->next; // at this point, p->next->x is NOT < z->x z->next = p->next; p->next = z; } } ++e; } // now process all active edges in XOR fashion if (active) stbtt__fill_active_edges(scanline, result->w, active, max_weight); ++y; } STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); ++j; } while (active) { stbtt__active_edge *z = active; active = active->next; STBTT_free(z, userdata); } if (scanline != scanline_data) STBTT_free(scanline, userdata); } static int stbtt__edge_compare(const void *p, const void *q) { stbtt__edge *a = (stbtt__edge *)p; stbtt__edge *b = (stbtt__edge *)q; if (a->y0 < b->y0) return -1; if (a->y0 > b->y0) return 1; return 0; } typedef struct { float x, y; } stbtt__point; static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, int off_x, int off_y, int invert, void *userdata) { float y_scale_inv = invert ? -scale_y : scale_y; stbtt__edge *e; int n, i, j, k, m; int vsubsample = result->h < 8 ? 15 : 5; // vsubsample should divide 255 evenly; otherwise we won't reach full opacity // now we have to blow out the windings into explicit edge lists n = 0; for (i = 0; i < windings; ++i) n += wcount[i]; e = (stbtt__edge *)STBTT_malloc(sizeof(*e) * (n + 1), userdata); // add an extra one as a sentinel if (e == 0) return; n = 0; m = 0; for (i = 0; i < windings; ++i) { stbtt__point *p = pts + m; m += wcount[i]; j = wcount[i] - 1; for (k = 0; k < wcount[i]; j = k++) { int a = k, b = j; // skip the edge if horizontal if (p[j].y == p[k].y) continue; // add edge from j to k to the list e[n].invert = 0; if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { e[n].invert = 1; a = j, b = k; } e[n].x0 = p[a].x * scale_x; e[n].y0 = p[a].y * y_scale_inv * vsubsample; e[n].x1 = p[b].x * scale_x; e[n].y1 = p[b].y * y_scale_inv * vsubsample; ++n; } } // now sort the edges by their highest point (should snap to integer, and then by x) STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); STBTT_free(e, userdata); } static void stbtt__add_point(stbtt__point *points, int n, float x, float y) { if (!points) return; // during first pass, it's unallocated points[n].x = x; points[n].y = y; } // tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) { // midpoint float mx = (x0 + 2 * x1 + x2) / 4; float my = (y0 + 2 * y1 + y2) / 4; // versus directly drawn line float dx = (x0 + x2) / 2 - mx; float dy = (y0 + y2) / 2 - my; if (n > 16) // 65536 segments on one curve better be enough! return 1; if (dx*dx + dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA stbtt__tesselate_curve(points, num_points, x0, y0, (x0 + x1) / 2.0f, (y0 + y1) / 2.0f, mx, my, objspace_flatness_squared, n + 1); stbtt__tesselate_curve(points, num_points, mx, my, (x1 + x2) / 2.0f, (y1 + y2) / 2.0f, x2, y2, objspace_flatness_squared, n + 1); } else { stbtt__add_point(points, *num_points, x2, y2); *num_points = *num_points + 1; } return 1; } // returns number of contours stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) { stbtt__point *points = 0; int num_points = 0; float objspace_flatness_squared = objspace_flatness * objspace_flatness; int i, n = 0, start = 0, pass; // count how many "moves" there are to get the contour count for (i = 0; i < num_verts; ++i) if (vertices[i].type == STBTT_vmove) ++n; *num_contours = n; if (n == 0) return 0; *contour_lengths = (int *)STBTT_malloc(sizeof(**contour_lengths) * n, userdata); if (*contour_lengths == 0) { *num_contours = 0; return 0; } // make two passes through the points so we don't need to realloc for (pass = 0; pass < 2; ++pass) { float x = 0, y = 0; if (pass == 1) { points = (stbtt__point *)STBTT_malloc(num_points * sizeof(points[0]), userdata); if (points == NULL) goto error; } num_points = 0; n = -1; for (i = 0; i < num_verts; ++i) { switch (vertices[i].type) { case STBTT_vmove: // start the next contour if (n >= 0) (*contour_lengths)[n] = num_points - start; ++n; start = num_points; x = vertices[i].x, y = vertices[i].y; stbtt__add_point(points, num_points++, x, y); break; case STBTT_vline: x = vertices[i].x, y = vertices[i].y; stbtt__add_point(points, num_points++, x, y); break; case STBTT_vcurve: stbtt__tesselate_curve(points, &num_points, x, y, vertices[i].cx, vertices[i].cy, vertices[i].x, vertices[i].y, objspace_flatness_squared, 0); x = vertices[i].x, y = vertices[i].y; break; } } (*contour_lengths)[n] = num_points - start; } return points; error: STBTT_free(points, userdata); STBTT_free(*contour_lengths, userdata); *contour_lengths = 0; *num_contours = 0; return NULL; } void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, int x_off, int y_off, int invert, void *userdata) { float scale = scale_x > scale_y ? scale_y : scale_x; int winding_count, *winding_lengths; stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); if (windings) { stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, x_off, y_off, invert, userdata); STBTT_free(winding_lengths, userdata); STBTT_free(windings, userdata); } } void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) { STBTT_free(bitmap, userdata); } unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) { int ix0, iy0, ix1, iy1; stbtt__bitmap gbm; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); if (scale_x == 0) scale_x = scale_y; if (scale_y == 0) { if (scale_x == 0) return NULL; scale_y = scale_x; } stbtt_GetGlyphBitmapBox(info, glyph, scale_x, scale_y, &ix0, &iy0, &ix1, &iy1); // now we get the size gbm.w = (ix1 - ix0); gbm.h = (iy1 - iy0); gbm.pixels = NULL; // in case we error if (width) *width = gbm.w; if (height) *height = gbm.h; if (xoff) *xoff = ix0; if (yoff) *yoff = iy0; if (gbm.w && gbm.h) { gbm.pixels = (unsigned char *)STBTT_malloc(gbm.w * gbm.h, info->userdata); if (gbm.pixels) { gbm.stride = gbm.w; stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, ix0, iy0, 1, info->userdata); } } STBTT_free(vertices, info->userdata); return gbm.pixels; } void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) { int ix0, iy0; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); stbtt__bitmap gbm; stbtt_GetGlyphBitmapBox(info, glyph, scale_x, scale_y, &ix0, &iy0, 0, 0); gbm.pixels = output; gbm.w = out_w; gbm.h = out_h; gbm.stride = out_stride; if (gbm.w && gbm.h) stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, ix0, iy0, 1, info->userdata); STBTT_free(vertices, info->userdata); } unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphBitmap(info, scale_x, scale_y, stbtt_FindGlyphIndex(info, codepoint), width, height, xoff, yoff); } void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) { stbtt_MakeGlyphBitmap(info, output, out_w, out_h, out_stride, scale_x, scale_y, stbtt_FindGlyphIndex(info, codepoint)); } ////////////////////////////////////////////////////////////////////////////// // // bitmap baking // // This is SUPER-SHITTY packing to keep source code small extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) float pixel_height, // height of font in pixels unsigned char *pixels, int pw, int ph, // bitmap to be filled in int first_char, int num_chars, // characters to bake stbtt_bakedchar *chardata) { float scale; int x, y, bottom_y, i; stbtt_fontinfo f; stbtt_InitFont(&f, data, offset); STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels x = y = 1; bottom_y = 1; scale = stbtt_ScaleForPixelHeight(&f, pixel_height); for (i = 0; i < num_chars; ++i) { int advance, lsb, x0, y0, x1, y1, gw, gh; int g = stbtt_FindGlyphIndex(&f, first_char + i); stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); stbtt_GetGlyphBitmapBox(&f, g, scale, scale, &x0, &y0, &x1, &y1); gw = x1 - x0; gh = y1 - y0; if (x + gw + 1 >= pw) y = bottom_y, x = 1; // advance to next row if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row return -i; STBTT_assert(x + gw < pw); STBTT_assert(y + gh < ph); stbtt_MakeGlyphBitmap(&f, pixels + x + y*pw, gw, gh, pw, scale, scale, g); chardata[i].x0 = (stbtt_int16)x; chardata[i].y0 = (stbtt_int16)y; chardata[i].x1 = (stbtt_int16)(x + gw); chardata[i].y1 = (stbtt_int16)(y + gh); chardata[i].xadvance = scale * advance; chardata[i].xoff = (float)x0; chardata[i].yoff = (float)y0; x = x + gw + 2; if (y + gh + 2 > bottom_y) bottom_y = y + gh + 2; } return bottom_y; } void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) { float d3d_bias = opengl_fillrule ? 0 : -0.5f; float ipw = 1.0f / pw, iph = 1.0f / ph; stbtt_bakedchar *b = chardata + char_index; int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); q->x0 = round_x + d3d_bias; q->y0 = round_y + d3d_bias; q->x1 = round_x + b->x1 - b->x0 + d3d_bias; q->y1 = round_y + b->y1 - b->y0 + d3d_bias; q->s0 = b->x0 * ipw; q->t0 = b->y0 * ipw; q->s1 = b->x1 * iph; q->t1 = b->y1 * iph; *xpos += b->xadvance; } ////////////////////////////////////////////////////////////////////////////// // // font name matching -- recommended not to use this // // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) { stbtt_int32 i = 0; // convert utf16 to utf8 and compare the results while converting while (len2) { stbtt_uint16 ch = s2[0] * 256 + s2[1]; if (ch < 0x80) { if (i >= len1) return -1; if (s1[i++] != ch) return -1; } else if (ch < 0x800) { if (i + 1 >= len1) return -1; if (s1[i++] != 0xc0 + (ch >> 6)) return -1; if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; } else if (ch >= 0xd800 && ch < 0xdc00) { stbtt_uint32 c; stbtt_uint16 ch2 = s2[2] * 256 + s2[3]; if (i + 3 >= len1) return -1; c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; if (s1[i++] != 0xf0 + (c >> 18)) return -1; if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; if (s1[i++] != 0x80 + ((c)& 0x3f)) return -1; s2 += 2; // plus another 2 below len2 -= 2; } else if (ch >= 0xdc00 && ch < 0xe000) { return -1; } else { if (i + 2 >= len1) return -1; if (s1[i++] != 0xe0 + (ch >> 12)) return -1; if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; if (s1[i++] != 0x80 + ((ch)& 0x3f)) return -1; } s2 += 2; len2 -= 2; } return i; } int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) { return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*)s1, len1, (stbtt_uint8*)s2, len2); } // returns results in whatever encoding you request... but note that 2-byte encodings // will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) { stbtt_int32 i, count, stringOffset; stbtt_uint8 *fc = font->data; stbtt_uint32 offset = font->fontstart; stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); if (!nm) return NULL; count = ttUSHORT(fc + nm + 2); stringOffset = nm + ttUSHORT(fc + nm + 4); for (i = 0; i < count; ++i) { stbtt_uint32 loc = nm + 6 + 12 * i; if (platformID == ttUSHORT(fc + loc + 0) && encodingID == ttUSHORT(fc + loc + 2) && languageID == ttUSHORT(fc + loc + 4) && nameID == ttUSHORT(fc + loc + 6)) { *length = ttUSHORT(fc + loc + 8); return (char *)(fc + stringOffset + ttUSHORT(fc + loc + 10)); } } return NULL; } static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) { stbtt_int32 i; stbtt_int32 count = ttUSHORT(fc + nm + 2); stbtt_int32 stringOffset = nm + ttUSHORT(fc + nm + 4); for (i = 0; i < count; ++i) { stbtt_uint32 loc = nm + 6 + 12 * i; stbtt_int32 id = ttUSHORT(fc + loc + 6); if (id == target_id) { // find the encoding stbtt_int32 platform = ttUSHORT(fc + loc + 0), encoding = ttUSHORT(fc + loc + 2), language = ttUSHORT(fc + loc + 4); // is this a Unicode encoding? if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { stbtt_int32 slen = ttUSHORT(fc + loc + 8), off = ttUSHORT(fc + loc + 10); // check if there's a prefix match stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc + stringOffset + off, slen); if (matchlen >= 0) { // check for target_id+1 immediately following, with same encoding & language if (i + 1 < count && ttUSHORT(fc + loc + 12 + 6) == next_id && ttUSHORT(fc + loc + 12) == platform && ttUSHORT(fc + loc + 12 + 2) == encoding && ttUSHORT(fc + loc + 12 + 4) == language) { stbtt_int32 slen = ttUSHORT(fc + loc + 12 + 8), off = ttUSHORT(fc + loc + 12 + 10); if (slen == 0) { if (matchlen == nlen) return 1; } else if (matchlen < nlen && name[matchlen] == ' ') { ++matchlen; if (stbtt_CompareUTF8toUTF16_bigendian((char*)(name + matchlen), nlen - matchlen, (char*)(fc + stringOffset + off), slen)) return 1; } } else { // if nothing immediately following if (matchlen == nlen) return 1; } } } // @TODO handle other encodings } } return 0; } static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) { stbtt_int32 nlen = STBTT_strlen((char *)name); stbtt_uint32 nm, hd; if (!stbtt__isfont(fc + offset)) return 0; // check italics/bold/underline flags in macStyle... if (flags) { hd = stbtt__find_table(fc, offset, "head"); if ((ttUSHORT(fc + hd + 44) & 7) != (flags & 7)) return 0; } nm = stbtt__find_table(fc, offset, "name"); if (!nm) return 0; if (flags) { // if we checked the macStyle flags, then just check the family and ignore the subfamily if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; } else { if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; } return 0; } int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) { stbtt_int32 i; for (i = 0;; ++i) { stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); if (off < 0) return off; if (stbtt__matches((stbtt_uint8 *)font_collection, off, (stbtt_uint8*)name_utf8, flags)) return off; } } #endif // STB_TRUETYPE_IMPLEMENTATION