From e8508be08565da7c50dafe777109adf5362c0572 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 30 Oct 2014 22:47:21 +0000 Subject: [PATCH] Re-do text rendering to use stb_truetype so it's portable to linux --- renderdoc/3rdparty/stb/stb_impl.c | 3 + renderdoc/3rdparty/stb/stb_truetype.h | 2099 +++++++++++++++++++++++ renderdoc/data/hlsl/debugcbuffers.h | 2 +- renderdoc/data/hlsl/debugtext.hlsl | 45 +- renderdoc/data/renderdoc.rc | 2 + renderdoc/data/resource.h | 2 + renderdoc/data/sourcecodepro.ttf | Bin 0 -> 120252 bytes renderdoc/driver/d3d11/d3d11_debug.cpp | 364 ++-- renderdoc/driver/d3d11/d3d11_debug.h | 13 +- renderdoc/driver/d3d11/d3d11_device.cpp | 24 +- renderdoc/renderdoc.vcxproj | 11 +- renderdoc/renderdoc.vcxproj.filters | 3 + 12 files changed, 2324 insertions(+), 244 deletions(-) create mode 100644 renderdoc/3rdparty/stb/stb_truetype.h create mode 100644 renderdoc/data/sourcecodepro.ttf diff --git a/renderdoc/3rdparty/stb/stb_impl.c b/renderdoc/3rdparty/stb/stb_impl.c index 2b4bb66b8..529ef5922 100644 --- a/renderdoc/3rdparty/stb/stb_impl.c +++ b/renderdoc/3rdparty/stb/stb_impl.c @@ -5,7 +5,10 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_TRUETYPE_IMPLEMENTATION + #pragma warning(disable:4996) #include "stb_image.h" #include "stb_image_write.h" +#include "stb_truetype.h" diff --git a/renderdoc/3rdparty/stb/stb_truetype.h b/renderdoc/3rdparty/stb/stb_truetype.h new file mode 100644 index 000000000..1c0ab4b42 --- /dev/null +++ b/renderdoc/3rdparty/stb/stb_truetype.h @@ -0,0 +1,2099 @@ +// stb_truetype.h - v0.99 - public domain +// authored from 2009-2014 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// +// VERSION HISTORY +// +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (STB) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevokable license to copy +// and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start, +// and you can cut and paste from it to move to more advanced) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLstbtt_uint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(data,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef 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; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +extern float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +extern void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +extern int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +extern unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +extern void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +extern void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern 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); +extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +extern void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +extern void stbtt_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); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +extern const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // 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 + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + 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(data+info->head + 50); + return 1; +} + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + stbtt_uint16 item, offset, start, end; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + searchRange >>= 1; + start = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + end = ttUSHORT(data + index_map + 14 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (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) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 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 + STBTT_assert(0); + return 0; +} + +int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 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; +} + +int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 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; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) 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 = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) 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 + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) 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 == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_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) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 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; +} + +int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +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,y0,x1,y1; + 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 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, 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; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + 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); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, 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, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +unsigned char *stbtt_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) 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; +} + +unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + stbtt_InitFont(&f, data, offset); + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 2; + if (y+gh+2 > bottom_y) + bottom_y = y+gh+2; + } + return bottom_y; +} + +void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION diff --git a/renderdoc/data/hlsl/debugcbuffers.h b/renderdoc/data/hlsl/debugcbuffers.h index 7c72ccfae..7edac06a7 100644 --- a/renderdoc/data/hlsl/debugcbuffers.h +++ b/renderdoc/data/hlsl/debugcbuffers.h @@ -45,7 +45,7 @@ cbuffer FontCBuffer REG(b0) { float2 TextPosition; - float CharacterOffsetX; + float txtpadding; float TextSize; float2 CharacterSize; diff --git a/renderdoc/data/hlsl/debugtext.hlsl b/renderdoc/data/hlsl/debugtext.hlsl index a069d7c19..61087a0a6 100644 --- a/renderdoc/data/hlsl/debugtext.hlsl +++ b/renderdoc/data/hlsl/debugtext.hlsl @@ -25,40 +25,55 @@ // text shader, used for the overlay in game so that we can pass indices in the positon stream // and it figures out the right place in the text texture to sample. -struct a2v +struct glyph { - float3 pos : POSITION; - uint tex : TEXCOORD0; + float4 posdata; + float4 uvdata; +}; + +cbuffer glyphdata : register(b1) +{ + glyph glyphs[127-32]; }; struct v2f { - float4 pos : SV_POSITION; - float4 tex : TEXCOORD0; + float4 pos : SV_Position; + float4 tex : TEX; + float2 glyphuv : GLYPH; }; -v2f RENDERDOC_TextVS(a2v IN) +v2f RENDERDOC_TextVS(float3 pos : POSITION, uint tex : GLYPHIDX) { v2f OUT = (v2f)0; - OUT.pos = float4((float2(IN.pos.z,0) + IN.pos.xy)*TextSize*FontScreenAspect.xy + TextPosition.xy, 0, 1)-float4(1,-1,0,0); - OUT.tex.xy = (IN.pos.xy+float2(0,1))*CharacterSize.xy + float2((IN.tex.x-1)*CharacterOffsetX, 0); - - if(IN.tex.x == 0) - OUT.tex.xy = 0; + float2 charPos = float2(pos.z + pos.x + TextPosition.x, -pos.y - TextPosition.y); + glyph G = glyphs[tex]; + + OUT.pos = float4(charPos.xy*2.0f*TextSize*FontScreenAspect.xy + float2(-1, 1), 1, 1); + OUT.glyphuv.xy = (pos.xy - G.posdata.xy) * G.posdata.zw; + OUT.tex = G.uvdata * CharacterSize.xyxy; + return OUT; } SamplerState pointSample : register(s0); SamplerState linearSample : register(s1); -Texture2D debugTexture : register(t0); +Texture2D fontTexture : register(t0); float4 RENDERDOC_TextPS(v2f IN) : SV_Target0 { - IN.tex.y = 1 - IN.tex.y; + float text = 0; - float4 text = debugTexture.Sample(linearSample, IN.tex.xy).xxxx; + if(IN.glyphuv.x >= 0.0f && IN.glyphuv.x <= 1.0f && + IN.glyphuv.y >= 0.0f && IN.glyphuv.y <= 1.0f) + { + float2 uv; + uv.x = lerp(IN.tex.x, IN.tex.z, IN.glyphuv.x); + uv.y = lerp(IN.tex.y, IN.tex.w, IN.glyphuv.y); + text = fontTexture.Sample(linearSample, uv.xy).x; + } - return text + float4(0.0.xxx, 0.5); + return float4(text.xxx, saturate(text + 0.5f)); } diff --git a/renderdoc/data/renderdoc.rc b/renderdoc/data/renderdoc.rc index b77caaa7f..6b2c7aadc 100644 --- a/renderdoc/data/renderdoc.rc +++ b/renderdoc/data/renderdoc.rc @@ -117,6 +117,8 @@ RESOURCE_generic_vert TYPE_EMBED "glsl/generic.vert" RESOURCE_generic_frag TYPE_EMBED "glsl/generic.frag" RESOURCE_mesh_vert TYPE_EMBED "glsl/mesh.vert" +RESOURCE_sourcecodepro_ttf TYPE_EMBED "sourcecodepro.ttf" + #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // diff --git a/renderdoc/data/resource.h b/renderdoc/data/resource.h index 0ac4ca753..b3006168c 100644 --- a/renderdoc/data/resource.h +++ b/renderdoc/data/resource.h @@ -19,6 +19,8 @@ #define RESOURCE_generic_frag 206 #define RESOURCE_mesh_vert 207 +#define RESOURCE_sourcecodepro_ttf 301 + #if !defined(STRINGIZE) #define STRINGIZE2(a) #a #define STRINGIZE(a) STRINGIZE2(a) diff --git a/renderdoc/data/sourcecodepro.ttf b/renderdoc/data/sourcecodepro.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b425f9ceea70c1e76d38d66f712caeccc1a00626 GIT binary patch literal 120252 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 z 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 // used for serialising out ms textures - converts typeless to uint typed where possible, @@ -991,67 +993,6 @@ bool D3D11DebugManager::InitDebugRendering() } } - { - D3D11_SUBRESOURCE_DATA initialPos; - - float *buf = new float[(2 + FONT_MAX_CHARS*4) *3]; - - // tri strip with degenerates to split characters: - // - // 0--24--68--.. - // | /|| /|| / - // |/ ||/ ||/ - // 1--35--79--.. - - buf[0] = 0.0f; - buf[1] = 0.0f; - buf[2] = 0.0f; - - buf[3] = 0.0f; - buf[4] = -1.0f; - buf[5] = 0.0f; - - for(int i=1; i <= FONT_MAX_CHARS; i++) - { - buf[i*12 - 6 + 0] = 1.0f; - buf[i*12 - 6 + 1] = 0.0f; - buf[i*12 - 6 + 2] = float(i-1); - - buf[i*12 - 6 + 3] = 1.0f; - buf[i*12 - 6 + 4] = -1.0f; - buf[i*12 - 6 + 5] = float(i-1); - - - buf[i*12 + 0 + 0] = 0.0f; - buf[i*12 + 0 + 1] = 0.0f; - buf[i*12 + 0 + 2] = float(i); - - buf[i*12 + 0 + 3] = 0.0f; - buf[i*12 + 0 + 4] = -1.0f; - buf[i*12 + 0 + 5] = float(i); - } - - initialPos.pSysMem = buf; - initialPos.SysMemPitch = initialPos.SysMemSlicePitch = 0; - - D3D11_BUFFER_DESC bufDesc; - - bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - bufDesc.Usage = D3D11_USAGE_DEFAULT; - bufDesc.ByteWidth = (2 + FONT_MAX_CHARS*4) *3*sizeof(float); - bufDesc.CPUAccessFlags = 0; - bufDesc.MiscFlags = 0; - - hr = m_pDevice->CreateBuffer(&bufDesc, &initialPos, &m_DebugRender.PosBuffer); - - if(FAILED(hr)) - { - RDCERR("Failed to create font pos buffer %08x", hr); - } - - delete[] buf; - } - RenderDoc::Inst().SetProgress(DebugManagerInit, 0.9f); { @@ -1496,6 +1437,69 @@ bool D3D11DebugManager::InitStreamOut() bool D3D11DebugManager::InitFontRendering() { + HRESULT hr = S_OK; + + { + D3D11_SUBRESOURCE_DATA initialPos; + + float *buf = new float[(2 + FONT_MAX_CHARS*4) *3]; + + // tri strip with degenerates to split characters: + // + // 0--24--68--.. + // | /|| /|| / + // |/ ||/ ||/ + // 1--35--79--.. + + buf[0] = 0.0f; + buf[1] = 0.0f; + buf[2] = 0.0f; + + buf[3] = 0.0f; + buf[4] = 1.0f; + buf[5] = 0.0f; + + for(int i=1; i <= FONT_MAX_CHARS; i++) + { + buf[i*12 - 6 + 0] = 1.0f; + buf[i*12 - 6 + 1] = 0.0f; + buf[i*12 - 6 + 2] = float(i-1); + + buf[i*12 - 6 + 3] = 1.0f; + buf[i*12 - 6 + 4] = 1.0f; + buf[i*12 - 6 + 5] = float(i-1); + + + buf[i*12 + 0 + 0] = 0.0f; + buf[i*12 + 0 + 1] = 0.0f; + buf[i*12 + 0 + 2] = float(i); + + buf[i*12 + 0 + 3] = 0.0f; + buf[i*12 + 0 + 4] = 1.0f; + buf[i*12 + 0 + 5] = float(i); + } + + initialPos.pSysMem = buf; + initialPos.SysMemPitch = initialPos.SysMemSlicePitch = 0; + + D3D11_BUFFER_DESC bufDesc; + + bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + bufDesc.Usage = D3D11_USAGE_DEFAULT; + bufDesc.ByteWidth = (2 + FONT_MAX_CHARS*4) *3*sizeof(float); + bufDesc.CPUAccessFlags = 0; + bufDesc.MiscFlags = 0; + + hr = m_pDevice->CreateBuffer(&bufDesc, &initialPos, &m_DebugRender.PosBuffer); + + if(FAILED(hr)) + { + RDCERR("Failed to create font pos buffer %08x", hr); + } + + delete[] buf; + } + D3D11_TEXTURE2D_DESC desc; RDCEraseEl(desc); @@ -1504,108 +1508,54 @@ bool D3D11DebugManager::InitFontRendering() desc.ArraySize = 1; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.Format = DXGI_FORMAT_R8_UNORM; desc.Width = width; desc.Height = height; - - { - int h=height>>1; - - desc.MipLevels = 1; - - while(h >= 8) - { - desc.MipLevels++; - - h >>= 1; - } - } + desc.MipLevels = 1; desc.MiscFlags = 0; desc.SampleDesc.Quality = 0; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; - D3D11_SUBRESOURCE_DATA *initialData = new D3D11_SUBRESOURCE_DATA[desc.MipLevels]; - - ///////////////////////////////////////////////////////////////////// - BITMAPINFOHEADER bih; - RDCEraseEl(bih); - bih.biSize = sizeof(BITMAPINFOHEADER); - bih.biWidth = width; - bih.biHeight = height; - bih.biPlanes = 1; - bih.biBitCount = 32; - bih.biCompression = BI_RGB; + D3D11_SUBRESOURCE_DATA initialData; - byte **buffers = new byte *[desc.MipLevels]; + string font = GetEmbeddedResource(sourcecodepro_ttf); + byte *ttfdata = (byte *)font.c_str(); - HDC pDC = GetDC(NULL); - HDC MemDC = CreateCompatibleDC(pDC); + const int firstChar = int(' ') + 1; + const int lastChar = 127; + const int numChars = lastChar-firstChar; - SetBkColor(MemDC, RGB(0, 0, 0)); - SetTextColor(MemDC, RGB(255, 255, 255)); + byte *buf = new byte[width*height]; - HBITMAP *bmps = new HBITMAP[desc.MipLevels]; + const float pixelHeight = 20.0f; - for(UINT i=0; i < desc.MipLevels; i++) - { - int w = width>>i; - int h = height>>i; + stbtt_bakedchar chardata[numChars]; + int ret = stbtt_BakeFontBitmap(ttfdata, 0, pixelHeight, buf, width, height, firstChar, numChars, chardata); - bih.biWidth = w; - bih.biHeight = h; + m_Font.CharSize = pixelHeight; + m_Font.CharAspect = chardata->xadvance / pixelHeight; - HFONT font = CreateFont(h,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS, - CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY, FIXED_PITCH,TEXT("Consolas")); + stbtt_fontinfo f = {0}; + stbtt_InitFont(&f, ttfdata, 0); - bmps[i] = CreateCompatibleBitmap(pDC, w, h); + int ascent = 0; + stbtt_GetFontVMetrics(&f, &ascent, NULL, NULL); - SelectObject(MemDC, bmps[i]); + float maxheight = float(ascent)*stbtt_ScaleForPixelHeight(&f, pixelHeight); - SelectObject(MemDC, font); - - char str[2] = {0, 0}; - - for(int s=0; s < 127-' '-1; s++) - { - str[0] = (char)(' '+s+1); - TextOutA(MemDC, int(s*h*0.75), -1, str, 1); - } - - byte *buf = buffers[i] = new byte[w*h*4]; - - GetDIBits(MemDC, bmps[i], 0, h, buf, (BITMAPINFO *)&bih, DIB_RGB_COLORS); - - DeleteObject(font); - - // flip it right side up - byte *tmpRow = new byte[w*4]; - for(int j=0; j < h/2; j++) - { - int x = h-j-1; - memcpy(tmpRow, &buf[j*w*4], w*4); - memcpy(&buf[j*w*4], &buf[x*w*4], w*4); - memcpy(&buf[x*w*4], tmpRow, w*4); - } - delete[] tmpRow; - - ///////////////////////////////////////////////////////////////////// - - initialData[i].pSysMem = buffers[i]; - initialData[i].SysMemPitch = w*4; - initialData[i].SysMemSlicePitch = w*h*4; - } + initialData.pSysMem = buf; + initialData.SysMemPitch = width; + initialData.SysMemSlicePitch = width*height; - ID3D11Texture2D *debugTex; + ID3D11Texture2D *debugTex = NULL; - HRESULT hr = S_OK; - - hr = m_pDevice->CreateTexture2D(&desc, initialData, &debugTex); + hr = m_pDevice->CreateTexture2D(&desc, &initialData, &debugTex); if(FAILED(hr)) RDCERR("Failed to create debugTex %08x", hr); - delete[] initialData; + delete[] buf; hr = m_pDevice->CreateShaderResourceView(debugTex, NULL, &m_Font.Tex); @@ -1614,65 +1564,67 @@ bool D3D11DebugManager::InitFontRendering() SAFE_RELEASE(debugTex); - for(UINT i=0; i < desc.MipLevels; i++) + Vec4f glyphData[2*(numChars+1)]; + + m_Font.GlyphData = MakeCBuffer(sizeof(glyphData)); + + for(int i=0; i < numChars; i++) { - SAFE_DELETE_ARRAY(buffers[i]); - DeleteObject(bmps[i]); + stbtt_bakedchar *b = chardata+i; + + float x = b->xoff; + float y = b->yoff + maxheight; + + glyphData[(i+1)*2 + 0] = Vec4f(x/b->xadvance, y/pixelHeight, b->xadvance/float(b->x1 - b->x0), pixelHeight/float(b->y1 - b->y0)); + glyphData[(i+1)*2 + 1] = Vec4f(b->x0, b->y0, b->x1, b->y1); } - SAFE_DELETE_ARRAY(buffers); - SAFE_DELETE_ARRAY(bmps); - DeleteDC(pDC); - DeleteDC(MemDC); + FillCBuffer(m_Font.GlyphData, (float *)&glyphData, sizeof(glyphData)); + m_Font.CBuffer = MakeCBuffer(sizeof(FontCBuffer)); + + D3D11_BUFFER_DESC bufDesc; + + bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + bufDesc.MiscFlags = 0; + bufDesc.Usage = D3D11_USAGE_DYNAMIC; + bufDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + bufDesc.ByteWidth = 2+FONT_MAX_CHARS*4*sizeof(uint32_t); + + hr = m_pDevice->CreateBuffer(&bufDesc, NULL, &m_Font.CharBuffer); + + if(FAILED(hr)) + RDCERR("Failed to create m_Font.CharBuffer %08x", hr); + + string fullhlsl = ""; { - HRESULT hr = S_OK; + string debugShaderCBuf = GetEmbeddedResource(debugcbuffers_h); + string textShaderHLSL = GetEmbeddedResource(debugtext_hlsl); - m_Font.CBuffer = MakeCBuffer(sizeof(FontCBuffer)); - - D3D11_BUFFER_DESC bufDesc; - - bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - bufDesc.MiscFlags = 0; - bufDesc.Usage = D3D11_USAGE_DYNAMIC; - bufDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - bufDesc.ByteWidth = 2+FONT_MAX_CHARS*4*4; - - hr = m_pDevice->CreateBuffer(&bufDesc, NULL, &m_Font.CharBuffer); - - if(FAILED(hr)) - RDCERR("Failed to create m_Font.CharBuffer %08x", hr); - - string fullhlsl = ""; - { - string textShaderHLSL = GetEmbeddedResource(debugtext_hlsl); - string debugShaderCBuf = GetEmbeddedResource(debugcbuffers_h); - - fullhlsl = debugShaderCBuf + textShaderHLSL; - } - - D3D11_INPUT_ELEMENT_DESC inputDescs[2]; - - inputDescs[0].SemanticName = "POSITION"; - inputDescs[0].SemanticIndex = 0; - inputDescs[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; - inputDescs[0].InputSlot = 0; - inputDescs[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; - inputDescs[0].AlignedByteOffset = 0; - inputDescs[0].InstanceDataStepRate = 0; - - inputDescs[1].SemanticName = "TEXCOORD"; - inputDescs[1].SemanticIndex = 0; - inputDescs[1].Format = DXGI_FORMAT_R32_UINT; - inputDescs[1].InputSlot = 1; - inputDescs[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; - inputDescs[1].AlignedByteOffset = 0; - inputDescs[1].InstanceDataStepRate = 0; - - m_Font.VS = MakeVShader(fullhlsl.c_str(), "RENDERDOC_TextVS", "vs_4_0", 2, inputDescs, &m_Font.Layout); - m_Font.PS = MakePShader(fullhlsl.c_str(), "RENDERDOC_TextPS", "ps_4_0"); + fullhlsl = debugShaderCBuf + textShaderHLSL; } + D3D11_INPUT_ELEMENT_DESC inputDescs[2]; + + inputDescs[0].SemanticName = "POSITION"; + inputDescs[0].SemanticIndex = 0; + inputDescs[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; + inputDescs[0].InputSlot = 0; + inputDescs[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + inputDescs[0].AlignedByteOffset = 0; + inputDescs[0].InstanceDataStepRate = 0; + + inputDescs[1].SemanticName = "GLYPHIDX"; + inputDescs[1].SemanticIndex = 0; + inputDescs[1].Format = DXGI_FORMAT_R32_UINT; + inputDescs[1].InputSlot = 1; + inputDescs[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + inputDescs[1].AlignedByteOffset = 0; + inputDescs[1].InstanceDataStepRate = 0; + + m_Font.VS = MakeVShader(fullhlsl.c_str(), "RENDERDOC_TextVS", "vs_4_0", 2, inputDescs, &m_Font.Layout); + m_Font.PS = MakePShader(fullhlsl.c_str(), "RENDERDOC_TextPS", "ps_4_0"); + return true; } @@ -3210,7 +3162,7 @@ D3D11DebugManager::TextureShaderDetails D3D11DebugManager::GetShaderDetails(Reso return details; } -void D3D11DebugManager::RenderText(float x, float y, float size, const char *textfmt, ...) +void D3D11DebugManager::RenderText(float x, float y, const char *textfmt, ...) { static char tmpBuf[4096]; @@ -3220,18 +3172,16 @@ void D3D11DebugManager::RenderText(float x, float y, float size, const char *tex tmpBuf[4095] = '\0'; va_end(args); - // normalize size to 720 (and scale respectively) - // invert y co-ordinates for convenience - RenderTextInternal(x, -y, size*(720.0f/float(GetHeight())), tmpBuf); + RenderTextInternal(x, y, tmpBuf); } -void D3D11DebugManager::RenderTextInternal(float x, float y, float size, const char *text) +void D3D11DebugManager::RenderTextInternal(float x, float y, const char *text) { if(char *t = strchr((char *)text, '\n')) { *t = 0; - RenderTextInternal(x, y, size, text); - RenderTextInternal(x, y-18.0f, size, t+1); + RenderTextInternal(x, y, text); + RenderTextInternal(x, y+1.0f, t+1); *t = '\n'; return; } @@ -3243,18 +3193,17 @@ void D3D11DebugManager::RenderTextInternal(float x, float y, float size, const c FontCBuffer data; - data.TextPosition.x = x*(2.0f/float(GetWidth())); - data.TextPosition.y = y*(2.0f/float(GetHeight())); + data.TextPosition.x = x; + data.TextPosition.y = y; - data.FontScreenAspect.x = (float(GetHeight())/float(GetWidth()))*0.5f; // 0.5 = character width / character height - data.FontScreenAspect.y = 1.0f; + data.FontScreenAspect.x = 1.0f/float(GetWidth()); + data.FontScreenAspect.y = 1.0f/float(GetHeight()); - data.CharacterSize.x = 0.5f*(float(FONT_TEX_HEIGHT)/float(FONT_TEX_WIDTH)); - data.CharacterSize.y = 1.0f; + data.TextSize = m_Font.CharSize; + data.FontScreenAspect.x *= m_Font.CharAspect; - data.TextSize = size*0.05f; // default arbitrary font size - - data.CharacterOffsetX = 0.75f*(float(FONT_TEX_HEIGHT)/float(FONT_TEX_WIDTH)); + data.CharacterSize.x = 1.0f/float(FONT_TEX_WIDTH); + data.CharacterSize.y = 1.0f/float(FONT_TEX_HEIGHT); D3D11_MAPPED_SUBRESOURCE mapped; @@ -3291,6 +3240,7 @@ void D3D11DebugManager::RenderTextInternal(float x, float y, float size, const c m_pImmediateContext->VSSetShader(m_Font.VS, NULL, 0); m_pImmediateContext->VSSetConstantBuffers(0, 1, &m_Font.CBuffer); + m_pImmediateContext->VSSetConstantBuffers(1, 1, &m_Font.GlyphData); m_pImmediateContext->HSSetShader(NULL, NULL, 0); m_pImmediateContext->DSSetShader(NULL, NULL, 0); diff --git a/renderdoc/driver/d3d11/d3d11_debug.h b/renderdoc/driver/d3d11/d3d11_debug.h index d3f93f042..0da55807f 100644 --- a/renderdoc/driver/d3d11/d3d11_debug.h +++ b/renderdoc/driver/d3d11/d3d11_debug.h @@ -132,7 +132,7 @@ class D3D11DebugManager void TimeDrawcalls(rdctype::array &arr); - void RenderText(float x, float y, float size, const char *textfmt, ...); + void RenderText(float x, float y, const char *textfmt, ...); void RenderMesh(uint32_t frameID, const vector &events, MeshDisplay cfg); ID3D11Buffer *MakeCBuffer(float *data, size_t size); @@ -339,14 +339,14 @@ class D3D11DebugManager bool InitFontRendering(); void ShutdownFontRendering(); - void RenderTextInternal(float x, float y, float size, const char *text); + void RenderTextInternal(float x, float y, const char *text); void CreateCustomShaderTex(uint32_t w, uint32_t h); void PixelHistoryCopyPixel(CopyPixelParams ¶ms, uint32_t x, uint32_t y); - static const int FONT_TEX_WIDTH = 4096; - static const int FONT_TEX_HEIGHT = 48; + static const int FONT_TEX_WIDTH = 256; + static const int FONT_TEX_HEIGHT = 128; static const int FONT_MAX_CHARS = 256; static const uint32_t STAGE_BUFFER_BYTE_SIZE = 4*1024*1024; @@ -359,6 +359,7 @@ class D3D11DebugManager SAFE_RELEASE(Layout); SAFE_RELEASE(Tex); SAFE_RELEASE(CBuffer); + SAFE_RELEASE(GlyphData); SAFE_RELEASE(CharBuffer); SAFE_RELEASE(VS); SAFE_RELEASE(PS); @@ -367,9 +368,13 @@ class D3D11DebugManager ID3D11InputLayout *Layout; ID3D11ShaderResourceView *Tex; ID3D11Buffer *CBuffer; + ID3D11Buffer *GlyphData; ID3D11Buffer *CharBuffer; ID3D11VertexShader *VS; ID3D11PixelShader *PS; + + float CharAspect; + float CharSize; } m_Font; struct DebugRenderData diff --git a/renderdoc/driver/d3d11/d3d11_device.cpp b/renderdoc/driver/d3d11/d3d11_device.cpp index f171ca79b..019a6ce50 100644 --- a/renderdoc/driver/d3d11/d3d11_device.cpp +++ b/renderdoc/driver/d3d11/d3d11_device.cpp @@ -2776,7 +2776,7 @@ bool WrappedID3D11Device::EndFrameCapture(void *wnd) default: break; } - GetDebugManager()->RenderText(0.0f, 0.0f, 1.0f, "Failed to capture frame %u: %hs", m_FrameCounter, reasonString); + GetDebugManager()->RenderText(0.0f, 0.0f, "Failed to capture frame %u: %hs", m_FrameCounter, reasonString); GetDebugManager()->SetOutputDimensions(w, h); } @@ -2961,15 +2961,19 @@ HRESULT WrappedID3D11Device::Present(IDXGISwapChain *swap, UINT SyncInterval, UI m_AvgFrametime, m_MinFrametime, m_MaxFrametime, 1000.0f/m_AvgFrametime); } - if(!overlayText.empty()) - GetDebugManager()->RenderText(0.0f, 0.0f, 1.0f, overlayText.c_str()); + float y=0.0f; - size_t i=0; + if(!overlayText.empty()) + { + GetDebugManager()->RenderText(0.0f, y, overlayText.c_str()); y += 1.0f; + } if(overlay & eOverlay_CaptureList) { - for(i=0; i < m_FrameRecord.size(); i++) - GetDebugManager()->RenderText(0.0f, (float)(i+1)*18.0f, 1.0f, "Captured frame %d.\n", m_FrameRecord[i].frameInfo.frameNumber); + for(size_t i=0; i < m_FrameRecord.size(); i++) + { + GetDebugManager()->RenderText(0.0f, y, "Captured frame %d.\n", m_FrameRecord[i].frameInfo.frameNumber); y += 1.0f; + } } if(m_FailedFrame > 0) @@ -2982,12 +2986,12 @@ HRESULT WrappedID3D11Device::Present(IDXGISwapChain *swap, UINT SyncInterval, UI default: break; } - GetDebugManager()->RenderText(0.0f, (float)(++i)*18.0f, 1.0f, "Failed capture at frame %d:\n", m_FailedFrame); - GetDebugManager()->RenderText(0.0f, (float)(++i)*18.0f, 1.0f, " %hs\n", reasonString); + GetDebugManager()->RenderText(0.0f, y, "Failed capture at frame %d:\n", m_FailedFrame); y += 1.0f; + GetDebugManager()->RenderText(0.0f, y, " %hs\n", reasonString); y += 1.0f; } #if !defined(RELEASE) - GetDebugManager()->RenderText(0.0f, float(++i)*18.0f, 1.0f, "%llu chunks - %.2f MB", Chunk::NumLiveChunks(), float(Chunk::TotalMem())/1024.0f/1024.0f); + GetDebugManager()->RenderText(0.0f, y, "%llu chunks - %.2f MB", Chunk::NumLiveChunks(), float(Chunk::TotalMem())/1024.0f/1024.0f); y += 1.0f; #endif } else @@ -3009,7 +3013,7 @@ HRESULT WrappedID3D11Device::Present(IDXGISwapChain *swap, UINT SyncInterval, UI if(!keys.empty()) str += " to cycle between swapchains"; - GetDebugManager()->RenderText(0.0f, 0.0f, 1.0f, str.c_str()); + GetDebugManager()->RenderText(0.0f, 0.0f, str.c_str()); } GetDebugManager()->SetOutputDimensions(w, h); diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index bdb24a610..16d999d7c 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -226,6 +226,7 @@ + @@ -301,17 +302,13 @@ - - NoListing - + - - AssemblyAndSourceCode - + @@ -390,4 +387,4 @@ - \ No newline at end of file + diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index 575afacdd..d05b1aed0 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -297,6 +297,9 @@ 3rdparty\stb + + 3rdparty\stb +