/* * Copyright (c) 2021-24 ColleagueRiley ColleagueRiley@gmail.com * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following r estrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * */ /* preprocessor args make sure ** #define RFONT_IMPLEMENTATION ** - include function defines is in exactly one of your files or arguments #define RFONT_NO_OPENGL - do not define graphics functions (that use opengl) #define RFONT_NO_STDIO - do not include stdio.h #define RFONT_EXTERNAL_STB - load stb_truetype from stb_truetype.h instead of using the internal version #define RFONT_NO_GRAPHICS - do not include any graphics functions at all #define RFONT_RENDER_RGL - use RGL functions for rendering #define RFONT_RENDER_LEGACY - use opengl legacy functions for rendering (if RGL is not chosen) -- NOTE: By default, opengl 3.3 vbos are used for rendering -- */ /* credits : stb_truetype.h - a dependency for RFont, most of (a slightly motified version of) stb_truetype.h is included directly into RFont.h http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - UTF-8 decoding function fontstash - fontstash was used as a refference for some parts */ /* ... = [add code here] BASIC TEMPLATE : #define RFONT_IMPLEMENTATION #include "RFont.h" ... int main () { ... RFont_init(window_width, window_height); RFont_font* font = RFont_font_init("font.ttf"); while (1) { ... RFont_draw_text(font, "text", 100, 100, 20); ... } RFont_font_free(font); RFont_close(); ... } */ #ifndef RFONT_NO_STDIO #include #endif #ifndef RFONT_MALLOC #include #define RFONT_MALLOC malloc #define RFONT_FREE free #endif #include #include #include #if !defined(u8) #if defined(_MSC_VER) || defined(__SYMBIAN32__) typedef unsigned char u8; typedef signed char i8; typedef unsigned short u16; typedef signed short i16; typedef unsigned int u32; typedef signed int i32; typedef unsigned long u64; typedef signed long i64; #else #include typedef uint8_t u8; typedef int8_t i8; typedef uint16_t u16; typedef int16_t i16; typedef uint32_t u32; typedef int32_t i32; typedef uint64_t u64; typedef int64_t i64; #endif #endif #if !defined(b8) typedef u8 b8; #endif /* You can define these yourself if you want to change anything */ #ifndef RFont_texture typedef u32 RFont_texture; #endif #ifndef RFONT_MAX_GLYPHS #define RFONT_MAX_GLYPHS 652 #endif #ifndef RFONT_ATLAS_WIDTH #define RFONT_ATLAS_WIDTH 6000 #endif #ifndef RFONT_ATLAS_HEIGHT #define RFONT_ATLAS_HEIGHT 400 #endif #ifndef RFONT_INIT_TEXT_SIZE #define RFONT_INIT_TEXT_SIZE 500 #endif #ifndef RFONT_INIT_VERTS #define RFONT_INIT_VERTS 1024 * 600 #endif #ifndef RFONT_TEXTFORMAT_MAX_SIZE #define RFONT_TEXTFORMAT_MAX_SIZE 923 #endif #ifndef RFONT_VSNPRINTF #define RFONT_VSNPRINTF vsnprintf #endif #ifndef RFONT_UNUSED #define RFONT_UNUSED(x) (void) (x); #endif /* make sure RFont declares aren't declared twice */ #ifndef RFONT_H #define RFONT_H #ifndef RFont_area typedef struct { u32 w, h; } RFont_area; #endif typedef struct RFont_font RFont_font; typedef struct { u32 codepoint; /* the character (for checking) */ size_t size; /* the size of the glyph */ i32 x, x2; /* coords of the character on the texture */ /* source glyph data */ i32 src; float w, h, x1, y1, advance; } RFont_glyph; /** * @brief Sets the framebuffer size AND runs the graphics init function. * @param width The framebuffer width. * @param height The framebuffer height. */ inline void RFont_init(size_t width, size_t height); /** * @brief Frees data allocated by the RFont for the RFont */ inline void RFont_close(void); /** * @brief Just updates the framebuffer size. * @param width The framebuffer width. * @param height The framebuffer height. */ inline void RFont_update_framebuffer(size_t width, size_t height); #ifndef RFONT_NO_STDIO /** * @brief Init font stucture with a TTF file path. * @param font_name The TTF file path. * @return The `RFont_font` created using the TTF file data. */ inline RFont_font* RFont_font_init(const char* font_name); #endif /** * @brief Init font stucture with raw TTF data. * @param font_data The raw TTF data. * @param auto_free If the memory should be automatically freed by `RFont_font_free`. * @return The `RFont_font` created from the data. */ inline RFont_font* RFont_font_init_data(u8* font_data, b8 auto_free); /** * @brief Free data from the font stucture, including the stucture itself * @param font The font stucture to free */ inline void RFont_font_free(RFont_font* font); /** * @brief Add a character to the font's atlas. * @param font The font to use. * @param ch The character to add to the atlas. * @param size The size of the character. * @return The `RFont_glyph` created from the data and added to the atlas. */ inline RFont_glyph RFont_font_add_char(RFont_font* font, char ch, size_t size); #ifndef RFONT_NO_FMT /** * @brief Formats a string. * @param string The source string * @param ... format data * @return The formatted string */ inline const char* RFont_fmt(const char* string, ...); #endif /** * @brief Add a string to the font's atlas. * @param font The font to use. * @param ch The character to add to the atlas. * @param sizes The supported sizes of the character. * @param sizeLen length of the size array */ inline void RFont_font_add_string(RFont_font* font, const char* string, size_t* sizes, size_t sizeLen); /** * @brief Add a string to the font's atlas based on a given string length. * @param font The font to use. * @param ch The character to add to the atlas. * @param strLen length of the string * @param sizes The supported sizes of the character. * @param sizeLen length of the size array */ inline void RFont_font_add_string_len(RFont_font* font, const char* string, size_t strLen, size_t* sizes, size_t sizeLen); /** * @brief Get the area of the text based on the size using the font. * @param font The font stucture to use for drawing * @param text The string to draw * @param size The size of the text * @return The area of the text based on the size */ inline RFont_area RFont_text_area(RFont_font* font, const char* text, u32 size); /** * @brief Get the area of the text based on the size using the font, using a given length. * @param font The font stucture to use for drawing * @param text The string to draw * @param size The size of the text * @param spacing The spacing of the text * @return The area of the text based on the size */ inline RFont_area RFont_text_area_spacing(RFont_font* font, const char* text, float spacing, u32 size); /** * @brief Get the area of the text based on the size using the font, using a given length. * @param font The font stucture to use for drawing * @param text The string to draw * @param len The length of the string * @param size The size of the text * @param stopNL the number of \n s until it stops (0 = don't stop until the end) * @param spacing The spacing of the text * @return The area of the text based on the size */ inline RFont_area RFont_text_area_len(RFont_font* font, const char* text, size_t len, u32 size, size_t stopNL, float spacing); /** * @brief Draw a text string using the font. * @param font The font stucture to use for drawing * @param text The string to draw * @param x The x position of the text * @param y The y position of the text * @param size The size of the text * @return The area of the text based on the size */ inline RFont_area RFont_draw_text(RFont_font* font, const char* text, float x, float y, u32 size); /** * @brief Draw a text string using the font and a given spacing. * @param font The font stucture to use for drawing * @param text The string to draw * @param x The x position of the text * @param y The y position of the text * @param size The size of the text * @param spacing The spacing of the text * @return The area of the text based on the size */ inline RFont_area RFont_draw_text_spacing(RFont_font* font, const char* text, float x, float y, u32 size, float spacing); /** * @brief Draw a text string using the font using a given length and a given spacing. * @param font The font stucture to use for drawing * @param text The string to draw * @param len The length of the string * @param x The x position of the text * @param y The y position of the text * @param size The size of the text * @param spacing The spacing of the text * @return The area of the text based on the size */ inline RFont_area RFont_draw_text_len(RFont_font* font, const char* text, size_t len, float x, float y, u32 size, float spacing); #define RFont_set_color RFont_render_set_color #ifndef RFONT_NO_GRAPHICS /* if you do not want to use opengl (or want to create your own implemntation of these functions), you'll have to define these yourself and add `#define RFONT_NO_OPENGL` */ inline void RFont_render_set_color(float r, float g, float b, float a); /* set the current rendering color */ inline void RFont_render_init(void); /* any initalizations the renderer needs to do */ inline RFont_texture RFont_create_atlas(u32 atlasWidth, u32 atlasHeight); /* create a bitmap texture based on the given size */ inline void RFont_bitmap_to_atlas(RFont_texture atlas, u8* bitmap, float x, float y, float w, float h); /* add the given bitmap to the texture based on the given coords and size data */ inline void RFont_render_text(RFont_texture atlas, float* verts, float* tcoords, size_t nverts); /* render the text, using the vertices, atlas texture, and texture coords given. */ inline void RFont_render_free(RFont_texture atlas); /* free any memory the renderer might need to free */ /* (if modern opengl is being used) switch to rendering using opengl legacy or not */ inline void RFont_render_legacy(u8 legacy); #endif #endif /* RFONT_H */ #ifdef RFONT_IMPLEMENTATION #ifdef RFONT_EXTERNAL_STB #define STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" #endif #ifndef RFONT_GET_TEXPOSX #define RFONT_GET_TEXPOSX(x) (float)((float)(x) / (float)(RFONT_ATLAS_WIDTH)) #define RFONT_GET_TEXPOSY(y) (float)((float)(y) / (float)(RFONT_ATLAS_HEIGHT)) #endif #ifndef RFONT_GET_WORLD_X #define RFONT_GET_WORLD_X(x, w) (float)((x) / (((w) / 2.0f)) - 1.0f) #define RFONT_GET_WORLD_Y(y, h) (float)(1.0f - ((y) / ((h) / 2.0f))) #endif /* stb defines required by RFont you probably don't care about this part if you're reading just the RFont code */ #ifndef RFONT_EXTERNAL_STB // private structure typedef struct { unsigned char *data; int cursor; int size; } stbtt__buf; typedef struct stbtt_fontinfo stbtt_fontinfo; struct stbtt_fontinfo { 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,kern,gpos,svg; // 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__buf cff; // cff font data stbtt__buf charstrings; // the charstring index stbtt__buf gsubrs; // global charstring subroutines index stbtt__buf subrs; // private charstring subroutines index stbtt__buf fontdicts; // array of font dicts stbtt__buf fdselect; // map from glyph to fontdict }; #ifdef STBTT_STATIC #define STBTT_DEF static #else #define STBTT_DEF extern inline #endif STBTT_DEF i16 ttSHORT(u8 *p); STBTT_DEF u16 ttUSHORT(u8 *p); STBTT_DEF u32 ttULONG(u8 *p); STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); STBTT_DEF unsigned char* stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); #endif /* RFONT_EXTERNAL_STB */ /* END of stb defines required by RFont you probably care about this part */ #ifndef RFONT_NO_FMT #include const char* RFont_fmt(const char* string, ...) { static char output[RFONT_TEXTFORMAT_MAX_SIZE]; va_list args; va_start(args, string); RFONT_VSNPRINTF(output, RFONT_TEXTFORMAT_MAX_SIZE, string, args); va_end(args); return output; } #endif struct RFont_font { stbtt_fontinfo info; /* source stb font */ b8 free_font_memory; float fheight; /* font height from stb */ float descent; /* font descent */ float numOfLongHorMetrics; float space_adv; RFont_glyph glyphs[RFONT_MAX_GLYPHS]; /* glyphs */ size_t glyph_len; RFont_texture atlas; /* atlas texture */ float atlasX; /* the current x position inside the atlas */ }; size_t RFont_width = 0, RFont_height = 0; float* RFont_verts; float* RFont_tcoords; RFont_font* font2; void RFont_update_framebuffer(size_t width, size_t height) { /* set size of the framebuffer (for rendering later on) */ RFont_width = width; RFont_height = height; } void RFont_init(size_t width, size_t height) { RFont_update_framebuffer(width, height); #ifndef RFONT_NO_GRAPHICS /* init any rendering stuff that needs to be initalized (eg. vbo objects) */ RFont_render_init(); #endif RFont_verts = RFONT_MALLOC(sizeof(float) * RFONT_INIT_VERTS); RFont_tcoords = RFONT_MALLOC(sizeof(float) * RFONT_INIT_VERTS); } #ifndef RFONT_NO_STDIO RFont_font* RFont_font_init(const char* font_name) { FILE* ttf_file = fopen(font_name, "rb"); fseek(ttf_file, 0U, SEEK_END); size_t size = ftell(ttf_file); char* ttf_buffer = (char*)RFONT_MALLOC(sizeof(char) * size); fseek(ttf_file, 0U, SEEK_SET); size_t out = fread(ttf_buffer, 1, size, ttf_file); RFONT_UNUSED(out) return RFont_font_init_data((u8*)ttf_buffer, 1); } #endif RFont_font* RFont_font_init_data(u8* font_data, b8 auto_free) { RFont_font* font = (RFont_font*)RFONT_MALLOC(sizeof(RFont_font)); stbtt_InitFont(&font->info, font_data, 0); font->fheight = ttSHORT(font->info.data + font->info.hhea + 4) - ttSHORT(font->info.data + font->info.hhea + 6); font->descent = ttSHORT(font->info.data + font->info.hhea + 6); font->numOfLongHorMetrics = ttUSHORT(font->info.data + font->info.hhea + 34); font->space_adv = ttSHORT(font->info.data + font->info.hmtx + 4 * (u32)(font->numOfLongHorMetrics - 1)); #ifndef RFONT_NO_GRAPHICS font->atlas = RFont_create_atlas(RFONT_ATLAS_WIDTH, RFONT_ATLAS_HEIGHT); #endif font->atlasX = 0; font->glyph_len = 0; font->free_font_memory = auto_free; return font; } void RFont_font_free(RFont_font* font) { #ifndef RFONT_NO_GRAPHICS RFont_render_free(font->atlas); #endif if (font->free_font_memory) RFONT_FREE(font->info.data); RFONT_FREE (font); } void RFont_close(void) { RFONT_FREE(RFont_verts); RFONT_FREE(RFont_tcoords); } /* decode utf8 character to codepoint */ // Copyright (c) 2008-2010 Bjoern Hoehrmann // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. #define RFONT_UTF8_ACCEPT 0 #define RFont_UTF8_REJECT 12 inline static u32 RFont_decode_utf8(u32* state, u32* codep, u32 byte); static u32 RFont_decode_utf8(u32* state, u32* codep, u32 byte) { static const uint8_t utf8d[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 }; uint32_t type = utf8d[byte]; *codep = (*state != RFONT_UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); *state = utf8d[256 + *state * 16 + type]; return *state; } void RFont_font_add_string(RFont_font* font, const char* string, size_t* sizes, size_t sizeLen) { RFont_font_add_string_len(font, string, 0, sizes, sizeLen); } void RFont_font_add_string_len(RFont_font* font, const char* string, size_t strLen, size_t* sizes, size_t sizeLen) { u32 i; char* str; for (str = (char*)string; (!strLen || (size_t)(str - string) < strLen) && *str; str++) for (i = 0; i < sizeLen; i++) RFont_font_add_char(font, *str, sizes[i]); } RFont_glyph RFont_font_add_char(RFont_font* font, char ch, size_t size) { static u32 utf8state = 0, codepoint = 0; if (RFont_decode_utf8(&utf8state, &codepoint, (u8)ch) != RFONT_UTF8_ACCEPT) return (RFont_glyph){0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; u32 i; for (i = 0; i < font->glyph_len; i++) if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == size) return font->glyphs[i]; RFont_glyph* glyph = &font->glyphs[i]; glyph->src = stbtt_FindGlyphIndex(&font->info, codepoint); if (glyph->src == 0 && font2 != NULL && font2->info.data != font->info.data) { stbtt_fontinfo saveInfo = font->info; RFont_font* fakeFont = font; fakeFont->info = font2->info; RFont_glyph g = RFont_font_add_char(fakeFont, 't', size); fakeFont->info = saveInfo; return g; } font->glyph_len++; i32 x0, y0, x1, y1, w = 0, h = 0; if (stbtt_GetGlyphBox(&font->info, glyph->src, &x0, &y0, &x1, &y1) == 0) return (RFont_glyph){0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; float scale = ((float)size) / font->fheight; u8* bitmap = stbtt_GetGlyphBitmapSubpixel(&font->info, 0, scale, 0.0f, 0.0f, glyph->src, &w, &h, 0, 0); glyph->w = (float)w; glyph->h = (float)h; glyph->codepoint = codepoint; glyph->size = size; glyph->x = font->atlasX; glyph->x2 = font->atlasX + glyph->w; glyph->x1 = floorf(x0 * scale); glyph->y1 = floor(-y1 * scale); #ifndef RFONT_NO_GRAPHICS RFont_bitmap_to_atlas(font->atlas, bitmap, font->atlasX, 0, glyph->w, glyph->h); #endif font->atlasX += glyph->w; RFONT_FREE(bitmap); i32 advanceX; if (glyph->src < font->numOfLongHorMetrics) advanceX = ttSHORT(font->info.data + font->info.hmtx + 4 * glyph->src); else advanceX = ttSHORT(font->info.data + font->info.hmtx + 4 * (u32)(font->numOfLongHorMetrics - 1)); glyph->advance = advanceX * scale; return *glyph; } RFont_area RFont_text_area(RFont_font* font, const char* text, u32 size) { return RFont_text_area_len(font, text, 0, size, 0, 0.0f); } RFont_area RFont_text_area_spacing(RFont_font* font, const char* text, float spacing, u32 size) { return RFont_text_area_len(font, text, 0, size, 0, spacing); } RFont_area RFont_text_area_len(RFont_font* font, const char* text, size_t len, u32 size, size_t stopNL, float spacing) { float x = 0; size_t y = 1; char* str; float scale = (((float)size) / font->fheight); float space_adv = (scale * font->space_adv) / 2; for (str = (char*)text; (len == 0 || (size_t)(str - text) < len) && *str; str++) { if (*str == '\n') { if (y == stopNL) return (RFont_area){(u32)x, y * size}; y++; x = 0; continue; } if (*str == ' ' || *str == '\t') { x += space_adv + spacing; continue; } RFont_glyph glyph = RFont_font_add_char(font, *str, size); if (glyph.codepoint == 0 && glyph.size == 0) continue; x += (float)glyph.advance + spacing; } return (RFont_area){(u32)x, y * size}; } RFont_area RFont_draw_text(RFont_font* font, const char* text, float x, float y, u32 size) { return RFont_draw_text_len(font, text, 0, x, y, size, 0.0f); } RFont_area RFont_draw_text_spacing(RFont_font* font, const char* text, float x, float y, u32 size, float spacing) { return RFont_draw_text_len(font, text, 0, x, y, size, spacing); } RFont_area RFont_draw_text_len(RFont_font* font, const char* text, size_t len, float x, float y, u32 size, float spacing) { float* verts = RFont_verts; float* tcoords = RFont_tcoords; float startX = x; float startY = y; y += size; u32 i = 0; u32 tIndex = 0; char* str; float scale = (((float)size) / font->fheight); float space_adv = (scale * font->space_adv) / 2; y -= (-font->descent * scale); for (str = (char*)text; (len == 0 || (size_t)(str - text) < len) && *str; str++) { if (*str == '\n') { x = startX; y += size; continue; } if (*str == ' ' || *str == '\t') { x += space_adv + spacing; continue; } RFont_glyph glyph = RFont_font_add_char(font, *str, size); if (glyph.codepoint == 0 && glyph.size == 0) continue; float realX = x + glyph.x1; float realY = y + glyph.y1; verts[i] = RFONT_GET_WORLD_X((i32)realX, RFont_width); verts[i + 1] = RFONT_GET_WORLD_Y(realY, RFont_height); verts[i + 2] = 0; /* */ verts[i + 3] = RFONT_GET_WORLD_X((i32)realX, RFont_width); verts[i + 4] = RFONT_GET_WORLD_Y(realY + glyph.h , RFont_height); verts[i + 5] = 0; /* */ verts[i + 6] = RFONT_GET_WORLD_X((i32)(realX + glyph.w), RFont_width); verts[i + 7] = RFONT_GET_WORLD_Y(realY + glyph.h , RFont_height); verts[i + 8] = 0; /* */ /* */ verts[i + 9] = RFONT_GET_WORLD_X((i32)(realX + glyph.w), RFont_width); verts[i + 10] = RFONT_GET_WORLD_Y(realY, RFont_height); verts[i + 11] = 0; /* */ verts[i + 12] = RFONT_GET_WORLD_X((i32)realX, RFont_width); verts[i + 13] = RFONT_GET_WORLD_Y(realY, RFont_height); verts[i + 14] = 0; /* */ verts[i + 15] = RFONT_GET_WORLD_X((i32)(realX + glyph.w), RFont_width); verts[i + 16] = RFONT_GET_WORLD_Y(realY + glyph.h , RFont_height); verts[i + 17] = 0; /* texture coords */ //#if defined(RFONT_RENDER_LEGACY) || defined(RFONT_RENDER_RGL) tcoords[tIndex] = RFONT_GET_TEXPOSX(glyph.x); tcoords[tIndex + 1] = 0; //#endif /* */ tcoords[tIndex + 2] = RFONT_GET_TEXPOSX(glyph.x); tcoords[tIndex + 3] = RFONT_GET_TEXPOSY(glyph.h); /* */ tcoords[tIndex + 4] = RFONT_GET_TEXPOSX(glyph.x2); tcoords[tIndex + 5] = RFONT_GET_TEXPOSY(glyph.h); /* */ /* */ tcoords[tIndex + 6] = RFONT_GET_TEXPOSX(glyph.x2); tcoords[tIndex + 7] = 0; /* */ tcoords[tIndex + 8] = RFONT_GET_TEXPOSX(glyph.x); tcoords[tIndex + 9] = 0; /* */ tcoords[tIndex + 10] = RFONT_GET_TEXPOSX(glyph.x2); tcoords[tIndex + 11] = RFONT_GET_TEXPOSY(glyph.h); i += 18; tIndex += 12; x += glyph.advance + spacing; } #ifndef RFONT_NO_GRAPHICS RFont_render_text(font->atlas, verts, tcoords, i / 3); #endif return (RFont_area){(u32)(x - startX), (u32)(y - startY) + (-font->descent * scale)}; } #ifndef __APPLE__ #include #else #include #endif #if !defined(RFONT_NO_OPENGL) && !defined(RFONT_NO_GRAPHICS) #if !defined(RFONT_RENDER_LEGACY) && !defined(RFONT_RENDER_RGL) #define GL_GLEXT_PROTOTYPES #endif #ifndef GL_PERSPECTIVE_CORRECTION_HINT #define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 #endif #ifndef GL_TEXTURE_SWIZZLE_RGBA #define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 #endif #ifndef GL_TEXTURE0 #define GL_TEXTURE0 0x84C0 #endif #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #ifdef RFONT_DEBUG #ifndef GL_DEBUG_TYPE_ERROR #define GL_DEBUG_TYPE_ERROR 0x824C #define GL_DEBUG_OUTPUT 0x92E0 #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 #define GL_INFO_LOG_LENGTH 0x8B84 #endif void RFont_debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* message, const void* userParam) { RFONT_UNUSED(source) RFONT_UNUSED(id) RFONT_UNUSED(severity) RFONT_UNUSED(length) RFONT_UNUSED(userParam) if (type != GL_DEBUG_TYPE_ERROR) return; printf("OpenGL Debug Message: %s\n", message); } void RFont_opengl_getError(void) { GLenum err; while ((err = glGetError()) != GL_NO_ERROR) { switch (err) { case GL_INVALID_ENUM: printf("OpenGL error: GL_INVALID_ENUM\n"); break; case GL_INVALID_VALUE: printf("OpenGL error: GL_INVALID_VALUE\n"); break; case GL_INVALID_OPERATION: printf("OpenGL error: GL_INVALID_OPERATION\n"); break; case GL_STACK_OVERFLOW: printf("OpenGL error: GL_STACK_OVERFLOW\n"); break; case GL_STACK_UNDERFLOW: printf("OpenGL error: GL_STACK_UNDERFLOW\n"); break; default: printf("OpenGL error: Unknown error code 0x%x\n", err); break; } exit(1); } } #endif RFont_texture RFont_create_atlas(u32 atlasWidth, u32 atlasHeight) { #if defined(RFONT_DEBUG) && !defined(RFONT_RENDER_LEGACY) glEnable(GL_DEBUG_OUTPUT); #endif u32 id = 0; glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1, &id); glBindTexture(GL_TEXTURE_2D, id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); u8* data = (u8*)calloc(atlasWidth * atlasHeight * 4, sizeof(u8)); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, atlasWidth, atlasHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); RFONT_FREE(data); glBindTexture(GL_TEXTURE_2D, id); static GLint swizzleRgbaParams[4] = {GL_ONE, GL_ONE, GL_ONE, GL_RED}; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleRgbaParams); glBindTexture(GL_TEXTURE_2D, 0); return id; } #ifndef GL_UNPACK_ROW_LENGTH #define GL_UNPACK_ROW_LENGTH 0x0CF2 #define GL_UNPACK_SKIP_PIXELS 0x0CF4 #define GL_UNPACK_SKIP_ROWS 0x0CF3 #endif void RFont_push_pixel_values(GLint alignment, GLint rowLength, GLint skipPixels, GLint skipRows); void RFont_push_pixel_values(GLint alignment, GLint rowLength, GLint skipPixels, GLint skipRows) { glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength); glPixelStorei(GL_UNPACK_SKIP_PIXELS, skipPixels); glPixelStorei(GL_UNPACK_SKIP_ROWS, skipRows); } void RFont_bitmap_to_atlas(RFont_texture atlas, u8* bitmap, float x, float y, float w, float h) { glEnable(GL_TEXTURE_2D); GLint alignment, rowLength, skipPixels, skipRows; glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment); glGetIntegerv(GL_UNPACK_ROW_LENGTH, &rowLength); glGetIntegerv(GL_UNPACK_SKIP_PIXELS, &skipPixels); glGetIntegerv(GL_UNPACK_SKIP_ROWS, &skipRows); #if !defined(RFONT_RENDER_LEGACY) glActiveTexture(GL_TEXTURE0 + atlas - 1); #endif glBindTexture(GL_TEXTURE_2D, atlas); RFont_push_pixel_values(1, w, 0, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RED, GL_UNSIGNED_BYTE, bitmap); RFont_push_pixel_values(alignment, rowLength, skipPixels, skipRows); glBindTexture(GL_TEXTURE_2D, 0); } #if defined(RFONT_RENDER_RGL) && !defined(RFONT_CUSTOM_GL) void RFont_render_set_color(float r, float g, float b, float a) { rglColor4f(r, g, b, a); } void RFont_render_text(RFont_texture atlas, float* verts, float* tcoords, size_t nverts) { glEnable(GL_TEXTURE_2D); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glShadeModel(GL_SMOOTH); rglMatrixMode(RGL_MODELVIEW); rglLoadIdentity(); rglPushMatrix(); glDisable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); rglSetTexture(atlas); rglBegin(RGL_TRIANGLES_2D); size_t i; size_t tIndex = 0; for (i = 0; i < (nverts * 3); i += 3) { rglTexCoord2f(tcoords[tIndex], tcoords[tIndex + 1]); tIndex += 2; rglVertex2f(verts[i], verts[i + 1]); } rglEnd(); rglPopMatrix(); rglSetTexture(0); glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_DEPTH_TEST); } void RFont_render_free(RFont_texture atlas) { glDeleteTextures(1, &atlas); } void RFont_render_legacy(u8 legacy) { rglLegacy(legacy); } void RFont_render_init() {} #endif /* RFONT_RENDER_RGL */ #if defined(RFONT_RENDER_LEGACY) && !defined(RFONT_RENDER_RGL) && !defined(RFONT_CUSTOM_GL) void RFont_render_set_color(float r, float g, float b, float a) { glColor4f(r, g, b, a); } void RFont_render_text(RFont_texture atlas, float* verts, float* tcoords, size_t nverts) { glEnable(GL_TEXTURE_2D); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glShadeModel(GL_SMOOTH); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); #if !defined(RFONT_RENDER_LEGACY) glActiveTexture(GL_TEXTURE0 + atlas - 1); #endif glBindTexture(GL_TEXTURE_2D, atlas); glPushMatrix(); glBegin(GL_TRIANGLES); size_t i; size_t tIndex = 0; for (i = 0; i < (nverts * 3); i += 3) { glTexCoord2f(tcoords[tIndex], tcoords[tIndex + 1]); tIndex += 2; glVertex2f(verts[i], verts[i + 1]); } glEnd(); glPopMatrix(); glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_DEPTH_TEST); } void RFont_render_free(RFont_texture atlas) { glDeleteTextures(1, &atlas); } void RFont_render_legacy(u8 legacy) { RFONT_UNUSED(legacy) } void RFont_render_init() {} #endif /* defined(RFONT_RENDER_LEGACY) && !defined(RFONT_RENDER_RGL) */ #if !defined(RFONT_RENDER_LEGACY) && !defined(RFONT_RENDER_RGL) typedef struct { GLuint vao, vbo, tbo, cbo, ebo, program, vShader, fShader; u8 legacy; } RFont_gl_info; RFont_gl_info RFont_gl = { 0 }; float RFont_color[4] = {0, 0, 0, 1}; #ifdef RFONT_DEBUG inline void RFont_debug_shader(u32 src, const char* shader, const char* action); void RFont_debug_shader(u32 src, const char* shader, const char* action) { GLint status; if (action[0] == 'l') glGetProgramiv(src, GL_LINK_STATUS, &status); else glGetShaderiv(src, GL_COMPILE_STATUS, &status); if (status == GL_TRUE) printf("%s Shader %s successfully.\n", shader, action); else { printf("%s Shader failed to %s.\n", shader, action); if (action[0] == 'c') { GLint infoLogLength; glGetShaderiv(src, GL_INFO_LOG_LENGTH, &infoLogLength); if (infoLogLength > 0) { GLchar* infoLog = (GLchar*)RFONT_MALLOC(infoLogLength); glGetShaderInfoLog(src, infoLogLength, NULL, infoLog); printf("%s Shader info log:\n%s\n", shader, infoLog); RFONT_FREE(infoLog); } } RFont_opengl_getError(); } } #endif #define RFONT_MULTILINE_STR(...) #__VA_ARGS__ void RFont_render_set_color(float r, float g, float b, float a) { if (RFont_gl.legacy) return glColor4f(r, g, b, a); RFont_color[0] = r; RFont_color[1] = g; RFont_color[2] = b; RFont_color[3] = a; } void RFont_render_init() { if (RFont_gl.vao != 0 || RFont_gl.legacy) return; static const char* defaultVShaderCode = RFONT_MULTILINE_STR( \x23version 330 core \n layout (location = 0) in vec3 vertexPosition; layout (location = 1) in vec2 vertexTexCoord; layout (location = 2) in vec4 inColor; out vec2 fragTexCoord; out vec4 fragColor; uniform mat4 mvp; \n void main() { fragColor = inColor; gl_Position = vec4(vertexPosition, 1.0); fragTexCoord = vertexTexCoord; } ); static const char* defaultFShaderCode = RFONT_MULTILINE_STR( \x23version 330 core \n out vec4 FragColor; in vec4 fragColor; in vec2 fragTexCoord; uniform sampler2D texture0; void main() { FragColor = texture(texture0, fragTexCoord) * fragColor; } ); glGenVertexArrays(1, &RFont_gl.vao); glBindVertexArray(RFont_gl.vao); glGenBuffers(1, &RFont_gl.vbo); glGenBuffers(1, &RFont_gl.tbo); glGenBuffers(1, &RFont_gl.cbo); glGenBuffers(1, &RFont_gl.ebo); /* compile vertex shader */ RFont_gl.vShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(RFont_gl.vShader, 1, &defaultVShaderCode, NULL); glCompileShader(RFont_gl.vShader); #ifdef RFONT_DEBUG RFont_debug_shader(RFont_gl.vShader, "Vertex", "compile"); #endif /* compile fragment shader */ RFont_gl.fShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(RFont_gl.fShader, 1, &defaultFShaderCode, NULL); glCompileShader(RFont_gl.fShader); #ifdef RFONT_DEBUG RFont_debug_shader(RFont_gl.fShader, "Fragment", "compile"); #endif /* create program and link vertex and fragment shaders */ RFont_gl.program = glCreateProgram(); glAttachShader(RFont_gl.program, RFont_gl.vShader); glAttachShader(RFont_gl.program, RFont_gl.fShader); glBindAttribLocation(RFont_gl.program, 0, "vertexPosition"); glBindAttribLocation(RFont_gl.program, 1, "vertexTexCoord"); glBindAttribLocation(RFont_gl.program, 2, "inColor"); glLinkProgram(RFont_gl.program); #ifdef RFONT_DEBUG RFont_debug_shader(RFont_gl.program, "Both", "link to the program"); #endif } void RFont_render_text(RFont_texture atlas, float* verts, float* tcoords, size_t nverts) { glEnable(GL_TEXTURE_2D); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glDisable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glShadeModel(GL_SMOOTH); if (RFont_gl.legacy) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glBindTexture(GL_TEXTURE_2D, atlas); glPushMatrix(); glBegin(GL_TRIANGLES); size_t i; size_t tIndex = 0; for (i = 0; i < (nverts * 3); i += 3) { glTexCoord2f(tcoords[tIndex], tcoords[tIndex + 1]); tIndex += 2; glVertex2f(verts[i], verts[i + 1]); } glEnd(); glPopMatrix(); } else { glBindVertexArray(RFont_gl.vao); glUseProgram(RFont_gl.program); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, RFont_gl.vbo); glBufferData(GL_ARRAY_BUFFER, nverts * 3 * sizeof(float), verts, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, RFont_gl.tbo); glBufferData(GL_ARRAY_BUFFER, nverts * 2 * sizeof(float), tcoords, GL_DYNAMIC_DRAW); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, NULL); float* colors = RFONT_MALLOC(sizeof(float) * nverts * 4); u32 i = 0; for (i = 0; i < (nverts * 4); i += 4) { colors[i] = RFont_color[0]; colors[i + 1] = RFont_color[1]; colors[i + 2] = RFont_color[2]; colors[i + 3] = RFont_color[3]; } glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, RFont_gl.cbo); glBufferData(GL_ARRAY_BUFFER, nverts * 4 * sizeof(float), colors, GL_DYNAMIC_DRAW); glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, NULL); RFONT_FREE(colors); GLushort* indices = RFONT_MALLOC(sizeof(GLushort) * 6 * nverts); int k = 0; u32 j; for (j = 0; j < (6 * nverts); j += 6) { indices[j] = 4* k; indices[j + 1] = 4*k + 1; indices[j + 2] = 4*k + 2; indices[j + 3] = 4*k; indices[j + 4] = 4*k + 2; indices[j + 5] = 4*k + 3; k++; } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, RFont_gl.ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * 6 * nverts, indices, GL_STATIC_DRAW); RFONT_FREE(indices); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, atlas); glDrawArrays(GL_TRIANGLES, 0, nverts); glUseProgram(0); } glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); } void RFont_render_free(RFont_texture atlas) { glDeleteTextures(1, &atlas); if (RFont_gl.vao == 0 || RFont_gl.legacy) return; /* free vertex array */ glDeleteVertexArrays(1, &RFont_gl.vao); RFont_gl.vao = 0; /* free buffers */ glDeleteBuffers(1, &RFont_gl.tbo); glDeleteBuffers(1, &RFont_gl.vbo); /* free program data */ glDeleteShader(RFont_gl.vShader); glDeleteShader(RFont_gl.fShader); glDeleteProgram(RFont_gl.program); } void RFont_render_legacy(u8 legacy) { RFont_gl.legacy = legacy; } #endif /* !defined(RFONT_RENDER_LEGACY) && !defined(RFONT_RENDER_RGL) */ #endif /* !defined(RFONT_NO_OPENGL) && !defined(RFONT_NO_GRAPHICS) */ /* stb_truetype defines and source code required by RFont you probably don't care about this part if you're reading just the RFont code */ #ifndef RFONT_EXTERNAL_STB typedef char stbtt__check_size32[sizeof(i32)==4 ? 1 : -1]; typedef char stbtt__check_size16[sizeof(i16)==2 ? 1 : -1]; // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid RFONT_MALLOC #ifndef STBTT_malloc #define STBTT_malloc(x,u) ((void)(u),RFONT_MALLOC(x)) #define STBTT_free(x,u) ((void)(u),RFONT_FREE(x)) #endif #ifdef __cplusplus extern "C" { #endif // as above, but takes one or more glyph indices for greater efficiency #ifndef STBTT_vmove // you can predefine these to use different values (but why?) enum { STBTT_vmove=1, STBTT_vline, STBTT_vcurve, STBTT_vcubic }; #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 i16 because that's not visible in the header file typedef struct { stbtt_vertex_type x,y,cx,cy,cx1,cy1; unsigned char type,padding; } stbtt_vertex; #endif STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); // @TODO: don't expose this structure typedef struct { int w,h,stride; unsigned char *pixels; } stbtt__bitmap; // rasterize a shape with quadratic beziers into a bitmap STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into float flatness_in_pixels, // allowable error of curve in pixels stbtt_vertex *vertices, // array of vertices defining shape int num_verts, // number of vertices in above array float scale_x, float scale_y, // scale applied to input vertices float shift_x, float shift_y, // translation applied to input vertices int x_off, int y_off, // another translation applied to input int invert, // if non-zero, vertically flip shape void *userdata); // context for to STBTT_MALLOC 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 #ifndef STBTT_MAX_OVERSAMPLE #define STBTT_MAX_OVERSAMPLE 8 #endif #if STBTT_MAX_OVERSAMPLE > 255 #error "STBTT_MAX_OVERSAMPLE cannot be > 255" #endif typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; #ifndef STBTT_RASTERIZER_VERSION #define STBTT_RASTERIZER_VERSION 2 #endif #ifdef _MSC_VER #define STBTT__NOTUSED(v) (void)(v) #else #define STBTT__NOTUSED(v) (void)sizeof(v) #endif STBTT_DEF u8 stbtt__buf_get8(stbtt__buf *b) { if (b->cursor >= b->size) return 0; return b->data[b->cursor++]; } STBTT_DEF u8 stbtt__buf_peek8(stbtt__buf *b) { if (b->cursor >= b->size) return 0; return b->data[b->cursor]; } STBTT_DEF void stbtt__buf_seek(stbtt__buf *b, int o) { assert(!(o > b->size || o < 0)); b->cursor = (o > b->size || o < 0) ? b->size : o; } STBTT_DEF void stbtt__buf_skip(stbtt__buf *b, int o) { stbtt__buf_seek(b, b->cursor + o); } STBTT_DEF u32 stbtt__buf_get(stbtt__buf *b, int n) { u32 v = 0; int i; assert(n >= 1 && n <= 4); for (i = 0; i < n; i++) v = (v << 8) | stbtt__buf_get8(b); return v; } STBTT_DEF stbtt__buf stbtt__new_buf(const void *p, size_t size) { stbtt__buf r; assert(size < 0x40000000); r.data = (u8*) p; r.size = (int) size; r.cursor = 0; return r; } #define stbtt__buf_get16(b) stbtt__buf_get((b), 2) #define stbtt__buf_get32(b) stbtt__buf_get((b), 4) STBTT_DEF stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) { stbtt__buf r = stbtt__new_buf(NULL, 0); if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; r.data = b->data + o; r.size = s; return r; } STBTT_DEF stbtt__buf stbtt__cff_get_index(stbtt__buf *b) { int count, start, offsize; start = b->cursor; count = stbtt__buf_get16(b); if (count) { offsize = stbtt__buf_get8(b); assert(offsize >= 1 && offsize <= 4); stbtt__buf_skip(b, offsize * count); stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); } return stbtt__buf_range(b, start, b->cursor - start); } STBTT_DEF u32 stbtt__cff_int(stbtt__buf *b) { int b0 = stbtt__buf_get8(b); if (b0 >= 32 && b0 <= 246) return b0 - 139; else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; else if (b0 == 28) return stbtt__buf_get16(b); else if (b0 == 29) return stbtt__buf_get32(b); assert(0); return 0; } STBTT_DEF void stbtt__cff_skip_operand(stbtt__buf *b) { int v, b0 = stbtt__buf_peek8(b); assert(b0 >= 28); if (b0 == 30) { stbtt__buf_skip(b, 1); while (b->cursor < b->size) { v = stbtt__buf_get8(b); if ((v & 0xF) == 0xF || (v >> 4) == 0xF) break; } } else { stbtt__cff_int(b); } } STBTT_DEF stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) { stbtt__buf_seek(b, 0); while (b->cursor < b->size) { int start = b->cursor, end, op; while (stbtt__buf_peek8(b) >= 28) stbtt__cff_skip_operand(b); end = b->cursor; op = stbtt__buf_get8(b); if (op == 12) op = stbtt__buf_get8(b) | 0x100; if (op == key) return stbtt__buf_range(b, start, end-start); } return stbtt__buf_range(b, 0, 0); } STBTT_DEF void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, u32 *out) { int i; stbtt__buf operands = stbtt__dict_get(b, key); for (i = 0; i < outcount && operands.cursor < operands.size; i++) out[i] = stbtt__cff_int(&operands); } STBTT_DEF int stbtt__cff_index_count(stbtt__buf *b) { stbtt__buf_seek(b, 0); return stbtt__buf_get16(b); } STBTT_DEF stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) { int count, offsize, start, end; stbtt__buf_seek(&b, 0); count = stbtt__buf_get16(&b); offsize = stbtt__buf_get8(&b); assert(i >= 0 && i < count); assert(offsize >= 1 && offsize <= 4); stbtt__buf_skip(&b, i*offsize); start = stbtt__buf_get(&b, offsize); end = stbtt__buf_get(&b, offsize); return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); } #define ttBYTE(p) (* (u8 *) (p)) #define ttCHAR(p) (* (i8 *) (p)) #define ttFixed(p) ttLONG(p) STBTT_DEF i16 ttSHORT(u8 *p) { return p[0]*256 + p[1]; } STBTT_DEF u16 ttUSHORT(u8 *p) { return p[0]*256 + p[1]; } STBTT_DEF u32 ttULONG(u8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } #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]) // @OPTIMIZE: binary search STBTT_DEF u32 stbtt__find_table(u8 *data, u32 fontstart, const char *tag) { i32 num_tables = ttUSHORT(data+fontstart+4); u32 tabledir = fontstart + 12; i32 i; for (i=0; i < num_tables; ++i) { u32 loc = tabledir + 16*i; if (stbtt_tag(data+loc+0, tag)) return ttULONG(data+loc+8); } return 0; } STBTT_DEF stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) { u32 subrsoff = 0, private_loc[2] = { 0, 0 }; stbtt__buf pdict; stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); if (!subrsoff) return stbtt__new_buf(NULL, 0); stbtt__buf_seek(&cff, private_loc[1]+subrsoff); return stbtt__cff_get_index(&cff); } STBTT_DEF void stbtt_setvertex(stbtt_vertex *v, u8 type, i32 x, i32 y, i32 cx, i32 cy) { v->type = type; v->x = (i16) x; v->y = (i16) y; v->cx = (i16) cx; v->cy = (i16) cy; } STBTT_DEF int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, i32 sx, i32 sy, i32 scx, i32 scy, i32 cx, i32 cy) { if (start_off) { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); } else { 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); } return num_vertices; } STBTT_DEF int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index); STBTT_DEF int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { i16 numberOfContours; u8 *endPtsOfContours; u8 *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) { u8 flags=0,flagcount; i32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; i32 x,y,cx,cy,sx,sy, scx,scy; u8 *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 + 2*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; 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) { i16 dx = *points++; x += (flags & 16) ? dx : -dx; // ??? } else { if (!(flags & 16)) { x = x + (i16) (points[0]*256 + points[1]); points += 2; } } vertices[off+i].x = (i16) x; } // now load y coordinates y=0; for (i=0; i < n; ++i) { flags = vertices[off+i].type; if (flags & 4) { i16 dy = *points++; y += (flags & 32) ? dy : -dy; // ??? } else { if (!(flags & 32)) { y = y + (i16) (points[0]*256 + points[1]); points += 2; } } vertices[off+i].y = (i16) y; } // now convert them to our format num_vertices=0; sx = sy = cx = cy = scx = scy = 0; for (i=0; i < n; ++i) { flags = vertices[off+i].type; x = (i16) vertices[off+i].x; y = (i16) vertices[off+i].y; if (next_move == i) { if (i != 0) num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); // now start the new one start_off = !(flags & 1); if (start_off) { // if we start off with an off-curve point, then when we need to find a point on the curve // where we can start, and we need to save some state for when we wraparound. scx = x; scy = y; if (!(vertices[off+i+1].type & 1)) { // next point is also a curve point, so interpolate an on-point curve sx = (x + (i32) vertices[off+i+1].x) >> 1; sy = (y + (i32) vertices[off+i+1].y) >> 1; } else { // otherwise just use the next point as our start point sx = (i32) vertices[off+i+1].x; sy = (i32) vertices[off+i+1].y; ++i; // we're using point i+1 as the starting point, so skip it } } else { sx = x; sy = y; } stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); was_off = 0; next_move = 1 + ttUSHORT(endPtsOfContours+j*2); ++j; } 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; } } } num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); } else if (numberOfContours < 0) { // Compound shapes. int more = 1; u8 *comp = data + g + 10; num_vertices = 0; vertices = 0; while (more) { u16 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 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 { // numberOfCounters == 0, do nothing } *pvertices = vertices; return num_vertices; } typedef struct { int bounds; int started; float first_x, first_y; float x, y; i32 min_x, max_x, min_y, max_y; stbtt_vertex *pvertices; int num_vertices; } stbtt__csctx; #define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} STBTT_DEF void stbtt__track_vertex(stbtt__csctx *c, i32 x, i32 y) { if (x > c->max_x || !c->started) c->max_x = x; if (y > c->max_y || !c->started) c->max_y = y; if (x < c->min_x || !c->started) c->min_x = x; if (y < c->min_y || !c->started) c->min_y = y; c->started = 1; } STBTT_DEF void stbtt__csctx_v(stbtt__csctx *c, u8 type, i32 x, i32 y, i32 cx, i32 cy, i32 cx1, i32 cy1) { if (c->bounds) { stbtt__track_vertex(c, x, y); if (type == STBTT_vcubic) { stbtt__track_vertex(c, cx, cy); stbtt__track_vertex(c, cx1, cy1); } } else { stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); c->pvertices[c->num_vertices].cx1 = (i16) cx1; c->pvertices[c->num_vertices].cy1 = (i16) cy1; } c->num_vertices++; } STBTT_DEF void stbtt__csctx_close_shape(stbtt__csctx *ctx) { if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); } STBTT_DEF void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) { stbtt__csctx_close_shape(ctx); ctx->first_x = ctx->x = ctx->x + dx; ctx->first_y = ctx->y = ctx->y + dy; stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); } STBTT_DEF void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) { ctx->x += dx; ctx->y += dy; stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); } STBTT_DEF void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) { float cx1 = ctx->x + dx1; float cy1 = ctx->y + dy1; float cx2 = cx1 + dx2; float cy2 = cy1 + dy2; ctx->x = cx2 + dx3; ctx->y = cy2 + dy3; stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); } STBTT_DEF stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) { int count = stbtt__cff_index_count(&idx); int bias = 107; if (count >= 33900) bias = 32768; else if (count >= 1240) bias = 1131; n += bias; if (n < 0 || n >= count) return stbtt__new_buf(NULL, 0); return stbtt__cff_index_get(idx, n); } STBTT_DEF stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) { stbtt__buf fdselect = info->fdselect; int nranges, start, end, v, fmt, fdselector = -1, i; stbtt__buf_seek(&fdselect, 0); fmt = stbtt__buf_get8(&fdselect); if (fmt == 0) { // untested stbtt__buf_skip(&fdselect, glyph_index); fdselector = stbtt__buf_get8(&fdselect); } else if (fmt == 3) { nranges = stbtt__buf_get16(&fdselect); start = stbtt__buf_get16(&fdselect); for (i = 0; i < nranges; i++) { v = stbtt__buf_get8(&fdselect); end = stbtt__buf_get16(&fdselect); if (glyph_index >= start && glyph_index < end) { fdselector = v; break; } start = end; } } if (fdselector == -1) stbtt__new_buf(NULL, 0); return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); } STBTT_DEF int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) { int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; int has_subrs = 0, clear_stack; float s[48]; stbtt__buf subr_stack[10], subrs = info->subrs, b; float f; #define STBTT__CSERR(s) (0) // this currently ignores the initial width value, which isn't needed if we have hmtx b = stbtt__cff_index_get(info->charstrings, glyph_index); while (b.cursor < b.size) { i = 0; clear_stack = 1; b0 = stbtt__buf_get8(&b); switch (b0) { // @TODO implement hinting case 0x13: // hintmask case 0x14: // cntrmask if (in_header) maskbits += (sp / 2); // implicit "vstem" in_header = 0; stbtt__buf_skip(&b, (maskbits + 7) / 8); break; case 0x01: // hstem case 0x03: // vstem case 0x12: // hstemhm case 0x17: // vstemhm maskbits += (sp / 2); break; case 0x15: // rmoveto in_header = 0; if (sp < 2) return STBTT__CSERR("rmoveto stack"); stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); break; case 0x04: // vmoveto in_header = 0; if (sp < 1) return STBTT__CSERR("vmoveto stack"); stbtt__csctx_rmove_to(c, 0, s[sp-1]); break; case 0x16: // hmoveto in_header = 0; if (sp < 1) return STBTT__CSERR("hmoveto stack"); stbtt__csctx_rmove_to(c, s[sp-1], 0); break; case 0x05: // rlineto if (sp < 2) return STBTT__CSERR("rlineto stack"); for (; i + 1 < sp; i += 2) stbtt__csctx_rline_to(c, s[i], s[i+1]); break; // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical // starting from a different place. case 0x07: // vlineto if (sp < 1) return STBTT__CSERR("vlineto stack"); goto vlineto; case 0x06: // hlineto if (sp < 1) return STBTT__CSERR("hlineto stack"); for (;;) { if (i >= sp) break; stbtt__csctx_rline_to(c, s[i], 0); i++; vlineto: if (i >= sp) break; stbtt__csctx_rline_to(c, 0, s[i]); i++; } break; case 0x1F: // hvcurveto if (sp < 4) return STBTT__CSERR("hvcurveto stack"); goto hvcurveto; case 0x1E: // vhcurveto if (sp < 4) return STBTT__CSERR("vhcurveto stack"); for (;;) { if (i + 3 >= sp) break; stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); i += 4; hvcurveto: if (i + 3 >= sp) break; stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); i += 4; } break; case 0x08: // rrcurveto if (sp < 6) return STBTT__CSERR("rcurveline stack"); for (; i + 5 < sp; i += 6) stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); break; case 0x18: // rcurveline if (sp < 8) return STBTT__CSERR("rcurveline stack"); for (; i + 5 < sp - 2; i += 6) stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); stbtt__csctx_rline_to(c, s[i], s[i+1]); break; case 0x19: // rlinecurve if (sp < 8) return STBTT__CSERR("rlinecurve stack"); for (; i + 1 < sp - 6; i += 2) stbtt__csctx_rline_to(c, s[i], s[i+1]); if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); break; case 0x1A: // vvcurveto case 0x1B: // hhcurveto if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); f = 0.0; if (sp & 1) { f = s[i]; i++; } for (; i + 3 < sp; i += 4) { if (b0 == 0x1B) stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); else stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); f = 0.0; } break; case 0x0A: // callsubr if (!has_subrs) { if (info->fdselect.size) subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); has_subrs = 1; } // fallthrough case 0x1D: // callgsubr if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); v = (int) s[--sp]; if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); subr_stack[subr_stack_height++] = b; b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); if (b.size == 0) return STBTT__CSERR("subr not found"); b.cursor = 0; clear_stack = 0; break; case 0x0B: // return if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); b = subr_stack[--subr_stack_height]; clear_stack = 0; break; case 0x0E: // endchar stbtt__csctx_close_shape(c); return 1; case 0x0C: { // two-byte escape float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; float dx, dy; int b1 = stbtt__buf_get8(&b); switch (b1) { // @TODO These "flex" implementations ignore the flex-depth and resolution, // and always draw beziers. case 0x22: // hflex if (sp < 7) return STBTT__CSERR("hflex stack"); dx1 = s[0]; dx2 = s[1]; dy2 = s[2]; dx3 = s[3]; dx4 = s[4]; dx5 = s[5]; dx6 = s[6]; stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); break; case 0x23: // flex if (sp < 13) return STBTT__CSERR("flex stack"); dx1 = s[0]; dy1 = s[1]; dx2 = s[2]; dy2 = s[3]; dx3 = s[4]; dy3 = s[5]; dx4 = s[6]; dy4 = s[7]; dx5 = s[8]; dy5 = s[9]; dx6 = s[10]; dy6 = s[11]; //fd is s[12] stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); break; case 0x24: // hflex1 if (sp < 9) return STBTT__CSERR("hflex1 stack"); dx1 = s[0]; dy1 = s[1]; dx2 = s[2]; dy2 = s[3]; dx3 = s[4]; dx4 = s[5]; dx5 = s[6]; dy5 = s[7]; dx6 = s[8]; stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); break; case 0x25: // flex1 if (sp < 11) return STBTT__CSERR("flex1 stack"); dx1 = s[0]; dy1 = s[1]; dx2 = s[2]; dy2 = s[3]; dx3 = s[4]; dy3 = s[5]; dx4 = s[6]; dy4 = s[7]; dx5 = s[8]; dy5 = s[9]; dx6 = dy6 = s[10]; dx = dx1+dx2+dx3+dx4+dx5; dy = dy1+dy2+dy3+dy4+dy5; if (fabs(dx) > fabs(dy)) dy6 = -dy; else dx6 = -dx; stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); break; default: return STBTT__CSERR("unimplemented"); } } break; default: if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) return STBTT__CSERR("reserved operator"); // push immediate if (b0 == 255) { f = (float)(i32)stbtt__buf_get32(&b) / 0x10000; } else { stbtt__buf_skip(&b, -1); f = (float)(i16)stbtt__cff_int(&b); } if (sp >= 48) return STBTT__CSERR("push stack overflow"); s[sp++] = f; clear_stack = 0; break; } if (clear_stack) sp = 0; } return STBTT__CSERR("no endchar"); #undef STBTT__CSERR } STBTT_DEF int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { // runs the charstring twice, once to count and once to output (to avoid realloc) stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); output_ctx.pvertices = *pvertices; if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { assert(output_ctx.num_vertices == count_ctx.num_vertices); return output_ctx.num_vertices; } } *pvertices = NULL; return 0; } STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { if (!info->cff.size) return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); else return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); } STBTT_DEF int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { u8 *data = info->data + info->kern; u32 needle, straw; int l, r, m; // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; l = 0; r = ttUSHORT(data+10) - 1; needle = glyph1 << 16 | glyph2; while (l <= r) { m = (l + r) >> 1; straw = ttULONG(data+18+(m*6)); // note: unaligned read if (needle < straw) r = m - 1; else if (needle > straw) l = m + 1; else return ttSHORT(data+22+(m*6)); } return 0; } STBTT_DEF i32 stbtt__GetCoverageIndex(u8 *coverageTable, int glyph) { u16 coverageFormat = ttUSHORT(coverageTable); switch(coverageFormat) { case 1: { u16 glyphCount = ttUSHORT(coverageTable + 2); // Binary search. i32 l=0, r=glyphCount-1, m; int straw, needle=glyph; while (l <= r) { u8 *glyphArray = coverageTable + 4; u16 glyphID; m = (l + r) >> 1; glyphID = ttUSHORT(glyphArray + 2 * m); straw = glyphID; if (needle < straw) r = m - 1; else if (needle > straw) l = m + 1; else { return m; } } } break; case 2: { u16 rangeCount = ttUSHORT(coverageTable + 2); u8 *rangeArray = coverageTable + 4; // Binary search. i32 l=0, r=rangeCount-1, m; int strawStart, strawEnd, needle=glyph; while (l <= r) { u8 *rangeRecord; m = (l + r) >> 1; rangeRecord = rangeArray + 6 * m; strawStart = ttUSHORT(rangeRecord); strawEnd = ttUSHORT(rangeRecord + 2); if (needle < strawStart) r = m - 1; else if (needle > strawEnd) l = m + 1; else { u16 startCoverageIndex = ttUSHORT(rangeRecord + 4); return startCoverageIndex + glyph - strawStart; } } } break; default: { // There are no other cases. assert(0); } break; } return -1; } STBTT_DEF i32 stbtt__GetGlyphClass(u8 *classDefTable, int glyph) { u16 classDefFormat = ttUSHORT(classDefTable); switch(classDefFormat) { case 1: { u16 startGlyphID = ttUSHORT(classDefTable + 2); u16 glyphCount = ttUSHORT(classDefTable + 4); u8 *classDef1ValueArray = classDefTable + 6; if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) return (i32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); classDefTable = classDef1ValueArray + 2 * glyphCount; } break; case 2: { u16 classRangeCount = ttUSHORT(classDefTable + 2); u8 *classRangeRecords = classDefTable + 4; // Binary search. i32 l=0, r=classRangeCount-1, m; int strawStart, strawEnd, needle=glyph; while (l <= r) { u8 *classRangeRecord; m = (l + r) >> 1; classRangeRecord = classRangeRecords + 6 * m; strawStart = ttUSHORT(classRangeRecord); strawEnd = ttUSHORT(classRangeRecord + 2); if (needle < strawStart) r = m - 1; else if (needle > strawEnd) l = m + 1; else return (i32)ttUSHORT(classRangeRecord + 4); } classDefTable = classRangeRecords + 6 * classRangeCount; } break; default: { // There are no other cases. assert(0); } break; } return -1; } // Define to assert(x) if you want to break on unimplemented formats. #define STBTT_GPOS_TODO_assert(x) assert(x) STBTT_DEF i32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { u16 lookupListOffset; u8 *lookupList; u16 lookupCount; u8 *data; i32 i; if (!info->gpos) return 0; data = info->data + info->gpos; if (ttUSHORT(data+0) != 1) return 0; // Major version 1 if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 lookupListOffset = ttUSHORT(data+8); lookupList = data + lookupListOffset; lookupCount = ttUSHORT(lookupList); for (i=0; i> 1; pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; secondGlyph = ttUSHORT(pairValue); straw = secondGlyph; if (needle < straw) r = m - 1; else if (needle > straw) l = m + 1; else { i16 xAdvance = ttSHORT(pairValue + 2); return xAdvance; } } } break; case 2: { u16 valueFormat1 = ttUSHORT(table + 4); u16 valueFormat2 = ttUSHORT(table + 6); u16 classDef1Offset = ttUSHORT(table + 8); u16 classDef2Offset = ttUSHORT(table + 10); int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); u16 class1Count = ttUSHORT(table + 12); u16 class2Count = ttUSHORT(table + 14); assert(glyph1class < class1Count); assert(glyph2class < class2Count); // TODO: Support more formats. STBTT_GPOS_TODO_assert(valueFormat1 == 4); if (valueFormat1 != 4) return 0; STBTT_GPOS_TODO_assert(valueFormat2 == 0); if (valueFormat2 != 0) return 0; if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { u8 *class1Records = table + 16; u8 *class2Records = class1Records + 2 * (glyph1class * class2Count); i16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); return xAdvance; } } break; default: { // There are no other cases. assert(0); break; }; } } break; }; default: // TODO: Implement other stuff. break; } } return 0; } STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) { int xAdvance = 0; if (info->gpos) xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); else if (info->kern) xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); return xAdvance; } typedef struct stbtt__hheap_chunk { struct stbtt__hheap_chunk *next; } stbtt__hheap_chunk; typedef struct stbtt__hheap { struct stbtt__hheap_chunk *head; void *first_free; int num_remaining_in_head_chunk; } stbtt__hheap; STBTT_DEF void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) { if (hh->first_free) { void *p = hh->first_free; hh->first_free = * (void **) p; return p; } else { if (hh->num_remaining_in_head_chunk == 0) { int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); if (c == NULL) return NULL; c->next = hh->head; hh->head = c; hh->num_remaining_in_head_chunk = count; } --hh->num_remaining_in_head_chunk; return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; } } STBTT_DEF void stbtt__hheap_free(stbtt__hheap *hh, void *p) { *(void **) p = hh->first_free; hh->first_free = p; } STBTT_DEF void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) { stbtt__hheap_chunk *c = hh->head; while (c) { stbtt__hheap_chunk *n = c->next; STBTT_free(c, userdata); c = n; } } typedef struct stbtt__edge { float x0,y0, x1,y1; int invert; } stbtt__edge; typedef struct stbtt__active_edge { struct stbtt__active_edge *next; #if STBTT_RASTERIZER_VERSION==1 int x,dx; float ey; int direction; #elif STBTT_RASTERIZER_VERSION==2 float fx,fdx,fdy; float direction; float sy; float ey; #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif } stbtt__active_edge; #if STBTT_RASTERIZER_VERSION == 1 #define STBTT_FIXSHIFT 10 #define STBTT_FIX (1 << STBTT_FIXSHIFT) #define STBTT_FIXMASK (STBTT_FIX-1) STBTT_DEF stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) { stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); assert(z != NULL); if (!z) return z; // round dx down to avoid overshooting if (dxdy < 0) z->dx = -floor(STBTT_FIX * -dxdy); else z->dx = floor(STBTT_FIX * dxdy); z->x = floor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount z->x -= off_x * STBTT_FIX; z->ey = e->y1; z->next = 0; z->direction = e->invert ? 1 : -1; return z; } #elif STBTT_RASTERIZER_VERSION == 2 STBTT_DEF stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) { stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); assert(z != NULL); //assert(e->y0 <= start_point); if (!z) return z; z->fdx = dxdy; z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; z->fx = e->x0 + dxdy * (start_point - e->y0); z->fx -= off_x; z->direction = e->invert ? 1.0f : -1.0f; z->sy = e->y0; z->ey = e->y1; z->next = 0; return z; } #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif #if STBTT_RASTERIZER_VERSION == 1 STBTT_DEF 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->direction; } else { int x1 = e->x; w += e->direction; // if we went to zero, we need to draw if (w == 0) { int i = x0 >> STBTT_FIXSHIFT; int j = x1 >> STBTT_FIXSHIFT; if (i < len && j >= 0) { if (i == j) { // x0,x1 are the same pixel, so compute combined coverage scanline[i] = scanline[i] + (u8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); } else { if (i >= 0) // add antialiasing for x0 scanline[i] = scanline[i] + (u8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); else i = -1; // clip if (j < len) // add antialiasing for x1 scanline[j] = scanline[j] + (u8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); else j = len; // clip for (++i; i < j; ++i) // fill pixels between x0 and x1 scanline[i] = scanline[i] + (u8) max_weight; } } } } e = e->next; } } STBTT_DEF void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { stbtt__hheap hh = { 0, 0, 0 }; 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) { 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 assert(z->direction); z->direction = 0; stbtt__hheap_free(&hh, z); } 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 = stbtt__new_active(&hh, e, off_x, scan_y, userdata); if (z != NULL) { // 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; } memcpy(result->pixels + j * result->stride, scanline, result->w); ++j; } stbtt__hheap_cleanup(&hh, userdata); if (scanline != scanline_data) STBTT_free(scanline, userdata); } #elif STBTT_RASTERIZER_VERSION == 2 STBTT_DEF void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) { if (y0 == y1) return; assert(y0 < y1); assert(e->sy <= e->ey); if (y0 > e->ey) return; if (y1 < e->sy) return; if (y0 < e->sy) { x0 += (x1-x0) * (e->sy - y0) / (y1-y0); y0 = e->sy; } if (y1 > e->ey) { x1 += (x1-x0) * (e->ey - y1) / (y1-y0); y1 = e->ey; } if (x0 == x) assert(x1 <= x+1); else if (x0 == x+1) assert(x1 >= x); else if (x0 <= x) assert(x1 <= x); else if (x0 >= x+1) assert(x1 >= x+1); else assert(x1 >= x && x1 <= x+1); if (x0 <= x && x1 <= x) scanline[x] += e->direction * (y1-y0); else if (x0 >= x+1 && x1 >= x+1) ; else { assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position } } STBTT_DEF void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) { float y_bottom = y_top+1; while (e) { // brute force every pixel // compute intersection points with top & bottom assert(e->ey >= y_top); if (e->fdx == 0) { float x0 = e->fx; if (x0 < len) { if (x0 >= 0) { stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); } else { stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); } } } else { float x0 = e->fx; float dx = e->fdx; float xb = x0 + dx; float x_top, x_bottom; float sy0,sy1; float dy = e->fdy; assert(e->sy <= y_bottom && e->ey >= y_top); if (e->sy > y_top) { x_top = x0 + dx * (e->sy - y_top); sy0 = e->sy; } else { x_top = x0; sy0 = y_top; } if (e->ey < y_bottom) { x_bottom = x0 + dx * (e->ey - y_top); sy1 = e->ey; } else { x_bottom = xb; sy1 = y_bottom; } if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { // from here on, we don't have to range check x values if ((int) x_top == (int) x_bottom) { float height; // simple case, only spans one pixel int x = (int) x_top; height = sy1 - sy0; assert(x >= 0 && x < len); scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; scanline_fill[x] += e->direction * height; // everything right of this pixel is filled } else { int x,x1,x2; float y_crossing, step, sign, area; // covers 2+ pixels if (x_top > x_bottom) { // flip scanline vertically; signed area is the same float t; sy0 = y_bottom - (sy0 - y_top); sy1 = y_bottom - (sy1 - y_top); t = sy0, sy0 = sy1, sy1 = t; t = x_bottom, x_bottom = x_top, x_top = t; dx = -dx; dy = -dy; t = x0, x0 = xb, xb = t; } x1 = (int) x_top; x2 = (int) x_bottom; // compute intersection with y axis at x1+1 y_crossing = (x1+1 - x0) * dy + y_top; sign = e->direction; // area of the rectangle covered from y0..y_crossing area = sign * (y_crossing-sy0); // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); step = sign * dy; for (x = x1+1; x < x2; ++x) { scanline[x] += area + step/2; area += step; } y_crossing += dy * (x2 - (x1+1)); assert(fabs(area) <= 1.01f); scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); scanline_fill[x2] += sign * (sy1-sy0); } } else { int x; for (x=0; x < len; ++x) { float y0 = y_top; float x1 = (float) (x); float x2 = (float) (x+1); float x3 = xb; float y3 = y_bottom; float y1 = (x - x0) / dx + y_top; float y2 = (x+1 - x0) / dx + y_top; if (x0 < x1 && x3 > x2) { // three segments descending down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); } else if (x3 < x1 && x0 > x2) { // three segments descending down-left stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); } else { // one segment stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); } } } } e = e->next; } } // directly AA rasterize edges w/o supersampling STBTT_DEF void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { stbtt__hheap hh = { 0, 0, 0 }; stbtt__active_edge *active = NULL; int y,j=0, i; float scanline_data[129], *scanline, *scanline2; STBTT__NOTUSED(vsubsample); if (result->w > 64) scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); else scanline = scanline_data; scanline2 = scanline + result->w; y = off_y; e[n].y0 = (float) (off_y + result->h) + 1; while (j < result->h) { // find center of pixel for this scanline float scan_y_top = y + 0.0f; float scan_y_bottom = y + 1.0f; stbtt__active_edge **step = &active; memset(scanline , 0, result->w*sizeof(scanline[0])); memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); // update all active edges; // remove all active edges that terminate before the top of this scanline while (*step) { stbtt__active_edge * z = *step; if (z->ey <= scan_y_top) { *step = z->next; // delete from list assert(z->direction); z->direction = 0; stbtt__hheap_free(&hh, z); } else { step = &((*step)->next); // advance through list } } // insert all edges that start before the bottom of this scanline while (e->y0 <= scan_y_bottom) { if (e->y0 != e->y1) { stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); if (z != NULL) { if (j == 0 && off_y != 0) { if (z->ey < scan_y_top) { // this can happen due to subpixel positioning and some kind of fp rounding error i think z->ey = scan_y_top; } } assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds // insert at front z->next = active; active = z; } } ++e; } // now process all active edges if (active) stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); { float sum = 0; for (i=0; i < result->w; ++i) { float k; int m; sum += scanline2[i]; k = scanline[i] + sum; k = (float) fabs(k)*255 + 0.5f; m = (int) k; if (m > 255) m = 255; result->pixels[j*result->stride + i] = (unsigned char) m; } } // advance all the edges step = &active; while (*step) { stbtt__active_edge *z = *step; z->fx += z->fdx; // advance to position for current scanline step = &((*step)->next); // advance through list } ++y; ++j; } stbtt__hheap_cleanup(&hh, userdata); if (scanline != scanline_data) STBTT_free(scanline, userdata); } #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif #define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) STBTT_DEF void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) { int i,j; for (i=1; i < n; ++i) { stbtt__edge t = p[i], *a = &t; j = i; while (j > 0) { stbtt__edge *b = &p[j-1]; int c = STBTT__COMPARE(a,b); if (!c) break; p[j] = p[j-1]; --j; } if (i != j) p[j] = t; } } STBTT_DEF void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) { /* threshold for transitioning to insertion sort */ while (n > 12) { stbtt__edge t; int c01,c12,c,m,i,j; /* compute median of three */ m = n >> 1; c01 = STBTT__COMPARE(&p[0],&p[m]); c12 = STBTT__COMPARE(&p[m],&p[n-1]); /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ if (c01 != c12) { /* otherwise, we'll need to swap something else to middle */ int z; c = STBTT__COMPARE(&p[0],&p[n-1]); /* 0>mid && midn => n; 0 0 */ /* 0n: 0>n => 0; 0 n */ z = (c == c12) ? 0 : n-1; t = p[z]; p[z] = p[m]; p[m] = t; } /* now p[m] is the median-of-three */ /* swap it to the beginning so it won't move around */ t = p[0]; p[0] = p[m]; p[m] = t; /* partition loop */ i=1; j=n-1; for(;;) { /* handling of equality is crucial here */ /* for sentinels & efficiency with duplicates */ for (;;++i) { if (!STBTT__COMPARE(&p[i], &p[0])) break; } for (;;--j) { if (!STBTT__COMPARE(&p[0], &p[j])) break; } /* make sure we haven't crossed */ if (i >= j) break; t = p[i]; p[i] = p[j]; p[j] = t; ++i; --j; } /* recurse on smaller side, iterate on larger */ if (j < (n-i)) { stbtt__sort_edges_quicksort(p,j); p = p+i; n = n-i; } else { stbtt__sort_edges_quicksort(p+i, n-i); n = j; } } } STBTT_DEF void stbtt__sort_edges(stbtt__edge *p, int n) { stbtt__sort_edges_quicksort(p, n); stbtt__sort_edges_ins_sort(p, n); } typedef struct { float x,y; } stbtt__point; STBTT_DEF void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_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; #if STBTT_RASTERIZER_VERSION == 1 int vsubsample = result->h < 8 ? 15 : 5; #elif STBTT_RASTERIZER_VERSION == 2 int vsubsample = 1; #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif // 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 + shift_x; e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; e[n].x1 = p[b].x * scale_x + shift_x; e[n].y1 = (p[b].y * y_scale_inv + shift_y) * 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); stbtt__sort_edges(e, n); // 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); } STBTT_DEF 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; } // tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching STBTT_DEF 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; } STBTT_DEF void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) { // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough float dx0 = x1-x0; float dy0 = y1-y0; float dx1 = x2-x1; float dy1 = y2-y1; float dx2 = x3-x2; float dy2 = y3-y2; float dx = x3-x0; float dy = y3-y0; float longlen = (float) (sqrt(dx0*dx0+dy0*dy0)+sqrt(dx1*dx1+dy1*dy1)+sqrt(dx2*dx2+dy2*dy2)); float shortlen = (float) sqrt(dx*dx+dy*dy); float flatness_squared = longlen*longlen-shortlen*shortlen; if (n > 16) // 65536 segments on one curve better be enough! return; if (flatness_squared > objspace_flatness_squared) { float x01 = (x0+x1)/2; float y01 = (y0+y1)/2; float x12 = (x1+x2)/2; float y12 = (y1+y2)/2; float x23 = (x2+x3)/2; float y23 = (y2+y3)/2; float xa = (x01+x12)/2; float ya = (y01+y12)/2; float xb = (x12+x23)/2; float yb = (y12+y23)/2; float mx = (xa+xb)/2; float my = (ya+yb)/2; stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); } else { stbtt__add_point(points, *num_points,x3,y3); *num_points = *num_points+1; } } // returns number of contours STBTT_DEF 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; case STBTT_vcubic: stbtt__tesselate_cubic(points, &num_points, x,y, vertices[i].cx, vertices[i].cy, vertices[i].cx1, vertices[i].cy1, 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; } STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) { float scale = scale_x > scale_y ? scale_y : scale_x; int winding_count = 0; int *winding_lengths = NULL; 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, shift_x, shift_y, x_off, y_off, invert, userdata); STBTT_free(winding_lengths, userdata); STBTT_free(windings, userdata); } } STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) { int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { // e.g. space character if (ix0) *ix0 = 0; if (iy0) *iy0 = 0; if (ix1) *ix1 = 0; if (iy1) *iy1 = 0; } else { // move to integral bboxes (treating pixels as little squares, what pixels get touched)? if (ix0) *ix0 = floor( x0 * scale_x + shift_x); if (iy0) *iy0 = floor(-y1 * scale_y + shift_y); if (ix1) *ix1 = ceil ( x1 * scale_x + shift_x); if (iy1) *iy1 = ceil (-y0 * scale_y + shift_y); } } STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_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) { STBTT_free(vertices, info->userdata); return NULL; } scale_y = scale_x; } stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_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, shift_x, shift_y, ix0, iy0, 1, info->userdata); } } STBTT_free(vertices, info->userdata); return gbm.pixels; } STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char* const_data, int fontstart) { unsigned char* data = (unsigned char*)const_data; u32 cmap, t; i32 i,numTables; info->data = (unsigned char*)data; info->fontstart = fontstart; info->cff = stbtt__new_buf(NULL, 0); cmap = stbtt__find_table(data, fontstart, "cmap"); // required info->loca = stbtt__find_table(data, fontstart, "loca"); // required info->head = stbtt__find_table(data, fontstart, "head"); // required info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required info->kern = stbtt__find_table(data, fontstart, "kern"); // not required info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required if (!cmap || !info->head || !info->hhea || !info->hmtx) return 0; if (info->glyf) { // required for truetype if (!info->loca) return 0; } else { // initialization for CFF / Type2 fonts (OTF) stbtt__buf b, topdict, topdictidx; u32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; u32 cff; cff = stbtt__find_table(data, fontstart, "CFF "); if (!cff) return 0; info->fontdicts = stbtt__new_buf(NULL, 0); info->fdselect = stbtt__new_buf(NULL, 0); // @TODO this should use size from table (not 512MB) info->cff = stbtt__new_buf(data+cff, 512*1024*1024); b = info->cff; // read the header stbtt__buf_skip(&b, 2); stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize // @TODO the name INDEX could list multiple fonts, // but we just use the first one. stbtt__cff_get_index(&b); // name INDEX topdictidx = stbtt__cff_get_index(&b); topdict = stbtt__cff_index_get(topdictidx, 0); stbtt__cff_get_index(&b); // string INDEX info->gsubrs = stbtt__cff_get_index(&b); stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); info->subrs = stbtt__get_subrs(b, topdict); // we only support Type 2 charstrings if (cstype != 2) return 0; if (charstrings == 0) return 0; if (fdarrayoff) { // looks like a CID font if (!fdselectoff) return 0; stbtt__buf_seek(&b, fdarrayoff); info->fontdicts = stbtt__cff_get_index(&b); info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); } stbtt__buf_seek(&b, charstrings); info->charstrings = stbtt__cff_get_index(&b); } t = stbtt__find_table(data, fontstart, "maxp"); if (t) info->numGlyphs = ttUSHORT(data+t+4); else info->numGlyphs = 0xffff; info->svg = -1; numTables = ttUSHORT(data + cmap + 2); info->index_map = 0; for (i=0; i < numTables; ++i) { u32 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; case STBTT_PLATFORM_ID_UNICODE: // Mac/iOS has these // all the encodingIDs are unicode, so we don't bother to check it info->index_map = cmap + ttULONG(data+encoding_record+4); break; } } if (info->index_map == 0) return 0; info->indexToLocFormat = ttUSHORT((unsigned char*)data + info->head + 50); return 1; } STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) { u8 *data = info->data; u32 index_map = info->index_map; u16 format = ttUSHORT(data + index_map + 0); if (format == 0) { // apple byte encoding i32 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) { u32 first = ttUSHORT(data + index_map + 6); u32 count = ttUSHORT(data + index_map + 8); if ((u32) unicode_codepoint >= first && (u32) unicode_codepoint < first+count) return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); return 0; } else if (format == 2) { 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 u16 segcount = ttUSHORT(data+index_map+6) >> 1; u16 searchRange = ttUSHORT(data+index_map+8) >> 1; u16 entrySelector = ttUSHORT(data+index_map+10); u16 rangeShift = ttUSHORT(data+index_map+12) >> 1; // do a binary search of the segments u32 endCount = index_map + 14; u32 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) { u16 end; searchRange >>= 1; end = ttUSHORT(data + search + searchRange*2); if (unicode_codepoint > end) search += searchRange*2; --entrySelector; } search += 2; { u16 offset, start; u16 item = (u16) ((search - endCount) >> 1); assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); if (unicode_codepoint < start) return 0; offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); if (offset == 0) return (u16) (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 || format == 13) { u32 ngroups = ttULONG(data+index_map+12); i32 low,high; low = 0; high = (i32)ngroups; // Binary search the right group. while (low < high) { i32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high u32 start_char = ttULONG(data+index_map+16+mid*12); u32 end_char = ttULONG(data+index_map+16+mid*12+4); if ((u32) unicode_codepoint < start_char) high = mid; else if ((u32) unicode_codepoint > end_char) low = mid+1; else { u32 start_glyph = ttULONG(data+index_map+16+mid*12+8); if (format == 12) return start_glyph + unicode_codepoint-start_char; else // format == 13 return start_glyph; } } return 0; // not found } // @TODO assert(0); return 0; } STBTT_DEF int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) { int g1,g2; assert(!info->cff.size); 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 } STBTT_DEF int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { stbtt__csctx c = STBTT__CSCTX_INIT(1); int r = stbtt__run_charstring(info, glyph_index, &c); if (x0) *x0 = r ? c.min_x : 0; if (y0) *y0 = r ? c.min_y : 0; if (x1) *x1 = r ? c.max_x : 0; if (y1) *y1 = r ? c.max_y : 0; return r ? c.num_vertices : 0; } STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { if (info->cff.size) { stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); } else { 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; } #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #endif #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif /* n RFONT_EXTERNAL_STB */ /* END of stb_truetype defines and source code required by RFont */ #endif /* RFONT_IMPLEMENTATION */