Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gamedev/fggl
  • onuralpsezer/fggl
2 results
Show changes
Showing
with 12924 additions and 0 deletions
// stb_truetype.h - v1.26 - public domain
// authored from 2009-2021 by Sean Barrett / RAD Game Tools
//
// =======================================================================
//
// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES
//
// This library does no range checking of the offsets found in the file,
// meaning an attacker can use it to read arbitrary memory.
//
// =======================================================================
//
// This library processes TrueType files:
// parse files
// extract glyph metrics
// extract glyph shapes
// render glyphs to one-channel bitmaps with antialiasing (box filter)
// render glyphs to one-channel SDF bitmaps (signed-distance field/function)
//
// 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
// Dougall Johnson: OpenType / Type 2 font handling
// Daniel Ribeiro Maciel: basic GPOS-based kerning
//
// Misc other:
// Ryan Gordon
// Simon Glass
// github:IntellectualKitty
// Imanol Celaya
// Daniel Ribeiro Maciel
//
// Bug/warning reports/fixes:
// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe
// Cass Everitt Martins Mozeiko github:aloucks
// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam
// Brian Hook Omar Cornut github:vassvik
// Walter van Niftrik Ryan Griege
// David Gow Peter LaValle
// David Given Sergey Popov
// Ivan-Assen Ivanov Giumo X. Clanjor
// Anthony Pesch Higor Euripedes
// Johan Duparc Thomas Fields
// Hou Qiming Derek Vinyard
// Rob Loach Cort Stratton
// Kenney Phillis Jr. Brian Costabile
// Ken Voskuil (kaesve)
//
// VERSION HISTORY
//
// 1.26 (2021-08-28) fix broken rasterizer
// 1.25 (2021-07-11) many fixes
// 1.24 (2020-02-05) fix warning
// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)
// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined
// 1.21 (2019-02-25) fix warning
// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()
// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod
// 1.18 (2018-01-29) add missing function
// 1.17 (2017-07-23) make more arguments const; doc fix
// 1.16 (2017-07-12) SDF support
// 1.15 (2017-03-03) make more arguments const
// 1.14 (2017-01-16) num-fonts-in-TTC function
// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts
// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
// 1.11 (2016-04-02) fix unused-variable warning
// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef
// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly
// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
// variant PackFontRanges to pack and render in separate phases;
// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
// fixed an assert() bug in the new rasterizer
// replace assert() with STBTT_assert() in new rasterizer
//
// Full history can be found at the end of this file.
//
// LICENSE
//
// See end of file for license information.
//
// USAGE
//
// Include this file in whatever places need 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.
//
// To make the implementation private to the file that generates the implementation,
// #define STBTT_STATIC
//
// Simple 3D API (don't ship this, but it's fine for tools and quick start)
// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture
// stbtt_GetBakedQuad() -- compute quad to draw for a given char
//
// Improved 3D API (more shippable):
// #include "stb_rect_pack.h" -- optional, but you really want it
// stbtt_PackBegin()
// stbtt_PackSetOversampling() -- for improved quality on small fonts
// stbtt_PackFontRanges() -- pack and renders
// stbtt_PackEnd()
// stbtt_GetPackedQuad()
//
// "Load" a font file from a memory buffer (you have to keep the buffer loaded)
// stbtt_InitFont()
// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections
// stbtt_GetNumberOfFonts() -- number of fonts 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_GetFontVMetricsOS2()
// stbtt_GetCodepointKernAdvance()
//
// Starting with version 1.06, the rasterizer was replaced with a new,
// faster and generally-more-precise rasterizer. The new rasterizer more
// accurately measures pixel coverage for anti-aliasing, except in the case
// where multiple shapes overlap, in which case it overestimates the AA pixel
// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If
// this turns out to be a problem, you can re-enable the old rasterizer with
// #define STBTT_RASTERIZER_VERSION 1
// which will incur about a 15% speed hit.
//
// 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 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.
//
// DETAILED USAGE:
//
// Scale:
// Select how high you want the font to be, in points or pixels.
// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute
// a scale factor SF that will be used by all other functions.
//
// Baseline:
// You need to select a y-coordinate that is the baseline of where
// your text will appear. Call GetFontBoundingBox to get the baseline-relative
// bounding box for all characters. SF*-y0 will be the distance in pixels
// that the worst-case character could extend above the baseline, so if
// you want the top edge of characters to appear at the top of the
// screen where y=0, then you would set the baseline to SF*-y0.
//
// Current point:
// Set the current point where the first character will appear. The
// first character could extend left of the current point; this is font
// dependent. You can either choose a current point that is the leftmost
// point and hope, or add some padding, or check the bounding box or
// left-side-bearing of the first character to be displayed and set
// the current point based on that.
//
// Displaying a character:
// Compute the bounding box of the character. It will contain signed values
// relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1,
// then the character should be displayed in the rectangle from
// <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1).
//
// Advancing for the next character:
// Call GlyphHMetrics, and compute 'current_point += SF * advance'.
//
//
// 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.
//
//
// PERFORMANCE MEASUREMENTS FOR 1.06:
//
// 32-bit 64-bit
// Previous release: 8.83 s 7.68 s
// Pool allocations: 7.72 s 6.34 s
// Inline sort : 6.54 s 5.65 s
// New rasterizer : 5.63 s 5.00 s
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////
//// SAMPLE PROGRAMS
////
//
// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless.
// See "tests/truetype_demo_win32.c" for a complete version.
#if 0
#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation
#include "stb_truetype.h"
unsigned char ttf_buffer[1<<20];
unsigned char temp_bitmap[512*512];
stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs
GLuint ftex;
void my_stbtt_initfont(void)
{
fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb"));
stbtt_BakeFontBitmap(ttf_buffer,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
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_2D);
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.t0); glVertex2f(q.x0,q.y0);
glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0);
glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1);
glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1);
}
++text;
}
glEnd();
}
#endif
//
//
//////////////////////////////////////////////////////////////////////////////
//
// Complete program (this compiles): get a single bitmap, print as ASCII art
//
#if 0
#include <stdio.h>
#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!"; // intentionally misspelled to show 'lj' brokenness
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, e.g. if you don't
//// link with the C runtime library.
#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];
// e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h
#ifndef STBTT_ifloor
#include <math.h>
#define STBTT_ifloor(x) ((int) floor(x))
#define STBTT_iceil(x) ((int) ceil(x))
#endif
#ifndef STBTT_sqrt
#include <math.h>
#define STBTT_sqrt(x) sqrt(x)
#define STBTT_pow(x,y) pow(x,y)
#endif
#ifndef STBTT_fmod
#include <math.h>
#define STBTT_fmod(x,y) fmod(x,y)
#endif
#ifndef STBTT_cos
#include <math.h>
#define STBTT_cos(x) cos(x)
#define STBTT_acos(x) acos(x)
#endif
#ifndef STBTT_fabs
#include <math.h>
#define STBTT_fabs(x) fabs(x)
#endif
// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h
#ifndef STBTT_malloc
#include <stdlib.h>
#define STBTT_malloc(x,u) ((void)(u),malloc(x))
#define STBTT_free(x,u) ((void)(u),free(x))
#endif
#ifndef STBTT_assert
#include <assert.h>
#define STBTT_assert(x) assert(x)
#endif
#ifndef STBTT_strlen
#include <string.h>
#define STBTT_strlen(x) strlen(x)
#endif
#ifndef STBTT_memcpy
#include <string.h>
#define STBTT_memcpy memcpy
#define STBTT_memset memset
#endif
#endif
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
////
//// INTERFACE
////
////
#ifndef __STB_INCLUDE_STB_TRUETYPE_H__
#define __STB_INCLUDE_STB_TRUETYPE_H__
#ifdef STBTT_STATIC
#define STBTT_DEF static
#else
#define STBTT_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
// private structure
typedef struct {
unsigned char *data;
int cursor;
int size;
} stbtt__buf;
//////////////////////////////////////////////////////////////////////////////
//
// 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;
STBTT_DEF 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;
STBTT_DEF void stbtt_GetBakedQuad(const 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.
STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata,
int index,
float size,
float *ascent,
float *descent,
float *lineGap);
// Query the font vertical metrics without having to create a font first.
//////////////////////////////////////////////////////////////////////////////
//
// NEW TEXTURE BAKING API
//
// This provides options for packing multiple fonts into one atlas, not
// perfectly but better than nothing.
typedef struct {
unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap
float xoff, yoff, xadvance;
float xoff2, yoff2;
} stbtt_packedchar;
typedef struct stbtt_pack_context stbtt_pack_context;
typedef struct stbtt_fontinfo stbtt_fontinfo;
#ifndef STB_RECT_PACK_VERSION
typedef struct stbrp_rect stbrp_rect;
#endif
STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc,
unsigned char *pixels,
int width,
int height,
int stride_in_bytes,
int padding,
void *alloc_context);
// Initializes a packing context stored in the passed-in stbtt_pack_context.
// Future calls using this context will pack characters into the bitmap passed
// in here: a 1-channel bitmap that is width * height. stride_in_bytes is
// the distance from one row to the next (or 0 to mean they are packed tightly
// together). "padding" is the amount of padding to leave between each
// character (normally you want '1' for bitmaps you'll use as textures with
// bilinear filtering).
//
// Returns 0 on failure, 1 on success.
STBTT_DEF void stbtt_PackEnd(stbtt_pack_context *spc);
// Cleans up the packing context and frees all memory.
#define STBTT_POINT_SIZE(x) (-(x))
STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc,
const unsigned char *fontdata,
int font_index,
float font_size,
int first_unicode_char_in_range,
int num_chars_in_range,
stbtt_packedchar *chardata_for_range);
// Creates character bitmaps from the font_index'th font found in fontdata (use
// font_index=0 if you don't know what that is). It creates num_chars_in_range
// bitmaps for characters with unicode values starting at first_unicode_char_in_range
// and increasing. Data for how to render them is stored in chardata_for_range;
// pass these to stbtt_GetPackedQuad to get back renderable quads.
//
// font_size is the full height of the character from ascender to descender,
// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed
// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE()
// and pass that result as 'font_size':
// ..., 20 , ... // font max minus min y is 20 pixels tall
// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall
typedef struct {
float font_size;
int
first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint
int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints
int num_chars;
stbtt_packedchar *chardata_for_range; // output
unsigned char h_oversample, v_oversample; // don't set these, they're used internally
} stbtt_pack_range;
STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc,
const unsigned char *fontdata,
int font_index,
stbtt_pack_range *ranges,
int num_ranges);
// Creates character bitmaps from multiple ranges of characters stored in
// ranges. This will usually create a better-packed bitmap than multiple
// calls to stbtt_PackFontRange. Note that you can call this multiple
// times within a single PackBegin/PackEnd.
STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample);
// Oversampling a font increases the quality by allowing higher-quality subpixel
// positioning, and is especially valuable at smaller text sizes.
//
// This function sets the amount of oversampling for all following calls to
// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given
// pack context. The default (no oversampling) is achieved by h_oversample=1
// and v_oversample=1. The total number of pixels required is
// h_oversample*v_oversample larger than the default; for example, 2x2
// oversampling requires 4x the storage of 1x1. For best results, render
// oversampled textures with bilinear filtering. Look at the readme in
// stb/tests/oversample for information about oversampled fonts
//
// To use with PackFontRangesGather etc., you must set it before calls
// call to PackFontRangesGatherRects.
STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip);
// If skip != 0, this tells stb_truetype to skip any codepoints for which
// there is no corresponding glyph. If skip=0, which is the default, then
// codepoints without a glyph recived the font's "missing character" glyph,
// typically an empty box by convention.
STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *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 align_to_integer);
STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc,
const stbtt_fontinfo *info,
stbtt_pack_range *ranges,
int num_ranges,
stbrp_rect *rects);
STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects);
STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc,
const stbtt_fontinfo *info,
stbtt_pack_range *ranges,
int num_ranges,
stbrp_rect *rects);
// Calling these functions in sequence is roughly equivalent to calling
// stbtt_PackFontRanges(). If you more control over the packing of multiple
// fonts, or if you want to pack custom data into a font texture, take a look
// at the source to of stbtt_PackFontRanges() and create a custom version
// using these functions, e.g. call GatherRects multiple times,
// building up a single array of rects, then call PackRects once,
// then call RenderIntoRects repeatedly. This may result in a
// better packing than calling PackFontRanges multiple times
// (or it may not).
// this is an opaque structure that you shouldn't mess with which holds
// all the context needed from PackBegin to PackEnd.
struct stbtt_pack_context {
void *user_allocator_context;
void *pack_info;
int width;
int height;
int stride_in_bytes;
int padding;
int skip_missing;
unsigned int h_oversample, v_oversample;
unsigned char *pixels;
void *nodes;
};
//////////////////////////////////////////////////////////////////////////////
//
// FONT LOADING
//
//
STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data);
// This function will determine the number of fonts in a font file. TrueType
// collection (.ttc) files may contain multiple fonts, while TrueType font
// (.ttf) files only contain one font. The number of fonts can be used for
// indexing with the previous function where the index is between zero and one
// less than the total fonts. If an error occurs, -1 is returned.
STBTT_DEF 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.
// The following structure is defined publicly so you can declare one on
// the stack or as a global or etc, but you should treat it as opaque.
struct stbtt_fontinfo {
void *userdata;
unsigned char *data; // pointer to .ttf file
int fontstart; // offset of start of font
int numGlyphs; // number of glyphs, needed for range checking
int loca, head, glyf, hhea, hmtx, kern, gpos, svg; // table locations as offset from start of .ttf
int index_map; // a cmap mapping for our chosen character encoding
int indexToLocFormat; // format needed to map from glyph index to glyph
stbtt__buf cff; // cff font data
stbtt__buf charstrings; // the charstring index
stbtt__buf gsubrs; // global charstring subroutines index
stbtt__buf subrs; // private charstring subroutines index
stbtt__buf fontdicts; // array of font dicts
stbtt__buf fdselect; // map from glyph to fontdict
};
STBTT_DEF 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
STBTT_DEF 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.
// Returns 0 if the character codepoint is not defined in the font.
//////////////////////////////////////////////////////////////////////////////
//
// CHARACTER PROPERTIES
//
STBTT_DEF 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.
STBTT_DEF 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.
STBTT_DEF 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
STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap);
// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2
// table (specific to MS/Windows TTF files).
//
// Returns 1 on success (table present), 0 on failure.
STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1);
// the bounding box around all possible characters
STBTT_DEF 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
STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2);
// an additional amount to add to the 'advance' value between ch1 and ch2
STBTT_DEF 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
STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info,
int glyph_index,
int *advanceWidth,
int *leftSideBearing);
STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2);
STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
// as above, but takes one or more glyph indices for greater efficiency
typedef struct stbtt_kerningentry {
int glyph1; // use stbtt_FindGlyphIndex
int glyph2;
int advance;
} stbtt_kerningentry;
STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info);
STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry *table, int table_length);
// Retrieves a complete list of all of the kerning pairs provided by the font
// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write.
// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1)
//////////////////////////////////////////////////////////////////////////////
//
// 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,
STBTT_vcubic
};
#endif
#ifndef stbtt_vertex // you can predefine this to use different values
// (we share this with other code at RAD)
#define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file
typedef struct {
stbtt_vertex_type x, y, cx, cy, cx1, cy1;
unsigned char type, padding;
} stbtt_vertex;
#endif
STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index);
// returns non-zero if nothing is drawn for this glyph
STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices);
STBTT_DEF 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 contours. 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.
STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices);
// frees the data allocated above
STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl);
STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg);
STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg);
// fills svg with the character's SVG data.
// returns data size or 0 if SVG not found.
//////////////////////////////////////////////////////////////////////////////
//
// BITMAP RENDERING
//
STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata);
// frees the bitmap allocated below
STBTT_DEF 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
STBTT_DEF 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
STBTT_DEF 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.
STBTT_DEF 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
STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(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 oversample_x,
int oversample_y,
float *sub_x,
float *sub_y,
int codepoint);
// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering
// is performed (see stbtt_PackSetOversampling)
STBTT_DEF 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.)
STBTT_DEF 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)
STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info,
float scale_x,
float scale_y,
int glyph,
int *width,
int *height,
int *xoff,
int *yoff);
STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info,
float scale_x,
float scale_y,
float shift_x,
float shift_y,
int glyph,
int *width,
int *height,
int *xoff,
int *yoff);
STBTT_DEF 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_DEF 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);
STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(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 oversample_x,
int oversample_y,
float *sub_x,
float *sub_y,
int glyph);
STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font,
int glyph,
float scale_x,
float scale_y,
int *ix0,
int *iy0,
int *ix1,
int *iy1);
STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font,
int glyph,
float scale_x,
float scale_y,
float shift_x,
float shift_y,
int *ix0,
int *iy0,
int *ix1,
int *iy1);
// @TODO: don't expose this structure
typedef struct {
int w, h, stride;
unsigned char *pixels;
} stbtt__bitmap;
// rasterize a shape with quadratic beziers into a bitmap
STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into
float flatness_in_pixels, // allowable error of curve in pixels
stbtt_vertex *vertices, // array of vertices defining shape
int num_verts, // number of vertices in above array
float scale_x, float scale_y, // scale applied to input vertices
float shift_x, float shift_y, // translation applied to input vertices
int x_off, int y_off, // another translation applied to input
int invert, // if non-zero, vertically flip shape
void *userdata); // context for to STBTT_MALLOC
//////////////////////////////////////////////////////////////////////////////
//
// Signed Distance Function (or Field) rendering
STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata);
// frees the SDF bitmap allocated below
STBTT_DEF unsigned char *stbtt_GetGlyphSDF(const stbtt_fontinfo *info,
float scale,
int glyph,
int padding,
unsigned char onedge_value,
float pixel_dist_scale,
int *width,
int *height,
int *xoff,
int *yoff);
STBTT_DEF unsigned char *stbtt_GetCodepointSDF(const stbtt_fontinfo *info,
float scale,
int codepoint,
int padding,
unsigned char onedge_value,
float pixel_dist_scale,
int *width,
int *height,
int *xoff,
int *yoff);
// These functions compute a discretized SDF field for a single character, suitable for storing
// in a single-channel texture, sampling with bilinear filtering, and testing against
// larger than some threshold to produce scalable fonts.
// info -- the font
// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap
// glyph/codepoint -- the character to generate the SDF for
// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0),
// which allows effects like bit outlines
// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character)
// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale)
// if positive, > onedge_value is inside; if negative, < onedge_value is inside
// width,height -- output height & width of the SDF bitmap (including padding)
// xoff,yoff -- output origin of the character
// return value -- a 2D array of bytes 0..255, width*height in size
//
// pixel_dist_scale & onedge_value are a scale & bias that allows you to make
// optimal use of the limited 0..255 for your application, trading off precision
// and special effects. SDF values outside the range 0..255 are clamped to 0..255.
//
// Example:
// scale = stbtt_ScaleForPixelHeight(22)
// padding = 5
// onedge_value = 180
// pixel_dist_scale = 180/5.0 = 36.0
//
// This will create an SDF bitmap in which the character is about 22 pixels
// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled
// shape, sample the SDF at each pixel and fill the pixel if the SDF value
// is greater than or equal to 180/255. (You'll actually want to antialias,
// which is beyond the scope of this example.) Additionally, you can compute
// offset outlines (e.g. to stroke the character border inside & outside,
// or only outside). For example, to fill outside the character up to 3 SDF
// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above
// choice of variables maps a range from 5 pixels outside the shape to
// 2 pixels inside the shape to 0..255; this is intended primarily for apply
// outside effects only (the interior range is needed to allow proper
// antialiasing of the font at *smaller* sizes)
//
// The function computes the SDF analytically at each SDF pixel, not by e.g.
// building a higher-res bitmap and approximating it. In theory the quality
// should be as high as possible for an SDF of this size & representation, but
// unclear if this is true in practice (perhaps building a higher-res bitmap
// and computing from that can allow drop-out prevention).
//
// The algorithm has not been optimized at all, so expect it to be slow
// if computing lots of characters or very large sizes.
//////////////////////////////////////////////////////////////////////////////
//
// 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.
STBTT_DEF 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
STBTT_DEF 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
STBTT_DEF 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
#ifndef STBTT_MAX_OVERSAMPLE
#define STBTT_MAX_OVERSAMPLE 8
#endif
#if STBTT_MAX_OVERSAMPLE > 255
#error "STBTT_MAX_OVERSAMPLE cannot be > 255"
#endif
typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1];
#ifndef STBTT_RASTERIZER_VERSION
#define STBTT_RASTERIZER_VERSION 2
#endif
#ifdef _MSC_VER
#define STBTT__NOTUSED(v) (void)(v)
#else
#define STBTT__NOTUSED(v) (void)sizeof(v)
#endif
//////////////////////////////////////////////////////////////////////////
//
// stbtt__buf helpers to parse data from file
//
static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b)
{
if (b->cursor >= b->size)
return 0;
return b->data[b->cursor++];
}
static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b)
{
if (b->cursor >= b->size)
return 0;
return b->data[b->cursor];
}
static void stbtt__buf_seek(stbtt__buf *b, int o)
{
STBTT_assert(!(o > b->size || o < 0));
b->cursor = (o > b->size || o < 0) ? b->size : o;
}
static void stbtt__buf_skip(stbtt__buf *b, int o)
{
stbtt__buf_seek(b, b->cursor + o);
}
static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n)
{
stbtt_uint32 v = 0;
int i;
STBTT_assert(n >= 1 && n <= 4);
for (i = 0; i < n; i++)
v = (v << 8) | stbtt__buf_get8(b);
return v;
}
static stbtt__buf stbtt__new_buf(const void *p, size_t size)
{
stbtt__buf r;
STBTT_assert(size < 0x40000000);
r.data = (stbtt_uint8*) p;
r.size = (int) size;
r.cursor = 0;
return r;
}
#define stbtt__buf_get16(b) stbtt__buf_get((b), 2)
#define stbtt__buf_get32(b) stbtt__buf_get((b), 4)
static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s)
{
stbtt__buf r = stbtt__new_buf(NULL, 0);
if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r;
r.data = b->data + o;
r.size = s;
return r;
}
static stbtt__buf stbtt__cff_get_index(stbtt__buf *b)
{
int count, start, offsize;
start = b->cursor;
count = stbtt__buf_get16(b);
if (count) {
offsize = stbtt__buf_get8(b);
STBTT_assert(offsize >= 1 && offsize <= 4);
stbtt__buf_skip(b, offsize * count);
stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1);
}
return stbtt__buf_range(b, start, b->cursor - start);
}
static stbtt_uint32 stbtt__cff_int(stbtt__buf *b)
{
int b0 = stbtt__buf_get8(b);
if (b0 >= 32 && b0 <= 246) return b0 - 139;
else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108;
else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108;
else if (b0 == 28) return stbtt__buf_get16(b);
else if (b0 == 29) return stbtt__buf_get32(b);
STBTT_assert(0);
return 0;
}
static void stbtt__cff_skip_operand(stbtt__buf *b) {
int v, b0 = stbtt__buf_peek8(b);
STBTT_assert(b0 >= 28);
if (b0 == 30) {
stbtt__buf_skip(b, 1);
while (b->cursor < b->size) {
v = stbtt__buf_get8(b);
if ((v & 0xF) == 0xF || (v >> 4) == 0xF)
break;
}
} else {
stbtt__cff_int(b);
}
}
static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key)
{
stbtt__buf_seek(b, 0);
while (b->cursor < b->size) {
int start = b->cursor, end, op;
while (stbtt__buf_peek8(b) >= 28)
stbtt__cff_skip_operand(b);
end = b->cursor;
op = stbtt__buf_get8(b);
if (op == 12) op = stbtt__buf_get8(b) | 0x100;
if (op == key) return stbtt__buf_range(b, start, end-start);
}
return stbtt__buf_range(b, 0, 0);
}
static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out)
{
int i;
stbtt__buf operands = stbtt__dict_get(b, key);
for (i = 0; i < outcount && operands.cursor < operands.size; i++)
out[i] = stbtt__cff_int(&operands);
}
static int stbtt__cff_index_count(stbtt__buf *b)
{
stbtt__buf_seek(b, 0);
return stbtt__buf_get16(b);
}
static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i)
{
int count, offsize, start, end;
stbtt__buf_seek(&b, 0);
count = stbtt__buf_get16(&b);
offsize = stbtt__buf_get8(&b);
STBTT_assert(i >= 0 && i < count);
STBTT_assert(offsize >= 1 && offsize <= 4);
stbtt__buf_skip(&b, i*offsize);
start = stbtt__buf_get(&b, offsize);
end = stbtt__buf_get(&b, offsize);
return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start);
}
//////////////////////////////////////////////////////////////////////////
//
// 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)
static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; }
static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; }
static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3))
#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3])
static int stbtt__isfont(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
if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts
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;
}
static int stbtt_GetFontOffsetForIndex_internal(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*4);
}
}
return -1;
}
static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection)
{
// if it's just a font, there's only one valid font
if (stbtt__isfont(font_collection))
return 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) {
return ttLONG(font_collection+8);
}
}
return 0;
}
static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict)
{
stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 };
stbtt__buf pdict;
stbtt__dict_get_ints(&fontdict, 18, 2, private_loc);
if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0);
pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]);
stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff);
if (!subrsoff) return stbtt__new_buf(NULL, 0);
stbtt__buf_seek(&cff, private_loc[1]+subrsoff);
return stbtt__cff_get_index(&cff);
}
// since most people won't use this, find this table the first time it's needed
static int stbtt__get_svg(stbtt_fontinfo *info)
{
stbtt_uint32 t;
if (info->svg < 0) {
t = stbtt__find_table(info->data, info->fontstart, "SVG ");
if (t) {
stbtt_uint32 offset = ttULONG(info->data + t + 2);
info->svg = t + offset;
} else {
info->svg = 0;
}
}
return info->svg;
}
static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart)
{
stbtt_uint32 cmap, t;
stbtt_int32 i,numTables;
info->data = data;
info->fontstart = fontstart;
info->cff = stbtt__new_buf(NULL, 0);
cmap = stbtt__find_table(data, fontstart, "cmap"); // required
info->loca = stbtt__find_table(data, fontstart, "loca"); // required
info->head = stbtt__find_table(data, fontstart, "head"); // required
info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required
info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required
info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required
info->kern = stbtt__find_table(data, fontstart, "kern"); // not required
info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required
if (!cmap || !info->head || !info->hhea || !info->hmtx)
return 0;
if (info->glyf) {
// required for truetype
if (!info->loca) return 0;
} else {
// initialization for CFF / Type2 fonts (OTF)
stbtt__buf b, topdict, topdictidx;
stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0;
stbtt_uint32 cff;
cff = stbtt__find_table(data, fontstart, "CFF ");
if (!cff) return 0;
info->fontdicts = stbtt__new_buf(NULL, 0);
info->fdselect = stbtt__new_buf(NULL, 0);
// @TODO this should use size from table (not 512MB)
info->cff = stbtt__new_buf(data+cff, 512*1024*1024);
b = info->cff;
// read the header
stbtt__buf_skip(&b, 2);
stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize
// @TODO the name INDEX could list multiple fonts,
// but we just use the first one.
stbtt__cff_get_index(&b); // name INDEX
topdictidx = stbtt__cff_get_index(&b);
topdict = stbtt__cff_index_get(topdictidx, 0);
stbtt__cff_get_index(&b); // string INDEX
info->gsubrs = stbtt__cff_get_index(&b);
stbtt__dict_get_ints(&topdict, 17, 1, &charstrings);
stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype);
stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff);
stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff);
info->subrs = stbtt__get_subrs(b, topdict);
// we only support Type 2 charstrings
if (cstype != 2) return 0;
if (charstrings == 0) return 0;
if (fdarrayoff) {
// looks like a CID font
if (!fdselectoff) return 0;
stbtt__buf_seek(&b, fdarrayoff);
info->fontdicts = stbtt__cff_get_index(&b);
info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff);
}
stbtt__buf_seek(&b, charstrings);
info->charstrings = stbtt__cff_get_index(&b);
}
t = stbtt__find_table(data, fontstart, "maxp");
if (t)
info->numGlyphs = ttUSHORT(data+t+4);
else
info->numGlyphs = 0xffff;
info->svg = -1;
// 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;
}
STBTT_DEF 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;
// do a binary search of the segments
stbtt_uint32 endCount = index_map + 14;
stbtt_uint32 search = endCount;
if (unicode_codepoint > 0xffff)
return 0;
// they lie from endCount .. endCount + segCount
// but searchRange is the nearest power of two, so...
if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2))
search += rangeShift*2;
// now decrement to bias correctly to find smallest
search -= 2;
while (entrySelector) {
stbtt_uint16 end;
searchRange >>= 1;
end = ttUSHORT(data + search + searchRange*2);
if (unicode_codepoint > end)
search += searchRange*2;
--entrySelector;
}
search += 2;
{
stbtt_uint16 offset, start, last;
stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1);
start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item);
last = ttUSHORT(data + endCount + 2*item);
if (unicode_codepoint < start || unicode_codepoint > last)
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;
}
STBTT_DEF 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;
STBTT_assert(!info->cff.size);
if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range
if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format
if (info->indexToLocFormat == 0) {
g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2;
g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2;
} else {
g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4);
g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4);
}
return g1==g2 ? -1 : g1; // if length is 0, return -1
}
static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
{
if (info->cff.size) {
stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1);
} else {
int g = stbtt__GetGlyfOffset(info, glyph_index);
if (g < 0) return 0;
if (x0) *x0 = ttSHORT(info->data + g + 2);
if (y0) *y0 = ttSHORT(info->data + g + 4);
if (x1) *x1 = ttSHORT(info->data + g + 6);
if (y1) *y1 = ttSHORT(info->data + g + 8);
}
return 1;
}
STBTT_DEF 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);
}
STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index)
{
stbtt_int16 numberOfContours;
int g;
if (info->cff.size)
return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0;
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;
}
static int stbtt__GetGlyphShapeTT(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 < 0) {
// 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 && vertices) 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 {
// numberOfCounters == 0, do nothing
}
*pvertices = vertices;
return num_vertices;
}
typedef struct
{
int bounds;
int started;
float first_x, first_y;
float x, y;
stbtt_int32 min_x, max_x, min_y, max_y;
stbtt_vertex *pvertices;
int num_vertices;
} stbtt__csctx;
#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0}
static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y)
{
if (x > c->max_x || !c->started) c->max_x = x;
if (y > c->max_y || !c->started) c->max_y = y;
if (x < c->min_x || !c->started) c->min_x = x;
if (y < c->min_y || !c->started) c->min_y = y;
c->started = 1;
}
static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1)
{
if (c->bounds) {
stbtt__track_vertex(c, x, y);
if (type == STBTT_vcubic) {
stbtt__track_vertex(c, cx, cy);
stbtt__track_vertex(c, cx1, cy1);
}
} else {
stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy);
c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1;
c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1;
}
c->num_vertices++;
}
static void stbtt__csctx_close_shape(stbtt__csctx *ctx)
{
if (ctx->first_x != ctx->x || ctx->first_y != ctx->y)
stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0);
}
static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy)
{
stbtt__csctx_close_shape(ctx);
ctx->first_x = ctx->x = ctx->x + dx;
ctx->first_y = ctx->y = ctx->y + dy;
stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);
}
static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy)
{
ctx->x += dx;
ctx->y += dy;
stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);
}
static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)
{
float cx1 = ctx->x + dx1;
float cy1 = ctx->y + dy1;
float cx2 = cx1 + dx2;
float cy2 = cy1 + dy2;
ctx->x = cx2 + dx3;
ctx->y = cy2 + dy3;
stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2);
}
static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n)
{
int count = stbtt__cff_index_count(&idx);
int bias = 107;
if (count >= 33900)
bias = 32768;
else if (count >= 1240)
bias = 1131;
n += bias;
if (n < 0 || n >= count)
return stbtt__new_buf(NULL, 0);
return stbtt__cff_index_get(idx, n);
}
static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index)
{
stbtt__buf fdselect = info->fdselect;
int nranges, start, end, v, fmt, fdselector = -1, i;
stbtt__buf_seek(&fdselect, 0);
fmt = stbtt__buf_get8(&fdselect);
if (fmt == 0) {
// untested
stbtt__buf_skip(&fdselect, glyph_index);
fdselector = stbtt__buf_get8(&fdselect);
} else if (fmt == 3) {
nranges = stbtt__buf_get16(&fdselect);
start = stbtt__buf_get16(&fdselect);
for (i = 0; i < nranges; i++) {
v = stbtt__buf_get8(&fdselect);
end = stbtt__buf_get16(&fdselect);
if (glyph_index >= start && glyph_index < end) {
fdselector = v;
break;
}
start = end;
}
}
if (fdselector == -1) stbtt__new_buf(NULL, 0);
return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector));
}
static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c)
{
int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0;
int has_subrs = 0, clear_stack;
float s[48];
stbtt__buf subr_stack[10], subrs = info->subrs, b;
float f;
#define STBTT__CSERR(s) (0)
// this currently ignores the initial width value, which isn't needed if we have hmtx
b = stbtt__cff_index_get(info->charstrings, glyph_index);
while (b.cursor < b.size) {
i = 0;
clear_stack = 1;
b0 = stbtt__buf_get8(&b);
switch (b0) {
// @TODO implement hinting
case 0x13: // hintmask
case 0x14: // cntrmask
if (in_header)
maskbits += (sp / 2); // implicit "vstem"
in_header = 0;
stbtt__buf_skip(&b, (maskbits + 7) / 8);
break;
case 0x01: // hstem
case 0x03: // vstem
case 0x12: // hstemhm
case 0x17: // vstemhm
maskbits += (sp / 2);
break;
case 0x15: // rmoveto
in_header = 0;
if (sp < 2) return STBTT__CSERR("rmoveto stack");
stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]);
break;
case 0x04: // vmoveto
in_header = 0;
if (sp < 1) return STBTT__CSERR("vmoveto stack");
stbtt__csctx_rmove_to(c, 0, s[sp-1]);
break;
case 0x16: // hmoveto
in_header = 0;
if (sp < 1) return STBTT__CSERR("hmoveto stack");
stbtt__csctx_rmove_to(c, s[sp-1], 0);
break;
case 0x05: // rlineto
if (sp < 2) return STBTT__CSERR("rlineto stack");
for (; i + 1 < sp; i += 2)
stbtt__csctx_rline_to(c, s[i], s[i+1]);
break;
// hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical
// starting from a different place.
case 0x07: // vlineto
if (sp < 1) return STBTT__CSERR("vlineto stack");
goto vlineto;
case 0x06: // hlineto
if (sp < 1) return STBTT__CSERR("hlineto stack");
for (;;) {
if (i >= sp) break;
stbtt__csctx_rline_to(c, s[i], 0);
i++;
vlineto:
if (i >= sp) break;
stbtt__csctx_rline_to(c, 0, s[i]);
i++;
}
break;
case 0x1F: // hvcurveto
if (sp < 4) return STBTT__CSERR("hvcurveto stack");
goto hvcurveto;
case 0x1E: // vhcurveto
if (sp < 4) return STBTT__CSERR("vhcurveto stack");
for (;;) {
if (i + 3 >= sp) break;
stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f);
i += 4;
hvcurveto:
if (i + 3 >= sp) break;
stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]);
i += 4;
}
break;
case 0x08: // rrcurveto
if (sp < 6) return STBTT__CSERR("rcurveline stack");
for (; i + 5 < sp; i += 6)
stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
break;
case 0x18: // rcurveline
if (sp < 8) return STBTT__CSERR("rcurveline stack");
for (; i + 5 < sp - 2; i += 6)
stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack");
stbtt__csctx_rline_to(c, s[i], s[i+1]);
break;
case 0x19: // rlinecurve
if (sp < 8) return STBTT__CSERR("rlinecurve stack");
for (; i + 1 < sp - 6; i += 2)
stbtt__csctx_rline_to(c, s[i], s[i+1]);
if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack");
stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
break;
case 0x1A: // vvcurveto
case 0x1B: // hhcurveto
if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack");
f = 0.0;
if (sp & 1) { f = s[i]; i++; }
for (; i + 3 < sp; i += 4) {
if (b0 == 0x1B)
stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0);
else
stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]);
f = 0.0;
}
break;
case 0x0A: // callsubr
if (!has_subrs) {
if (info->fdselect.size)
subrs = stbtt__cid_get_glyph_subrs(info, glyph_index);
has_subrs = 1;
}
// FALLTHROUGH
case 0x1D: // callgsubr
if (sp < 1) return STBTT__CSERR("call(g|)subr stack");
v = (int) s[--sp];
if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit");
subr_stack[subr_stack_height++] = b;
b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v);
if (b.size == 0) return STBTT__CSERR("subr not found");
b.cursor = 0;
clear_stack = 0;
break;
case 0x0B: // return
if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr");
b = subr_stack[--subr_stack_height];
clear_stack = 0;
break;
case 0x0E: // endchar
stbtt__csctx_close_shape(c);
return 1;
case 0x0C: { // two-byte escape
float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6;
float dx, dy;
int b1 = stbtt__buf_get8(&b);
switch (b1) {
// @TODO These "flex" implementations ignore the flex-depth and resolution,
// and always draw beziers.
case 0x22: // hflex
if (sp < 7) return STBTT__CSERR("hflex stack");
dx1 = s[0];
dx2 = s[1];
dy2 = s[2];
dx3 = s[3];
dx4 = s[4];
dx5 = s[5];
dx6 = s[6];
stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0);
stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0);
break;
case 0x23: // flex
if (sp < 13) return STBTT__CSERR("flex stack");
dx1 = s[0];
dy1 = s[1];
dx2 = s[2];
dy2 = s[3];
dx3 = s[4];
dy3 = s[5];
dx4 = s[6];
dy4 = s[7];
dx5 = s[8];
dy5 = s[9];
dx6 = s[10];
dy6 = s[11];
//fd is s[12]
stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);
stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);
break;
case 0x24: // hflex1
if (sp < 9) return STBTT__CSERR("hflex1 stack");
dx1 = s[0];
dy1 = s[1];
dx2 = s[2];
dy2 = s[3];
dx3 = s[4];
dx4 = s[5];
dx5 = s[6];
dy5 = s[7];
dx6 = s[8];
stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0);
stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5));
break;
case 0x25: // flex1
if (sp < 11) return STBTT__CSERR("flex1 stack");
dx1 = s[0];
dy1 = s[1];
dx2 = s[2];
dy2 = s[3];
dx3 = s[4];
dy3 = s[5];
dx4 = s[6];
dy4 = s[7];
dx5 = s[8];
dy5 = s[9];
dx6 = dy6 = s[10];
dx = dx1+dx2+dx3+dx4+dx5;
dy = dy1+dy2+dy3+dy4+dy5;
if (STBTT_fabs(dx) > STBTT_fabs(dy))
dy6 = -dy;
else
dx6 = -dx;
stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);
stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);
break;
default:
return STBTT__CSERR("unimplemented");
}
} break;
default:
if (b0 != 255 && b0 != 28 && b0 < 32)
return STBTT__CSERR("reserved operator");
// push immediate
if (b0 == 255) {
f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000;
} else {
stbtt__buf_skip(&b, -1);
f = (float)(stbtt_int16)stbtt__cff_int(&b);
}
if (sp >= 48) return STBTT__CSERR("push stack overflow");
s[sp++] = f;
clear_stack = 0;
break;
}
if (clear_stack) sp = 0;
}
return STBTT__CSERR("no endchar");
#undef STBTT__CSERR
}
static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
{
// runs the charstring twice, once to count and once to output (to avoid realloc)
stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1);
stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0);
if (stbtt__run_charstring(info, glyph_index, &count_ctx)) {
*pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata);
output_ctx.pvertices = *pvertices;
if (stbtt__run_charstring(info, glyph_index, &output_ctx)) {
STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices);
return output_ctx.num_vertices;
}
}
*pvertices = NULL;
return 0;
}
static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
{
stbtt__csctx c = STBTT__CSCTX_INIT(1);
int r = stbtt__run_charstring(info, glyph_index, &c);
if (x0) *x0 = r ? c.min_x : 0;
if (y0) *y0 = r ? c.min_y : 0;
if (x1) *x1 = r ? c.max_x : 0;
if (y1) *y1 = r ? c.max_y : 0;
return r ? c.num_vertices : 0;
}
STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
{
if (!info->cff.size)
return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices);
else
return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices);
}
STBTT_DEF 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));
}
}
STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info)
{
stbtt_uint8 *data = info->data + info->kern;
// 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;
return ttUSHORT(data+10);
}
STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length)
{
stbtt_uint8 *data = info->data + info->kern;
int k, length;
// 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;
length = ttUSHORT(data+10);
if (table_length < length)
length = table_length;
for (k = 0; k < length; k++)
{
table[k].glyph1 = ttUSHORT(data+18+(k*6));
table[k].glyph2 = ttUSHORT(data+20+(k*6));
table[k].advance = ttSHORT(data+22+(k*6));
}
return length;
}
static int stbtt__GetGlyphKernInfoAdvance(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;
}
static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph)
{
stbtt_uint16 coverageFormat = ttUSHORT(coverageTable);
switch (coverageFormat) {
case 1: {
stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2);
// Binary search.
stbtt_int32 l=0, r=glyphCount-1, m;
int straw, needle=glyph;
while (l <= r) {
stbtt_uint8 *glyphArray = coverageTable + 4;
stbtt_uint16 glyphID;
m = (l + r) >> 1;
glyphID = ttUSHORT(glyphArray + 2 * m);
straw = glyphID;
if (needle < straw)
r = m - 1;
else if (needle > straw)
l = m + 1;
else {
return m;
}
}
break;
}
case 2: {
stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2);
stbtt_uint8 *rangeArray = coverageTable + 4;
// Binary search.
stbtt_int32 l=0, r=rangeCount-1, m;
int strawStart, strawEnd, needle=glyph;
while (l <= r) {
stbtt_uint8 *rangeRecord;
m = (l + r) >> 1;
rangeRecord = rangeArray + 6 * m;
strawStart = ttUSHORT(rangeRecord);
strawEnd = ttUSHORT(rangeRecord + 2);
if (needle < strawStart)
r = m - 1;
else if (needle > strawEnd)
l = m + 1;
else {
stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4);
return startCoverageIndex + glyph - strawStart;
}
}
break;
}
default: return -1; // unsupported
}
return -1;
}
static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph)
{
stbtt_uint16 classDefFormat = ttUSHORT(classDefTable);
switch (classDefFormat)
{
case 1: {
stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2);
stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4);
stbtt_uint8 *classDef1ValueArray = classDefTable + 6;
if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount)
return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID));
break;
}
case 2: {
stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2);
stbtt_uint8 *classRangeRecords = classDefTable + 4;
// Binary search.
stbtt_int32 l=0, r=classRangeCount-1, m;
int strawStart, strawEnd, needle=glyph;
while (l <= r) {
stbtt_uint8 *classRangeRecord;
m = (l + r) >> 1;
classRangeRecord = classRangeRecords + 6 * m;
strawStart = ttUSHORT(classRangeRecord);
strawEnd = ttUSHORT(classRangeRecord + 2);
if (needle < strawStart)
r = m - 1;
else if (needle > strawEnd)
l = m + 1;
else
return (stbtt_int32)ttUSHORT(classRangeRecord + 4);
}
break;
}
default:
return -1; // Unsupported definition type, return an error.
}
// "All glyphs not assigned to a class fall into class 0". (OpenType spec)
return 0;
}
// Define to STBTT_assert(x) if you want to break on unimplemented formats.
#define STBTT_GPOS_TODO_assert(x)
static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
{
stbtt_uint16 lookupListOffset;
stbtt_uint8 *lookupList;
stbtt_uint16 lookupCount;
stbtt_uint8 *data;
stbtt_int32 i, sti;
if (!info->gpos) return 0;
data = info->data + info->gpos;
if (ttUSHORT(data+0) != 1) return 0; // Major version 1
if (ttUSHORT(data+2) != 0) return 0; // Minor version 0
lookupListOffset = ttUSHORT(data+8);
lookupList = data + lookupListOffset;
lookupCount = ttUSHORT(lookupList);
for (i=0; i<lookupCount; ++i) {
stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i);
stbtt_uint8 *lookupTable = lookupList + lookupOffset;
stbtt_uint16 lookupType = ttUSHORT(lookupTable);
stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4);
stbtt_uint8 *subTableOffsets = lookupTable + 6;
if (lookupType != 2) // Pair Adjustment Positioning Subtable
continue;
for (sti=0; sti<subTableCount; sti++) {
stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti);
stbtt_uint8 *table = lookupTable + subtableOffset;
stbtt_uint16 posFormat = ttUSHORT(table);
stbtt_uint16 coverageOffset = ttUSHORT(table + 2);
stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1);
if (coverageIndex == -1) continue;
switch (posFormat) {
case 1: {
stbtt_int32 l, r, m;
int straw, needle;
stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);
stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);
if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats?
stbtt_int32 valueRecordPairSizeInBytes = 2;
stbtt_uint16 pairSetCount = ttUSHORT(table + 8);
stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex);
stbtt_uint8 *pairValueTable = table + pairPosOffset;
stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable);
stbtt_uint8 *pairValueArray = pairValueTable + 2;
if (coverageIndex >= pairSetCount) return 0;
needle=glyph2;
r=pairValueCount-1;
l=0;
// Binary search.
while (l <= r) {
stbtt_uint16 secondGlyph;
stbtt_uint8 *pairValue;
m = (l + r) >> 1;
pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m;
secondGlyph = ttUSHORT(pairValue);
straw = secondGlyph;
if (needle < straw)
r = m - 1;
else if (needle > straw)
l = m + 1;
else {
stbtt_int16 xAdvance = ttSHORT(pairValue + 2);
return xAdvance;
}
}
} else
return 0;
break;
}
case 2: {
stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);
stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);
if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats?
stbtt_uint16 classDef1Offset = ttUSHORT(table + 8);
stbtt_uint16 classDef2Offset = ttUSHORT(table + 10);
int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1);
int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2);
stbtt_uint16 class1Count = ttUSHORT(table + 12);
stbtt_uint16 class2Count = ttUSHORT(table + 14);
stbtt_uint8 *class1Records, *class2Records;
stbtt_int16 xAdvance;
if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed
if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed
class1Records = table + 16;
class2Records = class1Records + 2 * (glyph1class * class2Count);
xAdvance = ttSHORT(class2Records + 2 * glyph2class);
return xAdvance;
} else
return 0;
break;
}
default:
return 0; // Unsupported position format
}
}
}
return 0;
}
STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2)
{
int xAdvance = 0;
if (info->gpos)
xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2);
else if (info->kern)
xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2);
return xAdvance;
}
STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2)
{
if (!info->kern && !info->gpos) // 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));
}
STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing)
{
stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing);
}
STBTT_DEF 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);
}
STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap)
{
int tab = stbtt__find_table(info->data, info->fontstart, "OS/2");
if (!tab)
return 0;
if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68);
if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70);
if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72);
return 1;
}
STBTT_DEF 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);
}
STBTT_DEF 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;
}
STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels)
{
int unitsPerEm = ttUSHORT(info->data + info->head + 18);
return pixels / unitsPerEm;
}
STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v)
{
STBTT_free(v, info->userdata);
}
STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl)
{
int i;
stbtt_uint8 *data = info->data;
stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info);
int numEntries = ttUSHORT(svg_doc_list);
stbtt_uint8 *svg_docs = svg_doc_list + 2;
for(i=0; i<numEntries; i++) {
stbtt_uint8 *svg_doc = svg_docs + (12 * i);
if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2)))
return svg_doc;
}
return 0;
}
STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg)
{
stbtt_uint8 *data = info->data;
stbtt_uint8 *svg_doc;
if (info->svg == 0)
return 0;
svg_doc = stbtt_FindSVGDoc(info, gl);
if (svg_doc != NULL) {
*svg = (char *) data + info->svg + ttULONG(svg_doc + 4);
return ttULONG(svg_doc + 8);
} else {
return 0;
}
}
STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg)
{
return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg);
}
//////////////////////////////////////////////////////////////////////////////
//
// antialiasing software rasterizer
//
STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)
{
int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning
if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) {
// e.g. space character
if (ix0) *ix0 = 0;
if (iy0) *iy0 = 0;
if (ix1) *ix1 = 0;
if (iy1) *iy1 = 0;
} else {
// move to integral bboxes (treating pixels as little squares, what pixels get touched)?
if (ix0) *ix0 = 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);
}
}
STBTT_DEF 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);
}
STBTT_DEF 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);
}
STBTT_DEF 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);
}
//////////////////////////////////////////////////////////////////////////////
//
// Rasterizer
typedef struct stbtt__hheap_chunk
{
struct stbtt__hheap_chunk *next;
} stbtt__hheap_chunk;
typedef struct stbtt__hheap
{
struct stbtt__hheap_chunk *head;
void *first_free;
int num_remaining_in_head_chunk;
} stbtt__hheap;
static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata)
{
if (hh->first_free) {
void *p = hh->first_free;
hh->first_free = * (void **) p;
return p;
} else {
if (hh->num_remaining_in_head_chunk == 0) {
int count = (size < 32 ? 2000 : size < 128 ? 800 : 100);
stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata);
if (c == NULL)
return NULL;
c->next = hh->head;
hh->head = c;
hh->num_remaining_in_head_chunk = count;
}
--hh->num_remaining_in_head_chunk;
return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk;
}
}
static void stbtt__hheap_free(stbtt__hheap *hh, void *p)
{
*(void **) p = hh->first_free;
hh->first_free = p;
}
static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata)
{
stbtt__hheap_chunk *c = hh->head;
while (c) {
stbtt__hheap_chunk *n = c->next;
STBTT_free(c, userdata);
c = n;
}
}
typedef struct stbtt__edge {
float x0,y0, x1,y1;
int invert;
} stbtt__edge;
typedef struct stbtt__active_edge
{
struct stbtt__active_edge *next;
#if STBTT_RASTERIZER_VERSION==1
int x,dx;
float ey;
int direction;
#elif STBTT_RASTERIZER_VERSION==2
float fx,fdx,fdy;
float direction;
float sy;
float ey;
#else
#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
#endif
} stbtt__active_edge;
#if STBTT_RASTERIZER_VERSION == 1
#define STBTT_FIXSHIFT 10
#define STBTT_FIX (1 << STBTT_FIXSHIFT)
#define STBTT_FIXMASK (STBTT_FIX-1)
static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)
{
stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
STBTT_assert(z != NULL);
if (!z) return z;
// round dx down to avoid overshooting
if (dxdy < 0)
z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);
else
z->dx = STBTT_ifloor(STBTT_FIX * dxdy);
z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount
z->x -= off_x * STBTT_FIX;
z->ey = e->y1;
z->next = 0;
z->direction = e->invert ? 1 : -1;
return z;
}
#elif STBTT_RASTERIZER_VERSION == 2
static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)
{
stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
STBTT_assert(z != NULL);
//STBTT_assert(e->y0 <= start_point);
if (!z) return z;
z->fdx = dxdy;
z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f;
z->fx = e->x0 + dxdy * (start_point - e->y0);
z->fx -= off_x;
z->direction = e->invert ? 1.0f : -1.0f;
z->sy = e->y0;
z->ey = e->y1;
z->next = 0;
return z;
}
#else
#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
#endif
#if STBTT_RASTERIZER_VERSION == 1
// 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->direction;
} else {
int x1 = e->x; w += e->direction;
// if we went to zero, we need to draw
if (w == 0) {
int i = x0 >> STBTT_FIXSHIFT;
int j = x1 >> STBTT_FIXSHIFT;
if (i < len && j >= 0) {
if (i == j) {
// x0,x1 are the same pixel, so compute combined coverage
scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT);
} else {
if (i >= 0) // add antialiasing for x0
scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT);
else
i = -1; // clip
if (j < len) // add antialiasing for x1
scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT);
else
j = len; // clip
for (++i; i < j; ++i) // fill pixels between x0 and x1
scanline[i] = scanline[i] + (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__hheap hh = { 0, 0, 0 };
stbtt__active_edge *active = NULL;
int y,j=0;
int max_weight = (255 / vsubsample); // weight per vertical scanline
int s; // vertical subsample index
unsigned char scanline_data[512], *scanline;
if (result->w > 512)
scanline = (unsigned char *) STBTT_malloc(result->w, userdata);
else
scanline = scanline_data;
y = off_y * vsubsample;
e[n].y0 = (off_y + result->h) * (float) vsubsample + 1;
while (j < result->h) {
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->direction);
z->direction = 0;
stbtt__hheap_free(&hh, z);
} else {
z->x += z->dx; // advance to position for current scanline
step = &((*step)->next); // advance through list
}
}
// resort the list if needed
for(;;) {
int changed=0;
step = &active;
while (*step && (*step)->next) {
if ((*step)->x > (*step)->next->x) {
stbtt__active_edge *t = *step;
stbtt__active_edge *q = t->next;
t->next = q->next;
q->next = t;
*step = q;
changed = 1;
}
step = &(*step)->next;
}
if (!changed) break;
}
// insert all edges that start before the center of this scanline -- omit ones that also end on this scanline
while (e->y0 <= scan_y) {
if (e->y1 > scan_y) {
stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata);
if (z != NULL) {
// find insertion point
if (active == NULL)
active = z;
else if (z->x < active->x) {
// insert at front
z->next = active;
active = z;
} else {
// find thing to insert AFTER
stbtt__active_edge *p = active;
while (p->next && p->next->x < z->x)
p = p->next;
// at this point, p->next->x is NOT < z->x
z->next = p->next;
p->next = z;
}
}
}
++e;
}
// now process all active edges in XOR fashion
if (active)
stbtt__fill_active_edges(scanline, result->w, active, max_weight);
++y;
}
STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w);
++j;
}
stbtt__hheap_cleanup(&hh, userdata);
if (scanline != scanline_data)
STBTT_free(scanline, userdata);
}
#elif STBTT_RASTERIZER_VERSION == 2
// the edge passed in here does not cross the vertical line at x or the vertical line at x+1
// (i.e. it has already been clipped to those)
static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1)
{
if (y0 == y1) return;
STBTT_assert(y0 < y1);
STBTT_assert(e->sy <= e->ey);
if (y0 > e->ey) return;
if (y1 < e->sy) return;
if (y0 < e->sy) {
x0 += (x1-x0) * (e->sy - y0) / (y1-y0);
y0 = e->sy;
}
if (y1 > e->ey) {
x1 += (x1-x0) * (e->ey - y1) / (y1-y0);
y1 = e->ey;
}
if (x0 == x)
STBTT_assert(x1 <= x+1);
else if (x0 == x+1)
STBTT_assert(x1 >= x);
else if (x0 <= x)
STBTT_assert(x1 <= x);
else if (x0 >= x+1)
STBTT_assert(x1 >= x+1);
else
STBTT_assert(x1 >= x && x1 <= x+1);
if (x0 <= x && x1 <= x)
scanline[x] += e->direction * (y1-y0);
else if (x0 >= x+1 && x1 >= x+1)
;
else {
STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1);
scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position
}
}
static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width)
{
STBTT_assert(top_width >= 0);
STBTT_assert(bottom_width >= 0);
return (top_width + bottom_width) / 2.0f * height;
}
static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1)
{
return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0);
}
static float stbtt__sized_triangle_area(float height, float width)
{
return height * width / 2;
}
static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top)
{
float y_bottom = y_top+1;
while (e) {
// brute force every pixel
// compute intersection points with top & bottom
STBTT_assert(e->ey >= y_top);
if (e->fdx == 0) {
float x0 = e->fx;
if (x0 < len) {
if (x0 >= 0) {
stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom);
stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom);
} else {
stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom);
}
}
} else {
float x0 = e->fx;
float dx = e->fdx;
float xb = x0 + dx;
float x_top, x_bottom;
float sy0,sy1;
float dy = e->fdy;
STBTT_assert(e->sy <= y_bottom && e->ey >= y_top);
// compute endpoints of line segment clipped to this scanline (if the
// line segment starts on this scanline. x0 is the intersection of the
// line with y_top, but that may be off the line segment.
if (e->sy > y_top) {
x_top = x0 + dx * (e->sy - y_top);
sy0 = e->sy;
} else {
x_top = x0;
sy0 = y_top;
}
if (e->ey < y_bottom) {
x_bottom = x0 + dx * (e->ey - y_top);
sy1 = e->ey;
} else {
x_bottom = xb;
sy1 = y_bottom;
}
if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) {
// from here on, we don't have to range check x values
if ((int) x_top == (int) x_bottom) {
float height;
// simple case, only spans one pixel
int x = (int) x_top;
height = (sy1 - sy0) * e->direction;
STBTT_assert(x >= 0 && x < len);
scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f);
scanline_fill[x] += height; // everything right of this pixel is filled
} else {
int x,x1,x2;
float y_crossing, y_final, step, sign, area;
// covers 2+ pixels
if (x_top > x_bottom) {
// flip scanline vertically; signed area is the same
float t;
sy0 = y_bottom - (sy0 - y_top);
sy1 = y_bottom - (sy1 - y_top);
t = sy0, sy0 = sy1, sy1 = t;
t = x_bottom, x_bottom = x_top, x_top = t;
dx = -dx;
dy = -dy;
t = x0, x0 = xb, xb = t;
}
STBTT_assert(dy >= 0);
STBTT_assert(dx >= 0);
x1 = (int) x_top;
x2 = (int) x_bottom;
// compute intersection with y axis at x1+1
y_crossing = y_top + dy * (x1+1 - x0);
// compute intersection with y axis at x2
y_final = y_top + dy * (x2 - x0);
// x1 x_top x2 x_bottom
// y_top +------|-----+------------+------------+--------|---+------------+
// | | | | | |
// | | | | | |
// sy0 | Txxxxx|............|............|............|............|
// y_crossing | *xxxxx.......|............|............|............|
// | | xxxxx..|............|............|............|
// | | /- xx*xxxx........|............|............|
// | | dy < | xxxxxx..|............|............|
// y_final | | \- | xx*xxx.........|............|
// sy1 | | | | xxxxxB...|............|
// | | | | | |
// | | | | | |
// y_bottom +------------+------------+------------+------------+------------+
//
// goal is to measure the area covered by '.' in each pixel
// if x2 is right at the right edge of x1, y_crossing can blow up, github #1057
// @TODO: maybe test against sy1 rather than y_bottom?
if (y_crossing > y_bottom)
y_crossing = y_bottom;
sign = e->direction;
// area of the rectangle covered from sy0..y_crossing
area = sign * (y_crossing-sy0);
// area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing)
scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top);
// check if final y_crossing is blown up; no test case for this
if (y_final > y_bottom) {
y_final = y_bottom;
dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom
}
// in second pixel, area covered by line segment found in first pixel
// is always a rectangle 1 wide * the height of that line segment; this
// is exactly what the variable 'area' stores. it also gets a contribution
// from the line segment within it. the THIRD pixel will get the first
// pixel's rectangle contribution, the second pixel's rectangle contribution,
// and its own contribution. the 'own contribution' is the same in every pixel except
// the leftmost and rightmost, a trapezoid that slides down in each pixel.
// the second pixel's contribution to the third pixel will be the
// rectangle 1 wide times the height change in the second pixel, which is dy.
step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x,
// which multiplied by 1-pixel-width is how much pixel area changes for each step in x
// so the area advances by 'step' every time
for (x = x1+1; x < x2; ++x) {
scanline[x] += area + step/2; // area of trapezoid is 1*step/2
area += step;
}
STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down
STBTT_assert(sy1 > y_final-0.01f);
// area covered in the last pixel is the rectangle from all the pixels to the left,
// plus the trapezoid filled by the line segment in this pixel all the way to the right edge
scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f);
// the rest of the line is filled based on the total height of the line segment in this pixel
scanline_fill[x2] += sign * (sy1-sy0);
}
} else {
// if edge goes outside of box we're drawing, we require
// clipping logic. since this does not match the intended use
// of this library, we use a different, very slow brute
// force implementation
// note though that this does happen some of the time because
// x_top and x_bottom can be extrapolated at the top & bottom of
// the shape and actually lie outside the bounding box
int x;
for (x=0; x < len; ++x) {
// cases:
//
// there can be up to two intersections with the pixel. any intersection
// with left or right edges can be handled by splitting into two (or three)
// regions. intersections with top & bottom do not necessitate case-wise logic.
//
// the old way of doing this found the intersections with the left & right edges,
// then used some simple logic to produce up to three segments in sorted order
// from top-to-bottom. however, this had a problem: if an x edge was epsilon
// across the x border, then the corresponding y position might not be distinct
// from the other y segment, and it might ignored as an empty segment. to avoid
// that, we need to explicitly produce segments based on x positions.
// rename variables to clearly-defined pairs
float y0 = y_top;
float x1 = (float) (x);
float x2 = (float) (x+1);
float x3 = xb;
float y3 = y_bottom;
// x = e->x + e->dx * (y-y_top)
// (y-y_top) = (x - e->x) / e->dx
// y = (x - e->x) / e->dx + y_top
float y1 = (x - x0) / dx + y_top;
float y2 = (x+1 - x0) / dx + y_top;
if (x0 < x1 && x3 > x2) { // three segments descending down-right
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2);
stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
} else if (x3 < x1 && x0 > x2) { // three segments descending down-left
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1);
stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
} else if (x0 < x1 && x3 > x1) { // two segments across x, down-right
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
} else if (x3 < x1 && x0 > x1) { // two segments across x, down-left
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
} else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
} else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
} else { // one segment
stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3);
}
}
}
}
e = e->next;
}
}
// directly AA rasterize edges w/o supersampling
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__hheap hh = { 0, 0, 0 };
stbtt__active_edge *active = NULL;
int y,j=0, i;
float scanline_data[129], *scanline, *scanline2;
STBTT__NOTUSED(vsubsample);
if (result->w > 64)
scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata);
else
scanline = scanline_data;
scanline2 = scanline + result->w;
y = off_y;
e[n].y0 = (float) (off_y + result->h) + 1;
while (j < result->h) {
// find center of pixel for this scanline
float scan_y_top = y + 0.0f;
float scan_y_bottom = y + 1.0f;
stbtt__active_edge **step = &active;
STBTT_memset(scanline , 0, result->w*sizeof(scanline[0]));
STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0]));
// update all active edges;
// remove all active edges that terminate before the top of this scanline
while (*step) {
stbtt__active_edge * z = *step;
if (z->ey <= scan_y_top) {
*step = z->next; // delete from list
STBTT_assert(z->direction);
z->direction = 0;
stbtt__hheap_free(&hh, z);
} else {
step = &((*step)->next); // advance through list
}
}
// insert all edges that start before the bottom of this scanline
while (e->y0 <= scan_y_bottom) {
if (e->y0 != e->y1) {
stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata);
if (z != NULL) {
if (j == 0 && off_y != 0) {
if (z->ey < scan_y_top) {
// this can happen due to subpixel positioning and some kind of fp rounding error i think
z->ey = scan_y_top;
}
}
STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds
// insert at front
z->next = active;
active = z;
}
}
++e;
}
// now process all active edges
if (active)
stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top);
{
float sum = 0;
for (i=0; i < result->w; ++i) {
float k;
int m;
sum += scanline2[i];
k = scanline[i] + sum;
k = (float) STBTT_fabs(k)*255 + 0.5f;
m = (int) k;
if (m > 255) m = 255;
result->pixels[j*result->stride + i] = (unsigned char) m;
}
}
// advance all the edges
step = &active;
while (*step) {
stbtt__active_edge *z = *step;
z->fx += z->fdx; // advance to position for current scanline
step = &((*step)->next); // advance through list
}
++y;
++j;
}
stbtt__hheap_cleanup(&hh, userdata);
if (scanline != scanline_data)
STBTT_free(scanline, userdata);
}
#else
#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
#endif
#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0)
static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n)
{
int i,j;
for (i=1; i < n; ++i) {
stbtt__edge t = p[i], *a = &t;
j = i;
while (j > 0) {
stbtt__edge *b = &p[j-1];
int c = STBTT__COMPARE(a,b);
if (!c) break;
p[j] = p[j-1];
--j;
}
if (i != j)
p[j] = t;
}
}
static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n)
{
/* threshold for transitioning to insertion sort */
while (n > 12) {
stbtt__edge t;
int c01,c12,c,m,i,j;
/* compute median of three */
m = n >> 1;
c01 = STBTT__COMPARE(&p[0],&p[m]);
c12 = STBTT__COMPARE(&p[m],&p[n-1]);
/* if 0 >= mid >= end, or 0 < mid < end, then use mid */
if (c01 != c12) {
/* otherwise, we'll need to swap something else to middle */
int z;
c = STBTT__COMPARE(&p[0],&p[n-1]);
/* 0>mid && mid<n: 0>n => n; 0<n => 0 */
/* 0<mid && mid>n: 0>n => 0; 0<n => n */
z = (c == c12) ? 0 : n-1;
t = p[z];
p[z] = p[m];
p[m] = t;
}
/* now p[m] is the median-of-three */
/* swap it to the beginning so it won't move around */
t = p[0];
p[0] = p[m];
p[m] = t;
/* partition loop */
i=1;
j=n-1;
for(;;) {
/* handling of equality is crucial here */
/* for sentinels & efficiency with duplicates */
for (;;++i) {
if (!STBTT__COMPARE(&p[i], &p[0])) break;
}
for (;;--j) {
if (!STBTT__COMPARE(&p[0], &p[j])) break;
}
/* make sure we haven't crossed */
if (i >= j) break;
t = p[i];
p[i] = p[j];
p[j] = t;
++i;
--j;
}
/* recurse on smaller side, iterate on larger */
if (j < (n-i)) {
stbtt__sort_edges_quicksort(p,j);
p = p+i;
n = n-i;
} else {
stbtt__sort_edges_quicksort(p+i, n-i);
n = j;
}
}
}
static void stbtt__sort_edges(stbtt__edge *p, int n)
{
stbtt__sort_edges_quicksort(p, n);
stbtt__sort_edges_ins_sort(p, n);
}
typedef struct
{
float x,y;
} stbtt__point;
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;
#if STBTT_RASTERIZER_VERSION == 1
int vsubsample = result->h < 8 ? 15 : 5;
#elif STBTT_RASTERIZER_VERSION == 2
int vsubsample = 1;
#else
#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
#endif
// vsubsample should divide 255 evenly; otherwise we won't reach full opacity
// now we have to blow out the windings into explicit edge lists
n = 0;
for (i=0; i < windings; ++i)
n += wcount[i];
e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel
if (e == 0) return;
n = 0;
m=0;
for (i=0; i < windings; ++i) {
stbtt__point *p = pts + m;
m += wcount[i];
j = wcount[i]-1;
for (k=0; k < wcount[i]; j=k++) {
int a=k,b=j;
// skip the edge if horizontal
if (p[j].y == p[k].y)
continue;
// add edge from j to k to the list
e[n].invert = 0;
if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) {
e[n].invert = 1;
a=j,b=k;
}
e[n].x0 = p[a].x * scale_x + shift_x;
e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample;
e[n].x1 = p[b].x * scale_x + shift_x;
e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample;
++n;
}
}
// now sort the edges by their highest point (should snap to integer, and then by x)
//STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare);
stbtt__sort_edges(e, n);
// now, traverse the scanlines and find the intersections on each scanline, use xor winding rule
stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata);
STBTT_free(e, userdata);
}
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;
}
// tessellate until threshold 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;
}
static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n)
{
// @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough
float dx0 = x1-x0;
float dy0 = y1-y0;
float dx1 = x2-x1;
float dy1 = y2-y1;
float dx2 = x3-x2;
float dy2 = y3-y2;
float dx = x3-x0;
float dy = y3-y0;
float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2));
float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy);
float flatness_squared = longlen*longlen-shortlen*shortlen;
if (n > 16) // 65536 segments on one curve better be enough!
return;
if (flatness_squared > objspace_flatness_squared) {
float x01 = (x0+x1)/2;
float y01 = (y0+y1)/2;
float x12 = (x1+x2)/2;
float y12 = (y1+y2)/2;
float x23 = (x2+x3)/2;
float y23 = (y2+y3)/2;
float xa = (x01+x12)/2;
float ya = (y01+y12)/2;
float xb = (x12+x23)/2;
float yb = (y12+y23)/2;
float mx = (xa+xb)/2;
float my = (ya+yb)/2;
stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1);
stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1);
} else {
stbtt__add_point(points, *num_points,x3,y3);
*num_points = *num_points+1;
}
}
// returns number of contours
static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata)
{
stbtt__point *points=0;
int num_points=0;
float objspace_flatness_squared = objspace_flatness * objspace_flatness;
int i,n=0,start=0, pass;
// count how many "moves" there are to get the contour count
for (i=0; i < num_verts; ++i)
if (vertices[i].type == STBTT_vmove)
++n;
*num_contours = n;
if (n == 0) return 0;
*contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata);
if (*contour_lengths == 0) {
*num_contours = 0;
return 0;
}
// make two passes through the points so we don't need to realloc
for (pass=0; pass < 2; ++pass) {
float x=0,y=0;
if (pass == 1) {
points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata);
if (points == NULL) goto error;
}
num_points = 0;
n= -1;
for (i=0; i < num_verts; ++i) {
switch (vertices[i].type) {
case STBTT_vmove:
// start the next contour
if (n >= 0)
(*contour_lengths)[n] = num_points - start;
++n;
start = num_points;
x = vertices[i].x, y = vertices[i].y;
stbtt__add_point(points, num_points++, x,y);
break;
case STBTT_vline:
x = vertices[i].x, y = vertices[i].y;
stbtt__add_point(points, num_points++, x, y);
break;
case STBTT_vcurve:
stbtt__tesselate_curve(points, &num_points, x,y,
vertices[i].cx, vertices[i].cy,
vertices[i].x, vertices[i].y,
objspace_flatness_squared, 0);
x = vertices[i].x, y = vertices[i].y;
break;
case STBTT_vcubic:
stbtt__tesselate_cubic(points, &num_points, x,y,
vertices[i].cx, vertices[i].cy,
vertices[i].cx1, vertices[i].cy1,
vertices[i].x, vertices[i].y,
objspace_flatness_squared, 0);
x = vertices[i].x, y = vertices[i].y;
break;
}
}
(*contour_lengths)[n] = num_points - start;
}
return points;
error:
STBTT_free(points, userdata);
STBTT_free(*contour_lengths, userdata);
*contour_lengths = 0;
*num_contours = 0;
return NULL;
}
STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata)
{
float scale = scale_x > scale_y ? scale_y : scale_x;
int winding_count = 0;
int *winding_lengths = NULL;
stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata);
if (windings) {
stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata);
STBTT_free(winding_lengths, userdata);
STBTT_free(windings, userdata);
}
}
STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata)
{
STBTT_free(bitmap, userdata);
}
STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff)
{
int ix0,iy0,ix1,iy1;
stbtt__bitmap gbm;
stbtt_vertex *vertices;
int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
if (scale_x == 0) scale_x = scale_y;
if (scale_y == 0) {
if (scale_x == 0) {
STBTT_free(vertices, info->userdata);
return NULL;
}
scale_y = scale_x;
}
stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1);
// now we get the size
gbm.w = (ix1 - ix0);
gbm.h = (iy1 - iy0);
gbm.pixels = NULL; // in case we error
if (width ) *width = gbm.w;
if (height) *height = gbm.h;
if (xoff ) *xoff = ix0;
if (yoff ) *yoff = iy0;
if (gbm.w && gbm.h) {
gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);
if (gbm.pixels) {
gbm.stride = gbm.w;
stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata);
}
}
STBTT_free(vertices, info->userdata);
return gbm.pixels;
}
STBTT_DEF 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);
}
STBTT_DEF 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);
}
STBTT_DEF 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);
}
STBTT_DEF 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);
}
STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(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 oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint)
{
stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint));
}
STBTT_DEF 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));
}
STBTT_DEF 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);
}
STBTT_DEF 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
static int stbtt_BakeFontBitmap_internal(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;
f.userdata = NULL;
if (!stbtt_InitFont(&f, data, offset))
return -1;
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 + 1;
if (y+gh+1 > bottom_y)
bottom_y = y+gh+1;
}
return bottom_y;
}
STBTT_DEF void stbtt_GetBakedQuad(const 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;
const stbtt_bakedchar *b = chardata + char_index;
int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f);
int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f);
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;
}
//////////////////////////////////////////////////////////////////////////////
//
// rectangle packing replacement routines if you don't have stb_rect_pack.h
//
#ifndef STB_RECT_PACK_VERSION
typedef int stbrp_coord;
////////////////////////////////////////////////////////////////////////////////////
// //
// //
// COMPILER WARNING ?!?!? //
// //
// //
// if you get a compile warning due to these symbols being defined more than //
// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" //
// //
////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
int width,height;
int x,y,bottom_y;
} stbrp_context;
typedef struct
{
unsigned char x;
} stbrp_node;
struct stbrp_rect
{
stbrp_coord x,y;
int id,w,h,was_packed;
};
static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes)
{
con->width = pw;
con->height = ph;
con->x = 0;
con->y = 0;
con->bottom_y = 0;
STBTT__NOTUSED(nodes);
STBTT__NOTUSED(num_nodes);
}
static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)
{
int i;
for (i=0; i < num_rects; ++i) {
if (con->x + rects[i].w > con->width) {
con->x = 0;
con->y = con->bottom_y;
}
if (con->y + rects[i].h > con->height)
break;
rects[i].x = con->x;
rects[i].y = con->y;
rects[i].was_packed = 1;
con->x += rects[i].w;
if (con->y + rects[i].h > con->bottom_y)
con->bottom_y = con->y + rects[i].h;
}
for ( ; i < num_rects; ++i)
rects[i].was_packed = 0;
}
#endif
//////////////////////////////////////////////////////////////////////////////
//
// bitmap baking
//
// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If
// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy.
STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context)
{
stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context);
int num_nodes = pw - padding;
stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context);
if (context == NULL || nodes == NULL) {
if (context != NULL) STBTT_free(context, alloc_context);
if (nodes != NULL) STBTT_free(nodes , alloc_context);
return 0;
}
spc->user_allocator_context = alloc_context;
spc->width = pw;
spc->height = ph;
spc->pixels = pixels;
spc->pack_info = context;
spc->nodes = nodes;
spc->padding = padding;
spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw;
spc->h_oversample = 1;
spc->v_oversample = 1;
spc->skip_missing = 0;
stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);
if (pixels)
STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
return 1;
}
STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc)
{
STBTT_free(spc->nodes , spc->user_allocator_context);
STBTT_free(spc->pack_info, spc->user_allocator_context);
}
STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample)
{
STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE);
STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE);
if (h_oversample <= STBTT_MAX_OVERSAMPLE)
spc->h_oversample = h_oversample;
if (v_oversample <= STBTT_MAX_OVERSAMPLE)
spc->v_oversample = v_oversample;
}
STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip)
{
spc->skip_missing = skip;
}
#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1)
static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
{
unsigned char buffer[STBTT_MAX_OVERSAMPLE];
int safe_w = w - kernel_width;
int j;
STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze
for (j=0; j < h; ++j) {
int i;
unsigned int total;
STBTT_memset(buffer, 0, kernel_width);
total = 0;
// make kernel_width a constant in common cases so compiler can optimize out the divide
switch (kernel_width) {
case 2:
for (i=0; i <= safe_w; ++i) {
total += pixels[i] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
pixels[i] = (unsigned char) (total / 2);
}
break;
case 3:
for (i=0; i <= safe_w; ++i) {
total += pixels[i] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
pixels[i] = (unsigned char) (total / 3);
}
break;
case 4:
for (i=0; i <= safe_w; ++i) {
total += pixels[i] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
pixels[i] = (unsigned char) (total / 4);
}
break;
case 5:
for (i=0; i <= safe_w; ++i) {
total += pixels[i] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
pixels[i] = (unsigned char) (total / 5);
}
break;
default:
for (i=0; i <= safe_w; ++i) {
total += pixels[i] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
pixels[i] = (unsigned char) (total / kernel_width);
}
break;
}
for (; i < w; ++i) {
STBTT_assert(pixels[i] == 0);
total -= buffer[i & STBTT__OVER_MASK];
pixels[i] = (unsigned char) (total / kernel_width);
}
pixels += stride_in_bytes;
}
}
static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
{
unsigned char buffer[STBTT_MAX_OVERSAMPLE];
int safe_h = h - kernel_width;
int j;
STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze
for (j=0; j < w; ++j) {
int i;
unsigned int total;
STBTT_memset(buffer, 0, kernel_width);
total = 0;
// make kernel_width a constant in common cases so compiler can optimize out the divide
switch (kernel_width) {
case 2:
for (i=0; i <= safe_h; ++i) {
total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
pixels[i*stride_in_bytes] = (unsigned char) (total / 2);
}
break;
case 3:
for (i=0; i <= safe_h; ++i) {
total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
pixels[i*stride_in_bytes] = (unsigned char) (total / 3);
}
break;
case 4:
for (i=0; i <= safe_h; ++i) {
total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
pixels[i*stride_in_bytes] = (unsigned char) (total / 4);
}
break;
case 5:
for (i=0; i <= safe_h; ++i) {
total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
pixels[i*stride_in_bytes] = (unsigned char) (total / 5);
}
break;
default:
for (i=0; i <= safe_h; ++i) {
total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);
}
break;
}
for (; i < h; ++i) {
STBTT_assert(pixels[i*stride_in_bytes] == 0);
total -= buffer[i & STBTT__OVER_MASK];
pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);
}
pixels += 1;
}
}
static float stbtt__oversample_shift(int oversample)
{
if (!oversample)
return 0.0f;
// The prefilter is a box filter of width "oversample",
// which shifts phase by (oversample - 1)/2 pixels in
// oversampled space. We want to shift in the opposite
// direction to counter this.
return (float)-(oversample - 1) / (2.0f * (float)oversample);
}
// rects array must be big enough to accommodate all characters in the given ranges
STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
{
int i,j,k;
int missing_glyph_added = 0;
k=0;
for (i=0; i < num_ranges; ++i) {
float fh = ranges[i].font_size;
float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);
ranges[i].h_oversample = (unsigned char) spc->h_oversample;
ranges[i].v_oversample = (unsigned char) spc->v_oversample;
for (j=0; j < ranges[i].num_chars; ++j) {
int x0,y0,x1,y1;
int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
int glyph = stbtt_FindGlyphIndex(info, codepoint);
if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) {
rects[k].w = rects[k].h = 0;
} else {
stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
scale * spc->h_oversample,
scale * spc->v_oversample,
0,0,
&x0,&y0,&x1,&y1);
rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
if (glyph == 0)
missing_glyph_added = 1;
}
++k;
}
}
return k;
}
STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(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 prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph)
{
stbtt_MakeGlyphBitmapSubpixel(info,
output,
out_w - (prefilter_x - 1),
out_h - (prefilter_y - 1),
out_stride,
scale_x,
scale_y,
shift_x,
shift_y,
glyph);
if (prefilter_x > 1)
stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x);
if (prefilter_y > 1)
stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y);
*sub_x = stbtt__oversample_shift(prefilter_x);
*sub_y = stbtt__oversample_shift(prefilter_y);
}
// rects array must be big enough to accommodate all characters in the given ranges
STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
{
int i,j,k, missing_glyph = -1, return_value = 1;
// save current values
int old_h_over = spc->h_oversample;
int old_v_over = spc->v_oversample;
k = 0;
for (i=0; i < num_ranges; ++i) {
float fh = ranges[i].font_size;
float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);
float recip_h,recip_v,sub_x,sub_y;
spc->h_oversample = ranges[i].h_oversample;
spc->v_oversample = ranges[i].v_oversample;
recip_h = 1.0f / spc->h_oversample;
recip_v = 1.0f / spc->v_oversample;
sub_x = stbtt__oversample_shift(spc->h_oversample);
sub_y = stbtt__oversample_shift(spc->v_oversample);
for (j=0; j < ranges[i].num_chars; ++j) {
stbrp_rect *r = &rects[k];
if (r->was_packed && r->w != 0 && r->h != 0) {
stbtt_packedchar *bc = &ranges[i].chardata_for_range[j];
int advance, lsb, x0,y0,x1,y1;
int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
int glyph = stbtt_FindGlyphIndex(info, codepoint);
stbrp_coord pad = (stbrp_coord) spc->padding;
// pad on left and top
r->x += pad;
r->y += pad;
r->w -= pad;
r->h -= pad;
stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb);
stbtt_GetGlyphBitmapBox(info, glyph,
scale * spc->h_oversample,
scale * spc->v_oversample,
&x0,&y0,&x1,&y1);
stbtt_MakeGlyphBitmapSubpixel(info,
spc->pixels + r->x + r->y*spc->stride_in_bytes,
r->w - spc->h_oversample+1,
r->h - spc->v_oversample+1,
spc->stride_in_bytes,
scale * spc->h_oversample,
scale * spc->v_oversample,
0,0,
glyph);
if (spc->h_oversample > 1)
stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
r->w, r->h, spc->stride_in_bytes,
spc->h_oversample);
if (spc->v_oversample > 1)
stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
r->w, r->h, spc->stride_in_bytes,
spc->v_oversample);
bc->x0 = (stbtt_int16) r->x;
bc->y0 = (stbtt_int16) r->y;
bc->x1 = (stbtt_int16) (r->x + r->w);
bc->y1 = (stbtt_int16) (r->y + r->h);
bc->xadvance = scale * advance;
bc->xoff = (float) x0 * recip_h + sub_x;
bc->yoff = (float) y0 * recip_v + sub_y;
bc->xoff2 = (x0 + r->w) * recip_h + sub_x;
bc->yoff2 = (y0 + r->h) * recip_v + sub_y;
if (glyph == 0)
missing_glyph = j;
} else if (spc->skip_missing) {
return_value = 0;
} else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) {
ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph];
} else {
return_value = 0; // if any fail, report failure
}
++k;
}
}
// restore original values
spc->h_oversample = old_h_over;
spc->v_oversample = old_v_over;
return return_value;
}
STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects)
{
stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects);
}
STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)
{
stbtt_fontinfo info;
int i,j,n, return_value = 1;
//stbrp_context *context = (stbrp_context *) spc->pack_info;
stbrp_rect *rects;
// flag all characters as NOT packed
for (i=0; i < num_ranges; ++i)
for (j=0; j < ranges[i].num_chars; ++j)
ranges[i].chardata_for_range[j].x0 =
ranges[i].chardata_for_range[j].y0 =
ranges[i].chardata_for_range[j].x1 =
ranges[i].chardata_for_range[j].y1 = 0;
n = 0;
for (i=0; i < num_ranges; ++i)
n += ranges[i].num_chars;
rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);
if (rects == NULL)
return 0;
info.userdata = spc->user_allocator_context;
stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index));
n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);
stbtt_PackFontRangesPackRects(spc, rects, n);
return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);
STBTT_free(rects, spc->user_allocator_context);
return return_value;
}
STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,
int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range)
{
stbtt_pack_range range;
range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range;
range.array_of_unicode_codepoints = NULL;
range.num_chars = num_chars_in_range;
range.chardata_for_range = chardata_for_range;
range.font_size = font_size;
return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1);
}
STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap)
{
int i_ascent, i_descent, i_lineGap;
float scale;
stbtt_fontinfo info;
stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index));
scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size);
stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap);
*ascent = (float) i_ascent * scale;
*descent = (float) i_descent * scale;
*lineGap = (float) i_lineGap * scale;
}
STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)
{
float ipw = 1.0f / pw, iph = 1.0f / ph;
const stbtt_packedchar *b = chardata + char_index;
if (align_to_integer) {
float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f);
float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f);
q->x0 = x;
q->y0 = y;
q->x1 = x + b->xoff2 - b->xoff;
q->y1 = y + b->yoff2 - b->yoff;
} else {
q->x0 = *xpos + b->xoff;
q->y0 = *ypos + b->yoff;
q->x1 = *xpos + b->xoff2;
q->y1 = *ypos + b->yoff2;
}
q->s0 = b->x0 * ipw;
q->t0 = b->y0 * iph;
q->s1 = b->x1 * ipw;
q->t1 = b->y1 * iph;
*xpos += b->xadvance;
}
//////////////////////////////////////////////////////////////////////////////
//
// sdf computation
//
#define STBTT_min(a,b) ((a) < (b) ? (a) : (b))
#define STBTT_max(a,b) ((a) < (b) ? (b) : (a))
static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2])
{
float q0perp = q0[1]*ray[0] - q0[0]*ray[1];
float q1perp = q1[1]*ray[0] - q1[0]*ray[1];
float q2perp = q2[1]*ray[0] - q2[0]*ray[1];
float roperp = orig[1]*ray[0] - orig[0]*ray[1];
float a = q0perp - 2*q1perp + q2perp;
float b = q1perp - q0perp;
float c = q0perp - roperp;
float s0 = 0., s1 = 0.;
int num_s = 0;
if (a != 0.0) {
float discr = b*b - a*c;
if (discr > 0.0) {
float rcpna = -1 / a;
float d = (float) STBTT_sqrt(discr);
s0 = (b+d) * rcpna;
s1 = (b-d) * rcpna;
if (s0 >= 0.0 && s0 <= 1.0)
num_s = 1;
if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) {
if (num_s == 0) s0 = s1;
++num_s;
}
}
} else {
// 2*b*s + c = 0
// s = -c / (2*b)
s0 = c / (-2 * b);
if (s0 >= 0.0 && s0 <= 1.0)
num_s = 1;
}
if (num_s == 0)
return 0;
else {
float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]);
float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2;
float q0d = q0[0]*rayn_x + q0[1]*rayn_y;
float q1d = q1[0]*rayn_x + q1[1]*rayn_y;
float q2d = q2[0]*rayn_x + q2[1]*rayn_y;
float rod = orig[0]*rayn_x + orig[1]*rayn_y;
float q10d = q1d - q0d;
float q20d = q2d - q0d;
float q0rd = q0d - rod;
hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d;
hits[0][1] = a*s0+b;
if (num_s > 1) {
hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d;
hits[1][1] = a*s1+b;
return 2;
} else {
return 1;
}
}
}
static int equal(float *a, float *b)
{
return (a[0] == b[0] && a[1] == b[1]);
}
static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts)
{
int i;
float orig[2], ray[2] = { 1, 0 };
float y_frac;
int winding = 0;
// make sure y never passes through a vertex of the shape
y_frac = (float) STBTT_fmod(y, 1.0f);
if (y_frac < 0.01f)
y += 0.01f;
else if (y_frac > 0.99f)
y -= 0.01f;
orig[0] = x;
orig[1] = y;
// test a ray from (-infinity,y) to (x,y)
for (i=0; i < nverts; ++i) {
if (verts[i].type == STBTT_vline) {
int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y;
int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
if (x_inter < x)
winding += (y0 < y1) ? 1 : -1;
}
}
if (verts[i].type == STBTT_vcurve) {
int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ;
int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy;
int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ;
int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2));
int by = STBTT_max(y0,STBTT_max(y1,y2));
if (y > ay && y < by && x > ax) {
float q0[2],q1[2],q2[2];
float hits[2][2];
q0[0] = (float)x0;
q0[1] = (float)y0;
q1[0] = (float)x1;
q1[1] = (float)y1;
q2[0] = (float)x2;
q2[1] = (float)y2;
if (equal(q0,q1) || equal(q1,q2)) {
x0 = (int)verts[i-1].x;
y0 = (int)verts[i-1].y;
x1 = (int)verts[i ].x;
y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
if (x_inter < x)
winding += (y0 < y1) ? 1 : -1;
}
} else {
int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits);
if (num_hits >= 1)
if (hits[0][0] < 0)
winding += (hits[0][1] < 0 ? -1 : 1);
if (num_hits >= 2)
if (hits[1][0] < 0)
winding += (hits[1][1] < 0 ? -1 : 1);
}
}
}
}
return winding;
}
static float stbtt__cuberoot( float x )
{
if (x<0)
return -(float) STBTT_pow(-x,1.0f/3.0f);
else
return (float) STBTT_pow( x,1.0f/3.0f);
}
// x^3 + a*x^2 + b*x + c = 0
static int stbtt__solve_cubic(float a, float b, float c, float* r)
{
float s = -a / 3;
float p = b - a*a / 3;
float q = a * (2*a*a - 9*b) / 27 + c;
float p3 = p*p*p;
float d = q*q + 4*p3 / 27;
if (d >= 0) {
float z = (float) STBTT_sqrt(d);
float u = (-q + z) / 2;
float v = (-q - z) / 2;
u = stbtt__cuberoot(u);
v = stbtt__cuberoot(v);
r[0] = s + u + v;
return 1;
} else {
float u = (float) STBTT_sqrt(-p/3);
float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative
float m = (float) STBTT_cos(v);
float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f;
r[0] = s + u * 2 * m;
r[1] = s - u * (m + n);
r[2] = s - u * (m - n);
//STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe?
//STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f);
//STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f);
return 3;
}
}
STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
{
float scale_x = scale, scale_y = scale;
int ix0,iy0,ix1,iy1;
int w,h;
unsigned char *data;
if (scale == 0) return NULL;
stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1);
// if empty, return NULL
if (ix0 == ix1 || iy0 == iy1)
return NULL;
ix0 -= padding;
iy0 -= padding;
ix1 += padding;
iy1 += padding;
w = (ix1 - ix0);
h = (iy1 - iy0);
if (width ) *width = w;
if (height) *height = h;
if (xoff ) *xoff = ix0;
if (yoff ) *yoff = iy0;
// invert for y-downwards bitmaps
scale_y = -scale_y;
{
int x,y,i,j;
float *precompute;
stbtt_vertex *verts;
int num_verts = stbtt_GetGlyphShape(info, glyph, &verts);
data = (unsigned char *) STBTT_malloc(w * h, info->userdata);
precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata);
for (i=0,j=num_verts-1; i < num_verts; j=i++) {
if (verts[i].type == STBTT_vline) {
float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y;
float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));
precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist;
} else if (verts[i].type == STBTT_vcurve) {
float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y;
float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y;
float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y;
float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
float len2 = bx*bx + by*by;
if (len2 != 0.0f)
precompute[i] = 1.0f / (bx*bx + by*by);
else
precompute[i] = 0.0f;
} else
precompute[i] = 0.0f;
}
for (y=iy0; y < iy1; ++y) {
for (x=ix0; x < ix1; ++x) {
float val;
float min_dist = 999999.0f;
float sx = (float) x + 0.5f;
float sy = (float) y + 0.5f;
float x_gspace = (sx / scale_x);
float y_gspace = (sy / scale_y);
int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path
for (i=0; i < num_verts; ++i) {
float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) {
float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y;
float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);
if (dist2 < min_dist*min_dist)
min_dist = (float) STBTT_sqrt(dist2);
// coarse culling against bbox
//if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist &&
// sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist)
dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i];
STBTT_assert(i != 0);
if (dist < min_dist) {
// check position along line
// x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0)
// minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy)
float dx = x1-x0, dy = y1-y0;
float px = x0-sx, py = y0-sy;
// minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy
// derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve
float t = -(px*dx + py*dy) / (dx*dx + dy*dy);
if (t >= 0.0f && t <= 1.0f)
min_dist = dist;
}
} else if (verts[i].type == STBTT_vcurve) {
float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y;
float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y;
float box_x0 = STBTT_min(STBTT_min(x0,x1),x2);
float box_y0 = STBTT_min(STBTT_min(y0,y1),y2);
float box_x1 = STBTT_max(STBTT_max(x0,x1),x2);
float box_y1 = STBTT_max(STBTT_max(y0,y1),y2);
// coarse culling against bbox to avoid computing cubic unnecessarily
if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) {
int num=0;
float ax = x1-x0, ay = y1-y0;
float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
float mx = x0 - sx, my = y0 - sy;
float res[3] = {0.f,0.f,0.f};
float px,py,t,it,dist2;
float a_inv = precompute[i];
if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula
float a = 3*(ax*bx + ay*by);
float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by);
float c = mx*ax+my*ay;
if (a == 0.0) { // if a is 0, it's linear
if (b != 0.0) {
res[num++] = -c/b;
}
} else {
float discriminant = b*b - 4*a*c;
if (discriminant < 0)
num = 0;
else {
float root = (float) STBTT_sqrt(discriminant);
res[0] = (-b - root)/(2*a);
res[1] = (-b + root)/(2*a);
num = 2; // don't bother distinguishing 1-solution case, as code below will still work
}
}
} else {
float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point
float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv;
float d = (mx*ax+my*ay) * a_inv;
num = stbtt__solve_cubic(b, c, d, res);
}
dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);
if (dist2 < min_dist*min_dist)
min_dist = (float) STBTT_sqrt(dist2);
if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) {
t = res[0], it = 1.0f - t;
px = it*it*x0 + 2*t*it*x1 + t*t*x2;
py = it*it*y0 + 2*t*it*y1 + t*t*y2;
dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
if (dist2 < min_dist * min_dist)
min_dist = (float) STBTT_sqrt(dist2);
}
if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) {
t = res[1], it = 1.0f - t;
px = it*it*x0 + 2*t*it*x1 + t*t*x2;
py = it*it*y0 + 2*t*it*y1 + t*t*y2;
dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
if (dist2 < min_dist * min_dist)
min_dist = (float) STBTT_sqrt(dist2);
}
if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) {
t = res[2], it = 1.0f - t;
px = it*it*x0 + 2*t*it*x1 + t*t*x2;
py = it*it*y0 + 2*t*it*y1 + t*t*y2;
dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
if (dist2 < min_dist * min_dist)
min_dist = (float) STBTT_sqrt(dist2);
}
}
}
}
if (winding == 0)
min_dist = -min_dist; // if outside the shape, value is negative
val = onedge_value + pixel_dist_scale * min_dist;
if (val < 0)
val = 0;
else if (val > 255)
val = 255;
data[(y-iy0)*w+(x-ix0)] = (unsigned char) val;
}
}
STBTT_free(precompute, info->userdata);
STBTT_free(verts, info->userdata);
}
return data;
}
STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
{
return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff);
}
STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata)
{
STBTT_free(bitmap, userdata);
}
//////////////////////////////////////////////////////////////////////////////
//
// font name matching -- recommended not to use this
//
// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string
static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)
{
stbtt_int32 i=0;
// convert utf16 to utf8 and compare the results while converting
while (len2) {
stbtt_uint16 ch = s2[0]*256 + s2[1];
if (ch < 0x80) {
if (i >= len1) return -1;
if (s1[i++] != ch) return -1;
} else if (ch < 0x800) {
if (i+1 >= len1) return -1;
if (s1[i++] != 0xc0 + (ch >> 6)) return -1;
if (s1[i++] != 0x80 + (ch & 0x3f)) return -1;
} else if (ch >= 0xd800 && ch < 0xdc00) {
stbtt_uint32 c;
stbtt_uint16 ch2 = s2[2]*256 + s2[3];
if (i+3 >= len1) return -1;
c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000;
if (s1[i++] != 0xf0 + (c >> 18)) return -1;
if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1;
if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1;
if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1;
s2 += 2; // plus another 2 below
len2 -= 2;
} else if (ch >= 0xdc00 && ch < 0xe000) {
return -1;
} else {
if (i+2 >= len1) return -1;
if (s1[i++] != 0xe0 + (ch >> 12)) return -1;
if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1;
if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1;
}
s2 += 2;
len2 -= 2;
}
return i;
}
static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)
{
return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2);
}
// returns results in whatever encoding you request... but note that 2-byte encodings
// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare
STBTT_DEF 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_internal((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;
}
static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, 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;
}
}
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,
float pixel_height, unsigned char *pixels, int pw, int ph,
int first_char, int num_chars, stbtt_bakedchar *chardata)
{
return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata);
}
STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index)
{
return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);
}
STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data)
{
return stbtt_GetNumberOfFonts_internal((unsigned char *) data);
}
STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset)
{
return stbtt_InitFont_internal(info, (unsigned char *) data, offset);
}
STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags)
{
return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags);
}
STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2)
{
return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2);
}
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#endif // STB_TRUETYPE_IMPLEMENTATION
// FULL VERSION HISTORY
//
// 1.25 (2021-07-11) many fixes
// 1.24 (2020-02-05) fix warning
// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)
// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined
// 1.21 (2019-02-25) fix warning
// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()
// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod
// 1.18 (2018-01-29) add missing function
// 1.17 (2017-07-23) make more arguments const; doc fix
// 1.16 (2017-07-12) SDF support
// 1.15 (2017-03-03) make more arguments const
// 1.14 (2017-01-16) num-fonts-in-TTC function
// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts
// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
// 1.11 (2016-04-02) fix unused-variable warning
// 1.10 (2016-04-02) allow user-defined fabs() replacement
// fix memory leak if fontsize=0.0
// fix warning from duplicate typedef
// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges
// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
// allow PackFontRanges to pack and render in separate phases;
// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
// fixed an assert() bug in the new rasterizer
// replace assert() with STBTT_assert() in new rasterizer
// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine)
// also more precise AA rasterizer, except if shapes overlap
// remove need for STBTT_sort
// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC
// 1.04 (2015-04-15) typo in example
// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes
// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++
// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match
// non-oversampled; STBTT_POINT_SIZE for packed case only
// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling
// 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
//
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
// Ogg Vorbis audio decoder - v1.22 - public domain
// http://nothings.org/stb_vorbis/
//
// Original version written by Sean Barrett in 2007.
//
// Originally sponsored by RAD Game Tools. Seeking implementation
// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker,
// Elias Software, Aras Pranckevicius, and Sean Barrett.
//
// LICENSE
//
// See end of file for license information.
//
// Limitations:
//
// - floor 0 not supported (used in old ogg vorbis files pre-2004)
// - lossless sample-truncation at beginning ignored
// - cannot concatenate multiple vorbis streams
// - sample positions are 32-bit, limiting seekable 192Khz
// files to around 6 hours (Ogg supports 64-bit)
//
// Feature contributors:
// Dougall Johnson (sample-exact seeking)
//
// Bugfix/warning contributors:
// Terje Mathisen Niklas Frykholm Andy Hill
// Casey Muratori John Bolton Gargaj
// Laurent Gomila Marc LeBlanc Ronny Chevalier
// Bernhard Wodo Evan Balster github:alxprd
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
// Phillip Bennefall Rohit Thiago Goulart
// github:manxorist Saga Musix github:infatum
// Timur Gagiev Maxwell Koo Peter Waller
// github:audinowho Dougall Johnson David Reid
// github:Clownacy Pedro J. Estebanez Remi Verschelde
// AnthoFoxo github:morlat Gabriel Ravier
//
// Partial history:
// 1.22 - 2021-07-11 - various small fixes
// 1.21 - 2021-07-02 - fix bug for files with no comments
// 1.20 - 2020-07-11 - several small fixes
// 1.19 - 2020-02-05 - warnings
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure)
// 1.16 - 2019-03-04 - fix warnings
// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found
// 1.14 - 2018-02-11 - delete bogus dealloca usage
// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully)
// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files
// 1.11 - 2017-07-23 - fix MinGW compilation
// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory
// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version
// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame
// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const
// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson)
// some crash fixes when out of memory or with corrupt files
// fix some inappropriately signed shifts
// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant
// 1.04 - 2014-08-27 - fix missing const-correct case in API
// 1.03 - 2014-08-07 - warning fixes
// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows
// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct)
// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel;
// (API change) report sample rate for decode-full-file funcs
//
// See end of file for full version history.
//////////////////////////////////////////////////////////////////////////////
//
// HEADER BEGINS HERE
//
#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H
#define STB_VORBIS_INCLUDE_STB_VORBIS_H
#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO)
#define STB_VORBIS_NO_STDIO 1
#endif
#ifndef STB_VORBIS_NO_STDIO
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/////////// THREAD SAFETY
// Individual stb_vorbis* handles are not thread-safe; you cannot decode from
// them from multiple threads at the same time. However, you can have multiple
// stb_vorbis* handles and decode from them independently in multiple thrads.
/////////// MEMORY ALLOCATION
// normally stb_vorbis uses malloc() to allocate memory at startup,
// and alloca() to allocate temporary memory during a frame on the
// stack. (Memory consumption will depend on the amount of setup
// data in the file and how you set the compile flags for speed
// vs. size. In my test files the maximal-size usage is ~150KB.)
//
// You can modify the wrapper functions in the source (setup_malloc,
// setup_temp_malloc, temp_malloc) to change this behavior, or you
// can use a simpler allocation model: you pass in a buffer from
// which stb_vorbis will allocate _all_ its memory (including the
// temp memory). "open" may fail with a VORBIS_outofmem if you
// do not pass in enough data; there is no way to determine how
// much you do need except to succeed (at which point you can
// query get_info to find the exact amount required. yes I know
// this is lame).
//
// If you pass in a non-NULL buffer of the type below, allocation
// will occur from it as described above. Otherwise just pass NULL
// to use malloc()/alloca()
typedef struct {
char *alloc_buffer;
int alloc_buffer_length_in_bytes;
} stb_vorbis_alloc;
/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES
typedef struct stb_vorbis stb_vorbis;
typedef struct {
unsigned int sample_rate;
int channels;
unsigned int setup_memory_required;
unsigned int setup_temp_memory_required;
unsigned int temp_memory_required;
int max_frame_size;
} stb_vorbis_info;
typedef struct {
char *vendor;
int comment_list_length;
char **comment_list;
} stb_vorbis_comment;
// get general information about the file
extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f);
// get ogg comments
extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f);
// get the last error detected (clears it, too)
extern int stb_vorbis_get_error(stb_vorbis *f);
// close an ogg vorbis file and free all memory in use
extern void stb_vorbis_close(stb_vorbis *f);
// this function returns the offset (in samples) from the beginning of the
// file that will be returned by the next decode, if it is known, or -1
// otherwise. after a flush_pushdata() call, this may take a while before
// it becomes valid again.
// NOT WORKING YET after a seek with PULLDATA API
extern int stb_vorbis_get_sample_offset(stb_vorbis *f);
// returns the current seek point within the file, or offset from the beginning
// of the memory buffer. In pushdata mode it returns 0.
extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f);
/////////// PUSHDATA API
#ifndef STB_VORBIS_NO_PUSHDATA_API
// this API allows you to get blocks of data from any source and hand
// them to stb_vorbis. you have to buffer them; stb_vorbis will tell
// you how much it used, and you have to give it the rest next time;
// and stb_vorbis may not have enough data to work with and you will
// need to give it the same data again PLUS more. Note that the Vorbis
// specification does not bound the size of an individual frame.
extern stb_vorbis *stb_vorbis_open_pushdata(
const unsigned char *datablock, int datablock_length_in_bytes,
int *datablock_memory_consumed_in_bytes,
int *error,
const stb_vorbis_alloc *alloc_buffer);
// create a vorbis decoder by passing in the initial data block containing
// the ogg&vorbis headers (you don't need to do parse them, just provide
// the first N bytes of the file--you're told if it's not enough, see below)
// on success, returns an stb_vorbis *, does not set error, returns the amount of
// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes;
// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed
// if returns NULL and *error is VORBIS_need_more_data, then the input block was
// incomplete and you need to pass in a larger block from the start of the file
extern int stb_vorbis_decode_frame_pushdata(
stb_vorbis *f,
const unsigned char *datablock, int datablock_length_in_bytes,
int *channels, // place to write number of float * buffers
float ***output, // place to write float ** array of float * buffers
int *samples // place to write number of output samples
);
// decode a frame of audio sample data if possible from the passed-in data block
//
// return value: number of bytes we used from datablock
//
// possible cases:
// 0 bytes used, 0 samples output (need more data)
// N bytes used, 0 samples output (resynching the stream, keep going)
// N bytes used, M samples output (one frame of data)
// note that after opening a file, you will ALWAYS get one N-bytes,0-sample
// frame, because Vorbis always "discards" the first frame.
//
// Note that on resynch, stb_vorbis will rarely consume all of the buffer,
// instead only datablock_length_in_bytes-3 or less. This is because it wants
// to avoid missing parts of a page header if they cross a datablock boundary,
// without writing state-machiney code to record a partial detection.
//
// The number of channels returned are stored in *channels (which can be
// NULL--it is always the same as the number of channels reported by
// get_info). *output will contain an array of float* buffers, one per
// channel. In other words, (*output)[0][0] contains the first sample from
// the first channel, and (*output)[1][0] contains the first sample from
// the second channel.
//
// *output points into stb_vorbis's internal output buffer storage; these
// buffers are owned by stb_vorbis and application code should not free
// them or modify their contents. They are transient and will be overwritten
// once you ask for more data to get decoded, so be sure to grab any data
// you need before then.
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
// inform stb_vorbis that your next datablock will not be contiguous with
// previous ones (e.g. you've seeked in the data); future attempts to decode
// frames will cause stb_vorbis to resynchronize (as noted above), and
// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it
// will begin decoding the _next_ frame.
//
// if you want to seek using pushdata, you need to seek in your file, then
// call stb_vorbis_flush_pushdata(), then start calling decoding, then once
// decoding is returning you data, call stb_vorbis_get_sample_offset, and
// if you don't like the result, seek your file again and repeat.
#endif
////////// PULLING INPUT API
#ifndef STB_VORBIS_NO_PULLDATA_API
// This API assumes stb_vorbis is allowed to pull data from a source--
// either a block of memory containing the _entire_ vorbis stream, or a
// FILE * that you or it create, or possibly some other reading mechanism
// if you go modify the source to replace the FILE * case with some kind
// of callback to your code. (But if you don't support seeking, you may
// just want to go ahead and use pushdata.)
#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output);
#endif
#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output);
#endif
// decode an entire file and output the data interleaved into a malloc()ed
// buffer stored in *output. The return value is the number of samples
// decoded, or -1 if the file could not be opened or was not an ogg vorbis file.
// When you're done with it, just free() the pointer returned in *output.
extern stb_vorbis *stb_vorbis_open_memory(const unsigned char *data, int len,
int *error, const stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from an ogg vorbis stream in memory (note
// this must be the entire stream!). on failure, returns NULL and sets *error
#ifndef STB_VORBIS_NO_STDIO
extern stb_vorbis *stb_vorbis_open_filename(const char *filename,
int *error, const stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from a filename via fopen(). on failure,
// returns NULL and sets *error (possibly to VORBIS_file_open_failure).
extern stb_vorbis *stb_vorbis_open_file(FILE *f, int close_handle_on_close,
int *error, const stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
// the _current_ seek point (ftell). on failure, returns NULL and sets *error.
// note that stb_vorbis must "own" this stream; if you seek it in between
// calls to stb_vorbis, it will become confused. Moreover, if you attempt to
// perform stb_vorbis_seek_*() operations on this file, it will assume it
// owns the _entire_ rest of the file after the start point. Use the next
// function, stb_vorbis_open_file_section(), to limit it.
extern stb_vorbis *stb_vorbis_open_file_section(FILE *f, int close_handle_on_close,
int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len);
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
// the _current_ seek point (ftell); the stream will be of length 'len' bytes.
// on failure, returns NULL and sets *error. note that stb_vorbis must "own"
// this stream; if you seek it in between calls to stb_vorbis, it will become
// confused.
#endif
extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number);
extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number);
// these functions seek in the Vorbis file to (approximately) 'sample_number'.
// after calling seek_frame(), the next call to get_frame_*() will include
// the specified sample. after calling stb_vorbis_seek(), the next call to
// stb_vorbis_get_samples_* will start with the specified sample. If you
// do not need to seek to EXACTLY the target sample when using get_samples_*,
// you can also use seek_frame().
extern int stb_vorbis_seek_start(stb_vorbis *f);
// this function is equivalent to stb_vorbis_seek(f,0)
extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f);
extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f);
// these functions return the total length of the vorbis stream
extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output);
// decode the next frame and return the number of samples. the number of
// channels returned are stored in *channels (which can be NULL--it is always
// the same as the number of channels reported by get_info). *output will
// contain an array of float* buffers, one per channel. These outputs will
// be overwritten on the next call to stb_vorbis_get_frame_*.
//
// You generally should not intermix calls to stb_vorbis_get_frame_*()
// and stb_vorbis_get_samples_*(), since the latter calls the former.
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts);
extern int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples);
#endif
// decode the next frame and return the number of *samples* per channel.
// Note that for interleaved data, you pass in the number of shorts (the
// size of your array), but the return value is the number of samples per
// channel, not the total number of samples.
//
// The data is coerced to the number of channels you request according to the
// channel coercion rules (see below). You must pass in the size of your
// buffer(s) so that stb_vorbis will not overwrite the end of the buffer.
// The maximum buffer size needed can be gotten from get_info(); however,
// the Vorbis I specification implies an absolute maximum of 4096 samples
// per channel.
// Channel coercion rules:
// Let M be the number of channels requested, and N the number of channels present,
// and Cn be the nth channel; let stereo L be the sum of all L and center channels,
// and stereo R be the sum of all R and center channels (channel assignment from the
// vorbis spec).
// M N output
// 1 k sum(Ck) for all k
// 2 * stereo L, stereo R
// k l k > l, the first l channels, then 0s
// k l k <= l, the first k channels
// Note that this is not _good_ surround etc. mixing at all! It's just so
// you get something useful.
extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats);
extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples);
// gets num_samples samples, not necessarily on a frame boundary--this requires
// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES.
// Returns the number of samples stored per channel; it may be less than requested
// at the end of the file. If there are no more samples in the file, returns 0.
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts);
extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples);
#endif
// gets num_samples samples, not necessarily on a frame boundary--this requires
// buffering so you have to supply the buffers. Applies the coercion rules above
// to produce 'channels' channels. Returns the number of samples stored per channel;
// it may be less than requested at the end of the file. If there are no more
// samples in the file, returns 0.
#endif
//////// ERROR CODES
enum STBVorbisError {
VORBIS__no_error,
VORBIS_need_more_data = 1, // not a real error
VORBIS_invalid_api_mixing, // can't mix API modes
VORBIS_outofmem, // not enough memory
VORBIS_feature_not_supported, // uses floor 0
VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small
VORBIS_file_open_failure, // fopen() failed
VORBIS_seek_without_length, // can't seek in unknown-length file
VORBIS_unexpected_eof = 10, // file is truncated?
VORBIS_seek_invalid, // seek past EOF
// decoding errors (corrupt/invalid stream) -- you probably
// don't care about the exact details of these
// vorbis errors:
VORBIS_invalid_setup = 20,
VORBIS_invalid_stream,
// ogg errors:
VORBIS_missing_capture_pattern = 30,
VORBIS_invalid_stream_structure_version,
VORBIS_continued_packet_flag_invalid,
VORBIS_incorrect_stream_serial_number,
VORBIS_invalid_first_page,
VORBIS_bad_packet_type,
VORBIS_cant_find_last_page,
VORBIS_seek_failed,
VORBIS_ogg_skeleton_not_supported
};
#ifdef __cplusplus
}
#endif
#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H
//
// HEADER ENDS HERE
//
//////////////////////////////////////////////////////////////////////////////
#ifndef STB_VORBIS_HEADER_ONLY
// global configuration settings (e.g. set these in the project/makefile),
// or just set them in this file at the top (although ideally the first few
// should be visible when the header file is compiled too, although it's not
// crucial)
// STB_VORBIS_NO_PUSHDATA_API
// does not compile the code for the various stb_vorbis_*_pushdata()
// functions
// #define STB_VORBIS_NO_PUSHDATA_API
// STB_VORBIS_NO_PULLDATA_API
// does not compile the code for the non-pushdata APIs
// #define STB_VORBIS_NO_PULLDATA_API
// STB_VORBIS_NO_STDIO
// does not compile the code for the APIs that use FILE *s internally
// or externally (implied by STB_VORBIS_NO_PULLDATA_API)
// #define STB_VORBIS_NO_STDIO
// STB_VORBIS_NO_INTEGER_CONVERSION
// does not compile the code for converting audio sample data from
// float to integer (implied by STB_VORBIS_NO_PULLDATA_API)
// #define STB_VORBIS_NO_INTEGER_CONVERSION
// STB_VORBIS_NO_FAST_SCALED_FLOAT
// does not use a fast float-to-int trick to accelerate float-to-int on
// most platforms which requires endianness be defined correctly.
//#define STB_VORBIS_NO_FAST_SCALED_FLOAT
// STB_VORBIS_MAX_CHANNELS [number]
// globally define this to the maximum number of channels you need.
// The spec does not put a restriction on channels except that
// the count is stored in a byte, so 255 is the hard limit.
// Reducing this saves about 16 bytes per value, so using 16 saves
// (255-16)*16 or around 4KB. Plus anything other memory usage
// I forgot to account for. Can probably go as low as 8 (7.1 audio),
// 6 (5.1 audio), or 2 (stereo only).
#ifndef STB_VORBIS_MAX_CHANNELS
#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone?
#endif
// STB_VORBIS_PUSHDATA_CRC_COUNT [number]
// after a flush_pushdata(), stb_vorbis begins scanning for the
// next valid page, without backtracking. when it finds something
// that looks like a page, it streams through it and verifies its
// CRC32. Should that validation fail, it keeps scanning. But it's
// possible that _while_ streaming through to check the CRC32 of
// one candidate page, it sees another candidate page. This #define
// determines how many "overlapping" candidate pages it can search
// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas
// garbage pages could be as big as 64KB, but probably average ~16KB.
// So don't hose ourselves by scanning an apparent 64KB page and
// missing a ton of real ones in the interim; so minimum of 2
#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT
#define STB_VORBIS_PUSHDATA_CRC_COUNT 4
#endif
// STB_VORBIS_FAST_HUFFMAN_LENGTH [number]
// sets the log size of the huffman-acceleration table. Maximum
// supported value is 24. with larger numbers, more decodings are O(1),
// but the table size is larger so worse cache missing, so you'll have
// to probe (and try multiple ogg vorbis files) to find the sweet spot.
#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH
#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10
#endif
// STB_VORBIS_FAST_BINARY_LENGTH [number]
// sets the log size of the binary-search acceleration table. this
// is used in similar fashion to the fast-huffman size to set initial
// parameters for the binary search
// STB_VORBIS_FAST_HUFFMAN_INT
// The fast huffman tables are much more efficient if they can be
// stored as 16-bit results instead of 32-bit results. This restricts
// the codebooks to having only 65535 possible outcomes, though.
// (At least, accelerated by the huffman table.)
#ifndef STB_VORBIS_FAST_HUFFMAN_INT
#define STB_VORBIS_FAST_HUFFMAN_SHORT
#endif
// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH
// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls
// back on binary searching for the correct one. This requires storing
// extra tables with the huffman codes in sorted order. Defining this
// symbol trades off space for speed by forcing a linear search in the
// non-fast case, except for "sparse" codebooks.
// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH
// STB_VORBIS_DIVIDES_IN_RESIDUE
// stb_vorbis precomputes the result of the scalar residue decoding
// that would otherwise require a divide per chunk. you can trade off
// space for time by defining this symbol.
// #define STB_VORBIS_DIVIDES_IN_RESIDUE
// STB_VORBIS_DIVIDES_IN_CODEBOOK
// vorbis VQ codebooks can be encoded two ways: with every case explicitly
// stored, or with all elements being chosen from a small range of values,
// and all values possible in all elements. By default, stb_vorbis expands
// this latter kind out to look like the former kind for ease of decoding,
// because otherwise an integer divide-per-vector-element is required to
// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can
// trade off storage for speed.
//#define STB_VORBIS_DIVIDES_IN_CODEBOOK
#ifdef STB_VORBIS_CODEBOOK_SHORTS
#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats"
#endif
// STB_VORBIS_DIVIDE_TABLE
// this replaces small integer divides in the floor decode loop with
// table lookups. made less than 1% difference, so disabled by default.
// STB_VORBIS_NO_INLINE_DECODE
// disables the inlining of the scalar codebook fast-huffman decode.
// might save a little codespace; useful for debugging
// #define STB_VORBIS_NO_INLINE_DECODE
// STB_VORBIS_NO_DEFER_FLOOR
// Normally we only decode the floor without synthesizing the actual
// full curve. We can instead synthesize the curve immediately. This
// requires more memory and is very likely slower, so I don't think
// you'd ever want to do it except for debugging.
// #define STB_VORBIS_NO_DEFER_FLOOR
//////////////////////////////////////////////////////////////////////////////
#ifdef STB_VORBIS_NO_PULLDATA_API
#define STB_VORBIS_NO_INTEGER_CONVERSION
#define STB_VORBIS_NO_STDIO
#endif
#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO)
#define STB_VORBIS_NO_STDIO 1
#endif
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT
// only need endianness for fast-float-to-int, which we don't
// use for pushdata
#ifndef STB_VORBIS_BIG_ENDIAN
#define STB_VORBIS_ENDIAN 0
#else
#define STB_VORBIS_ENDIAN 1
#endif
#endif
#endif
#ifndef STB_VORBIS_NO_STDIO
#include <stdio.h>
#endif
#ifndef STB_VORBIS_NO_CRT
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
// find definition of alloca if it's not in stdlib.h:
#if defined(_MSC_VER) || defined(__MINGW32__)
#include <malloc.h>
#endif
#if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
#include <alloca.h>
#endif
#else // STB_VORBIS_NO_CRT
#define NULL 0
#define malloc(s) 0
#define free(s) ((void) 0)
#define realloc(s) 0
#endif // STB_VORBIS_NO_CRT
#include <limits.h>
#ifdef __MINGW32__
// eff you mingw:
// "fixed":
// http://sourceforge.net/p/mingw-w64/mailman/message/32882927/
// "no that broke the build, reverted, who cares about C":
// http://sourceforge.net/p/mingw-w64/mailman/message/32890381/
#ifdef __forceinline
#undef __forceinline
#endif
#define __forceinline
#ifndef alloca
#define alloca __builtin_alloca
#endif
#elif !defined(_MSC_VER)
#if __GNUC__
#define __forceinline inline
#else
#define __forceinline
#endif
#endif
#if STB_VORBIS_MAX_CHANNELS > 256
#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range"
#endif
#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24
#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range"
#endif
#if 0
#include <crtdbg.h>
#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1])
#else
#define CHECK(f) ((void) 0)
#endif
#define MAX_BLOCKSIZE_LOG 13 // from specification
#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG)
typedef unsigned char uint8;
typedef signed char int8;
typedef unsigned short uint16;
typedef signed short int16;
typedef unsigned int uint32;
typedef signed int int32;
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
typedef float codetype;
#ifdef _MSC_VER
#define STBV_NOTUSED(v) (void)(v)
#else
#define STBV_NOTUSED(v) (void)sizeof(v)
#endif
// @NOTE
//
// Some arrays below are tagged "//varies", which means it's actually
// a variable-sized piece of data, but rather than malloc I assume it's
// small enough it's better to just allocate it all together with the
// main thing
//
// Most of the variables are specified with the smallest size I could pack
// them into. It might give better performance to make them all full-sized
// integers. It should be safe to freely rearrange the structures or change
// the sizes larger--nothing relies on silently truncating etc., nor the
// order of variables.
#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH)
#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1)
typedef struct {
int dimensions, entries;
uint8 *codeword_lengths;
float minimum_value;
float delta_value;
uint8 value_bits;
uint8 lookup_type;
uint8 sequence_p;
uint8 sparse;
uint32 lookup_values;
codetype *multiplicands;
uint32 *codewords;
#ifdef STB_VORBIS_FAST_HUFFMAN_SHORT
int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE];
#else
int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE];
#endif
uint32 *sorted_codewords;
int *sorted_values;
int sorted_entries;
} Codebook;
typedef struct {
uint8 order;
uint16 rate;
uint16 bark_map_size;
uint8 amplitude_bits;
uint8 amplitude_offset;
uint8 number_of_books;
uint8 book_list[16]; // varies
} Floor0;
typedef struct {
uint8 partitions;
uint8 partition_class_list[32]; // varies
uint8 class_dimensions[16]; // varies
uint8 class_subclasses[16]; // varies
uint8 class_masterbooks[16]; // varies
int16 subclass_books[16][8]; // varies
uint16 Xlist[31 * 8 + 2]; // varies
uint8 sorted_order[31 * 8 + 2];
uint8 neighbors[31 * 8 + 2][2];
uint8 floor1_multiplier;
uint8 rangebits;
int values;
} Floor1;
typedef union {
Floor0 floor0;
Floor1 floor1;
} Floor;
typedef struct {
uint32 begin, end;
uint32 part_size;
uint8 classifications;
uint8 classbook;
uint8 **classdata;
int16 (*residue_books)[8];
} Residue;
typedef struct {
uint8 magnitude;
uint8 angle;
uint8 mux;
} MappingChannel;
typedef struct {
uint16 coupling_steps;
MappingChannel *chan;
uint8 submaps;
uint8 submap_floor[15]; // varies
uint8 submap_residue[15]; // varies
} Mapping;
typedef struct {
uint8 blockflag;
uint8 mapping;
uint16 windowtype;
uint16 transformtype;
} Mode;
typedef struct {
uint32 goal_crc; // expected crc if match
int bytes_left; // bytes left in packet
uint32 crc_so_far; // running crc
int bytes_done; // bytes processed in _current_ chunk
uint32 sample_loc; // granule pos encoded in page
} CRCscan;
typedef struct {
uint32 page_start, page_end;
uint32 last_decoded_sample;
} ProbedPage;
struct stb_vorbis {
// user-accessible info
unsigned int sample_rate;
int channels;
unsigned int setup_memory_required;
unsigned int temp_memory_required;
unsigned int setup_temp_memory_required;
char *vendor;
int comment_list_length;
char **comment_list;
// input config
#ifndef STB_VORBIS_NO_STDIO
FILE *f;
uint32 f_start;
int close_on_free;
#endif
uint8 *stream;
uint8 *stream_start;
uint8 *stream_end;
uint32 stream_len;
uint8 push_mode;
// the page to seek to when seeking to start, may be zero
uint32 first_audio_page_offset;
// p_first is the page on which the first audio packet ends
// (but not necessarily the page on which it starts)
ProbedPage p_first, p_last;
// memory management
stb_vorbis_alloc alloc;
int setup_offset;
int temp_offset;
// run-time results
int eof;
enum STBVorbisError error;
// user-useful data
// header info
int blocksize[2];
int blocksize_0, blocksize_1;
int codebook_count;
Codebook *codebooks;
int floor_count;
uint16 floor_types[64]; // varies
Floor *floor_config;
int residue_count;
uint16 residue_types[64]; // varies
Residue *residue_config;
int mapping_count;
Mapping *mapping;
int mode_count;
Mode mode_config[64]; // varies
uint32 total_samples;
// decode buffer
float *channel_buffers[STB_VORBIS_MAX_CHANNELS];
float *outputs[STB_VORBIS_MAX_CHANNELS];
float *previous_window[STB_VORBIS_MAX_CHANNELS];
int previous_length;
#ifndef STB_VORBIS_NO_DEFER_FLOOR
int16 *finalY[STB_VORBIS_MAX_CHANNELS];
#else
float *floor_buffers[STB_VORBIS_MAX_CHANNELS];
#endif
uint32 current_loc; // sample location of next frame to decode
int current_loc_valid;
// per-blocksize precomputed data
// twiddle factors
float *A[2], *B[2], *C[2];
float *window[2];
uint16 *bit_reverse[2];
// current page/packet/segment streaming info
uint32 serial; // stream serial number for verification
int last_page;
int segment_count;
uint8 segments[255];
uint8 page_flag;
uint8 bytes_in_seg;
uint8 first_decode;
int next_seg;
int last_seg; // flag that we're on the last segment
int last_seg_which; // what was the segment number of the last seg?
uint32 acc;
int valid_bits;
int packet_bytes;
int end_seg_with_known_loc;
uint32 known_loc_for_packet;
int discard_samples_deferred;
uint32 samples_output;
// push mode scanning
int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching
#ifndef STB_VORBIS_NO_PUSHDATA_API
CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT];
#endif
// sample-access
int channel_buffer_start;
int channel_buffer_end;
};
#if defined(STB_VORBIS_NO_PUSHDATA_API)
#define IS_PUSH_MODE(f) FALSE
#elif defined(STB_VORBIS_NO_PULLDATA_API)
#define IS_PUSH_MODE(f) TRUE
#else
#define IS_PUSH_MODE(f) ((f)->push_mode)
#endif
typedef struct stb_vorbis vorb;
static int error(vorb *f, enum STBVorbisError e) {
f->error = e;
if (!f->eof && e != VORBIS_need_more_data) {
f->error = e; // breakpoint for debugging
}
return 0;
}
// these functions are used for allocating temporary memory
// while decoding. if you can afford the stack space, use
// alloca(); otherwise, provide a temp buffer and it will
// allocate out of those.
#define array_size_required(count, size) (count*(sizeof(void *)+(size)))
#define temp_alloc(f, size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size))
#define temp_free(f, p) (void)0
#define temp_alloc_save(f) ((f)->temp_offset)
#define temp_alloc_restore(f, p) ((f)->temp_offset = (p))
#define temp_block_array(f, count, size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size)
// given a sufficiently large block of memory, make an array of pointers to subblocks of it
static void *make_block_array(void *mem, int count, int size) {
int i;
void **p = (void **) mem;
char *q = (char *) (p + count);
for (i = 0; i < count; ++i) {
p[i] = q;
q += size;
}
return p;
}
static void *setup_malloc(vorb *f, int sz) {
sz = (sz + 7) & ~7; // round up to nearest 8 for alignment of future allocs.
f->setup_memory_required += sz;
if (f->alloc.alloc_buffer) {
void *p = (char *) f->alloc.alloc_buffer + f->setup_offset;
if (f->setup_offset + sz > f->temp_offset)
return NULL;
f->setup_offset += sz;
return p;
}
return sz ? malloc(sz) : NULL;
}
static void setup_free(vorb *f, void *p) {
if (f->alloc.alloc_buffer)
return; // do nothing; setup mem is a stack
free(p);
}
static void *setup_temp_malloc(vorb *f, int sz) {
sz = (sz + 7) & ~7; // round up to nearest 8 for alignment of future allocs.
if (f->alloc.alloc_buffer) {
if (f->temp_offset - sz < f->setup_offset)
return NULL;
f->temp_offset -= sz;
return (char *) f->alloc.alloc_buffer + f->temp_offset;
}
return malloc(sz);
}
static void setup_temp_free(vorb *f, void *p, int sz) {
if (f->alloc.alloc_buffer) {
f->temp_offset += (sz + 7) & ~7;
return;
}
free(p);
}
#define CRC32_POLY 0x04c11db7 // from spec
static uint32 crc_table[256];
static void crc32_init(void) {
int i, j;
uint32 s;
for (i = 0; i < 256; i++) {
for (s = (uint32) i << 24, j = 0; j < 8; ++j)
s = (s << 1) ^ (s >= (1U << 31) ? CRC32_POLY : 0);
crc_table[i] = s;
}
}
static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) {
return (crc << 8) ^ crc_table[byte ^ (crc >> 24)];
}
// used in setup, and for huffman that doesn't go fast path
static unsigned int bit_reverse(unsigned int n) {
n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1);
n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4);
n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8);
return (n >> 16) | (n << 16);
}
static float square(float x) {
return x * x;
}
// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3
// as required by the specification. fast(?) implementation from stb.h
// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup
static int ilog(int32 n) {
static signed char log2_4[16] = {0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4};
if (n < 0)
return 0; // signed n returns 0
// 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29)
if (n < (1 << 14))
if (n < (1 << 4))
return 0 + log2_4[n];
else if (n < (1 << 9))
return 5 + log2_4[n >> 5];
else
return 10 + log2_4[n >> 10];
else if (n < (1 << 24))
if (n < (1 << 19))
return 15 + log2_4[n >> 15];
else
return 20 + log2_4[n >> 20];
else if (n < (1 << 29))
return 25 + log2_4[n >> 25];
else
return 30 + log2_4[n >> 30];
}
#ifndef M_PI
#define M_PI 3.14159265358979323846264f // from CRC
#endif
// code length assigned to a value with no huffman encoding
#define NO_CODE 255
/////////////////////// LEAF SETUP FUNCTIONS //////////////////////////
//
// these functions are only called at setup, and only a few times
// per file
static float float32_unpack(uint32 x) {
// from the specification
uint32 mantissa = x & 0x1fffff;
uint32 sign = x & 0x80000000;
uint32 exp = (x & 0x7fe00000) >> 21;
double res = sign ? -(double) mantissa : (double) mantissa;
return (float) ldexp((float) res, (int) exp - 788);
}
// zlib & jpeg huffman tables assume that the output symbols
// can either be arbitrarily arranged, or have monotonically
// increasing frequencies--they rely on the lengths being sorted;
// this makes for a very simple generation algorithm.
// vorbis allows a huffman table with non-sorted lengths. This
// requires a more sophisticated construction, since symbols in
// order do not map to huffman codes "in order".
static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) {
if (!c->sparse) {
c->codewords[symbol] = huff_code;
} else {
c->codewords[count] = huff_code;
c->codeword_lengths[count] = len;
values[count] = symbol;
}
}
static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) {
int i, k, m = 0;
uint32 available[32];
memset(available, 0, sizeof(available));
// find the first entry
for (k = 0; k < n; ++k)
if (len[k] < NO_CODE)
break;
if (k == n) {
assert(c->sorted_entries == 0);
return TRUE;
}
assert(len[k] < 32); // no error return required, code reading lens checks this
// add to the list
add_entry(c, 0, k, m++, len[k], values);
// add all available leaves
for (i = 1; i <= len[k]; ++i)
available[i] = 1U << (32 - i);
// note that the above code treats the first case specially,
// but it's really the same as the following code, so they
// could probably be combined (except the initial code is 0,
// and I use 0 in available[] to mean 'empty')
for (i = k + 1; i < n; ++i) {
uint32 res;
int z = len[i], y;
if (z == NO_CODE)
continue;
assert(z < 32); // no error return required, code reading lens checks this
// find lowest available leaf (should always be earliest,
// which is what the specification calls for)
// note that this property, and the fact we can never have
// more than one free leaf at a given level, isn't totally
// trivial to prove, but it seems true and the assert never
// fires, so!
while (z > 0 && !available[z])
--z;
if (z == 0) {
return FALSE;
}
res = available[z];
available[z] = 0;
add_entry(c, bit_reverse(res), i, m++, len[i], values);
// propagate availability up the tree
if (z != len[i]) {
for (y = len[i]; y > z; --y) {
assert(available[y] == 0);
available[y] = res + (1 << (32 - y));
}
}
}
return TRUE;
}
// accelerated huffman table allows fast O(1) match of all symbols
// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH
static void compute_accelerated_huffman(Codebook *c) {
int i, len;
for (i = 0; i < FAST_HUFFMAN_TABLE_SIZE; ++i)
c->fast_huffman[i] = -1;
len = c->sparse ? c->sorted_entries : c->entries;
#ifdef STB_VORBIS_FAST_HUFFMAN_SHORT
if (len > 32767)
len = 32767; // largest possible value we can encode!
#endif
for (i = 0; i < len; ++i) {
if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) {
uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i];
// set table entries for all bit combinations in the higher bits
while (z < FAST_HUFFMAN_TABLE_SIZE) {
c->fast_huffman[z] = i;
z += 1 << c->codeword_lengths[i];
}
}
}
}
#ifdef _MSC_VER
#define STBV_CDECL __cdecl
#else
#define STBV_CDECL
#endif
static int STBV_CDECL uint32_compare(const void *p, const void *q) {
uint32 x = *(uint32 *) p;
uint32 y = *(uint32 *) q;
return x < y ? -1 : x > y;
}
static int include_in_sort(Codebook *c, uint8 len) {
if (c->sparse) {
assert(len != NO_CODE);
return TRUE;
}
if (len == NO_CODE)
return FALSE;
if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH)
return TRUE;
return FALSE;
}
// if the fast table above doesn't work, we want to binary
// search them... need to reverse the bits
static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) {
int i, len;
// build a list of all the entries
// OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN.
// this is kind of a frivolous optimization--I don't see any performance improvement,
// but it's like 4 extra lines of code, so.
if (!c->sparse) {
int k = 0;
for (i = 0; i < c->entries; ++i)
if (include_in_sort(c, lengths[i]))
c->sorted_codewords[k++] = bit_reverse(c->codewords[i]);
assert(k == c->sorted_entries);
} else {
for (i = 0; i < c->sorted_entries; ++i)
c->sorted_codewords[i] = bit_reverse(c->codewords[i]);
}
qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare);
c->sorted_codewords[c->sorted_entries] = 0xffffffff;
len = c->sparse ? c->sorted_entries : c->entries;
// now we need to indicate how they correspond; we could either
// #1: sort a different data structure that says who they correspond to
// #2: for each sorted entry, search the original list to find who corresponds
// #3: for each original entry, find the sorted entry
// #1 requires extra storage, #2 is slow, #3 can use binary search!
for (i = 0; i < len; ++i) {
int huff_len = c->sparse ? lengths[values[i]] : lengths[i];
if (include_in_sort(c, huff_len)) {
uint32 code = bit_reverse(c->codewords[i]);
int x = 0, n = c->sorted_entries;
while (n > 1) {
// invariant: sc[x] <= code < sc[x+n]
int m = x + (n >> 1);
if (c->sorted_codewords[m] <= code) {
x = m;
n -= (n >> 1);
} else {
n >>= 1;
}
}
assert(c->sorted_codewords[x] == code);
if (c->sparse) {
c->sorted_values[x] = values[i];
c->codeword_lengths[x] = huff_len;
} else {
c->sorted_values[x] = i;
}
}
}
}
// only run while parsing the header (3 times)
static int vorbis_validate(uint8 *data) {
static uint8 vorbis[6] = {'v', 'o', 'r', 'b', 'i', 's'};
return memcmp(data, vorbis, 6) == 0;
}
// called from setup only, once per code book
// (formula implied by specification)
static int lookup1_values(int entries, int dim) {
int r = (int) floor(exp((float) log((float) entries) / dim));
if ((int) floor(pow((float) r + 1, dim)) <= entries) // (int) cast for MinGW warning;
++r; // floor() to avoid _ftol() when non-CRT
if (pow((float) r + 1, dim) <= entries)
return -1;
if ((int) floor(pow((float) r, dim)) > entries)
return -1;
return r;
}
// called twice per file
static void compute_twiddle_factors(int n, float *A, float *B, float *C) {
int n4 = n >> 2, n8 = n >> 3;
int k, k2;
for (k = k2 = 0; k < n4; ++k, k2 += 2) {
A[k2] = (float) cos(4 * k * M_PI / n);
A[k2 + 1] = (float) -sin(4 * k * M_PI / n);
B[k2] = (float) cos((k2 + 1) * M_PI / n / 2) * 0.5f;
B[k2 + 1] = (float) sin((k2 + 1) * M_PI / n / 2) * 0.5f;
}
for (k = k2 = 0; k < n8; ++k, k2 += 2) {
C[k2] = (float) cos(2 * (k2 + 1) * M_PI / n);
C[k2 + 1] = (float) -sin(2 * (k2 + 1) * M_PI / n);
}
}
static void compute_window(int n, float *window) {
int n2 = n >> 1, i;
for (i = 0; i < n2; ++i)
window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI)));
}
static void compute_bitreverse(int n, uint16 *rev) {
int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions
int i, n8 = n >> 3;
for (i = 0; i < n8; ++i)
rev[i] = (bit_reverse(i) >> (32 - ld + 3)) << 2;
}
static int init_blocksize(vorb *f, int b, int n) {
int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3;
f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2);
f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2);
f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4);
if (!f->A[b] || !f->B[b] || !f->C[b])
return error(f, VORBIS_outofmem);
compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]);
f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2);
if (!f->window[b])
return error(f, VORBIS_outofmem);
compute_window(n, f->window[b]);
f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8);
if (!f->bit_reverse[b])
return error(f, VORBIS_outofmem);
compute_bitreverse(n, f->bit_reverse[b]);
return TRUE;
}
static void neighbors(uint16 *x, int n, int *plow, int *phigh) {
int low = -1;
int high = 65536;
int i;
for (i = 0; i < n; ++i) {
if (x[i] > low && x[i] < x[n]) {
*plow = i;
low = x[i];
}
if (x[i] < high && x[i] > x[n]) {
*phigh = i;
high = x[i];
}
}
}
// this has been repurposed so y is now the original index instead of y
typedef struct {
uint16 x, id;
} stbv__floor_ordering;
static int STBV_CDECL point_compare(const void *p, const void *q) {
stbv__floor_ordering *a = (stbv__floor_ordering *) p;
stbv__floor_ordering *b = (stbv__floor_ordering *) q;
return a->x < b->x ? -1 : a->x > b->x;
}
//
/////////////////////// END LEAF SETUP FUNCTIONS //////////////////////////
#if defined(STB_VORBIS_NO_STDIO)
#define USE_MEMORY(z) TRUE
#else
#define USE_MEMORY(z) ((z)->stream)
#endif
static uint8 get8(vorb *z) {
if (USE_MEMORY(z)) {
if (z->stream >= z->stream_end) {
z->eof = TRUE;
return 0;
}
return *z->stream++;
}
#ifndef STB_VORBIS_NO_STDIO
{
int c = fgetc(z->f);
if (c == EOF) {
z->eof = TRUE;
return 0;
}
return c;
}
#endif
}
static uint32 get32(vorb *f) {
uint32 x;
x = get8(f);
x += get8(f) << 8;
x += get8(f) << 16;
x += (uint32) get8(f) << 24;
return x;
}
static int getn(vorb *z, uint8 *data, int n) {
if (USE_MEMORY(z)) {
if (z->stream + n > z->stream_end) {
z->eof = 1;
return 0;
}
memcpy(data, z->stream, n);
z->stream += n;
return 1;
}
#ifndef STB_VORBIS_NO_STDIO
if (fread(data, n, 1, z->f) == 1)
return 1;
else {
z->eof = 1;
return 0;
}
#endif
}
static void skip(vorb *z, int n) {
if (USE_MEMORY(z)) {
z->stream += n;
if (z->stream >= z->stream_end)
z->eof = 1;
return;
}
#ifndef STB_VORBIS_NO_STDIO
{
long x = ftell(z->f);
fseek(z->f, x + n, SEEK_SET);
}
#endif
}
static int set_file_offset(stb_vorbis *f, unsigned int loc) {
#ifndef STB_VORBIS_NO_PUSHDATA_API
if (f->push_mode)
return 0;
#endif
f->eof = 0;
if (USE_MEMORY(f)) {
if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) {
f->stream = f->stream_end;
f->eof = 1;
return 0;
} else {
f->stream = f->stream_start + loc;
return 1;
}
}
#ifndef STB_VORBIS_NO_STDIO
if (loc + f->f_start < loc || loc >= 0x80000000) {
loc = 0x7fffffff;
f->eof = 1;
} else {
loc += f->f_start;
}
if (!fseek(f->f, loc, SEEK_SET))
return 1;
f->eof = 1;
fseek(f->f, f->f_start, SEEK_END);
return 0;
#endif
}
static uint8 ogg_page_header[4] = {0x4f, 0x67, 0x67, 0x53};
static int capture_pattern(vorb *f) {
if (0x4f != get8(f))
return FALSE;
if (0x67 != get8(f))
return FALSE;
if (0x67 != get8(f))
return FALSE;
if (0x53 != get8(f))
return FALSE;
return TRUE;
}
#define PAGEFLAG_continued_packet 1
#define PAGEFLAG_first_page 2
#define PAGEFLAG_last_page 4
static int start_page_no_capturepattern(vorb *f) {
uint32 loc0, loc1, n;
if (f->first_decode && !IS_PUSH_MODE(f)) {
f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4;
}
// stream structure version
if (0 != get8(f))
return error(f, VORBIS_invalid_stream_structure_version);
// header flag
f->page_flag = get8(f);
// absolute granule position
loc0 = get32(f);
loc1 = get32(f);
// @TODO: validate loc0,loc1 as valid positions?
// stream serial number -- vorbis doesn't interleave, so discard
get32(f);
//if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number);
// page sequence number
n = get32(f);
f->last_page = n;
// CRC32
get32(f);
// page_segments
f->segment_count = get8(f);
if (!getn(f, f->segments, f->segment_count))
return error(f, VORBIS_unexpected_eof);
// assume we _don't_ know any the sample position of any segments
f->end_seg_with_known_loc = -2;
if (loc0 != ~0U || loc1 != ~0U) {
int i;
// determine which packet is the last one that will complete
for (i = f->segment_count - 1; i >= 0; --i)
if (f->segments[i] < 255)
break;
// 'i' is now the index of the _last_ segment of a packet that ends
if (i >= 0) {
f->end_seg_with_known_loc = i;
f->known_loc_for_packet = loc0;
}
}
if (f->first_decode) {
int i, len;
len = 0;
for (i = 0; i < f->segment_count; ++i)
len += f->segments[i];
len += 27 + f->segment_count;
f->p_first.page_end = f->p_first.page_start + len;
f->p_first.last_decoded_sample = loc0;
}
f->next_seg = 0;
return TRUE;
}
static int start_page(vorb *f) {
if (!capture_pattern(f))
return error(f, VORBIS_missing_capture_pattern);
return start_page_no_capturepattern(f);
}
static int start_packet(vorb *f) {
while (f->next_seg == -1) {
if (!start_page(f))
return FALSE;
if (f->page_flag & PAGEFLAG_continued_packet)
return error(f, VORBIS_continued_packet_flag_invalid);
}
f->last_seg = FALSE;
f->valid_bits = 0;
f->packet_bytes = 0;
f->bytes_in_seg = 0;
// f->next_seg is now valid
return TRUE;
}
static int maybe_start_packet(vorb *f) {
if (f->next_seg == -1) {
int x = get8(f);
if (f->eof)
return FALSE; // EOF at page boundary is not an error!
if (0x4f != x)
return error(f, VORBIS_missing_capture_pattern);
if (0x67 != get8(f))
return error(f, VORBIS_missing_capture_pattern);
if (0x67 != get8(f))
return error(f, VORBIS_missing_capture_pattern);
if (0x53 != get8(f))
return error(f, VORBIS_missing_capture_pattern);
if (!start_page_no_capturepattern(f))
return FALSE;
if (f->page_flag & PAGEFLAG_continued_packet) {
// set up enough state that we can read this packet if we want,
// e.g. during recovery
f->last_seg = FALSE;
f->bytes_in_seg = 0;
return error(f, VORBIS_continued_packet_flag_invalid);
}
}
return start_packet(f);
}
static int next_segment(vorb *f) {
int len;
if (f->last_seg)
return 0;
if (f->next_seg == -1) {
f->last_seg_which = f->segment_count - 1; // in case start_page fails
if (!start_page(f)) {
f->last_seg = 1;
return 0;
}
if (!(f->page_flag & PAGEFLAG_continued_packet))
return error(f, VORBIS_continued_packet_flag_invalid);
}
len = f->segments[f->next_seg++];
if (len < 255) {
f->last_seg = TRUE;
f->last_seg_which = f->next_seg - 1;
}
if (f->next_seg >= f->segment_count)
f->next_seg = -1;
assert(f->bytes_in_seg == 0);
f->bytes_in_seg = len;
return len;
}
#define EOP (-1)
#define INVALID_BITS (-1)
static int get8_packet_raw(vorb *f) {
if (!f->bytes_in_seg) { // CLANG!
if (f->last_seg)
return EOP;
else if (!next_segment(f))
return EOP;
}
assert(f->bytes_in_seg > 0);
--f->bytes_in_seg;
++f->packet_bytes;
return get8(f);
}
static int get8_packet(vorb *f) {
int x = get8_packet_raw(f);
f->valid_bits = 0;
return x;
}
static int get32_packet(vorb *f) {
uint32 x;
x = get8_packet(f);
x += get8_packet(f) << 8;
x += get8_packet(f) << 16;
x += (uint32) get8_packet(f) << 24;
return x;
}
static void flush_packet(vorb *f) {
while (get8_packet_raw(f) != EOP);
}
// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important
// as the huffman decoder?
static uint32 get_bits(vorb *f, int n) {
uint32 z;
if (f->valid_bits < 0)
return 0;
if (f->valid_bits < n) {
if (n > 24) {
// the accumulator technique below would not work correctly in this case
z = get_bits(f, 24);
z += get_bits(f, n - 24) << 24;
return z;
}
if (f->valid_bits == 0)
f->acc = 0;
while (f->valid_bits < n) {
int z = get8_packet_raw(f);
if (z == EOP) {
f->valid_bits = INVALID_BITS;
return 0;
}
f->acc += z << f->valid_bits;
f->valid_bits += 8;
}
}
assert(f->valid_bits >= n);
z = f->acc & ((1 << n) - 1);
f->acc >>= n;
f->valid_bits -= n;
return z;
}
// @OPTIMIZE: primary accumulator for huffman
// expand the buffer to as many bits as possible without reading off end of packet
// it might be nice to allow f->valid_bits and f->acc to be stored in registers,
// e.g. cache them locally and decode locally
static __forceinline void prep_huffman(vorb *f) {
if (f->valid_bits <= 24) {
if (f->valid_bits == 0)
f->acc = 0;
do {
int z;
if (f->last_seg && !f->bytes_in_seg)
return;
z = get8_packet_raw(f);
if (z == EOP)
return;
f->acc += (unsigned) z << f->valid_bits;
f->valid_bits += 8;
} while (f->valid_bits <= 24);
}
}
enum {
VORBIS_packet_id = 1,
VORBIS_packet_comment = 3,
VORBIS_packet_setup = 5
};
static int codebook_decode_scalar_raw(vorb *f, Codebook *c) {
int i;
prep_huffman(f);
if (c->codewords == NULL && c->sorted_codewords == NULL)
return -1;
// cases to use binary search: sorted_codewords && !c->codewords
// sorted_codewords && c->entries > 8
if (c->entries > 8 ? c->sorted_codewords != NULL : !c->codewords) {
// binary search
uint32 code = bit_reverse(f->acc);
int x = 0, n = c->sorted_entries, len;
while (n > 1) {
// invariant: sc[x] <= code < sc[x+n]
int m = x + (n >> 1);
if (c->sorted_codewords[m] <= code) {
x = m;
n -= (n >> 1);
} else {
n >>= 1;
}
}
// x is now the sorted index
if (!c->sparse)
x = c->sorted_values[x];
// x is now sorted index if sparse, or symbol otherwise
len = c->codeword_lengths[x];
if (f->valid_bits >= len) {
f->acc >>= len;
f->valid_bits -= len;
return x;
}
f->valid_bits = 0;
return -1;
}
// if small, linear search
assert(!c->sparse);
for (i = 0; i < c->entries; ++i) {
if (c->codeword_lengths[i] == NO_CODE)
continue;
if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i]) - 1))) {
if (f->valid_bits >= c->codeword_lengths[i]) {
f->acc >>= c->codeword_lengths[i];
f->valid_bits -= c->codeword_lengths[i];
return i;
}
f->valid_bits = 0;
return -1;
}
}
error(f, VORBIS_invalid_stream);
f->valid_bits = 0;
return -1;
}
#ifndef STB_VORBIS_NO_INLINE_DECODE
#define DECODE_RAW(var, f, c) \
if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \
prep_huffman(f); \
var = f->acc & FAST_HUFFMAN_TABLE_MASK; \
var = c->fast_huffman[var]; \
if (var >= 0) { \
int n = c->codeword_lengths[var]; \
f->acc >>= n; \
f->valid_bits -= n; \
if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \
} else { \
var = codebook_decode_scalar_raw(f,c); \
}
#else
static int codebook_decode_scalar(vorb *f, Codebook *c)
{
int i;
if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH)
prep_huffman(f);
// fast huffman table lookup
i = f->acc & FAST_HUFFMAN_TABLE_MASK;
i = c->fast_huffman[i];
if (i >= 0) {
f->acc >>= c->codeword_lengths[i];
f->valid_bits -= c->codeword_lengths[i];
if (f->valid_bits < 0) { f->valid_bits = 0; return -1; }
return i;
}
return codebook_decode_scalar_raw(f,c);
}
#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c);
#endif
#define DECODE(var, f, c) \
DECODE_RAW(var,f,c) \
if (c->sparse) var = c->sorted_values[var];
#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK
#define DECODE_VQ(var, f, c) DECODE_RAW(var,f,c)
#else
#define DECODE_VQ(var,f,c) DECODE(var,f,c)
#endif
// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case
// where we avoid one addition
#define CODEBOOK_ELEMENT(c, off) (c->multiplicands[off])
#define CODEBOOK_ELEMENT_FAST(c, off) (c->multiplicands[off])
#define CODEBOOK_ELEMENT_BASE(c) (0)
static int codebook_decode_start(vorb *f, Codebook *c) {
int z = -1;
// type 0 is only legal in a scalar context
if (c->lookup_type == 0)
error(f, VORBIS_invalid_stream);
else {
DECODE_VQ(z, f, c);
if (c->sparse)
assert(z < c->sorted_entries);
if (z < 0) { // check for EOP
if (!f->bytes_in_seg)
if (f->last_seg)
return z;
error(f, VORBIS_invalid_stream);
}
}
return z;
}
static int codebook_decode(vorb *f, Codebook *c, float *output, int len) {
int i, z = codebook_decode_start(f, c);
if (z < 0)
return FALSE;
if (len > c->dimensions)
len = c->dimensions;
#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK
if (c->lookup_type == 1) {
float last = CODEBOOK_ELEMENT_BASE(c);
int div = 1;
for (i=0; i < len; ++i) {
int off = (z / div) % c->lookup_values;
float val = CODEBOOK_ELEMENT_FAST(c,off) + last;
output[i] += val;
if (c->sequence_p) last = val + c->minimum_value;
div *= c->lookup_values;
}
return TRUE;
}
#endif
z *= c->dimensions;
if (c->sequence_p) {
float last = CODEBOOK_ELEMENT_BASE(c);
for (i = 0; i < len; ++i) {
float val = CODEBOOK_ELEMENT_FAST(c, z + i) + last;
output[i] += val;
last = val + c->minimum_value;
}
} else {
float last = CODEBOOK_ELEMENT_BASE(c);
for (i = 0; i < len; ++i) {
output[i] += CODEBOOK_ELEMENT_FAST(c, z + i) + last;
}
}
return TRUE;
}
static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) {
int i, z = codebook_decode_start(f, c);
float last = CODEBOOK_ELEMENT_BASE(c);
if (z < 0)
return FALSE;
if (len > c->dimensions)
len = c->dimensions;
#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK
if (c->lookup_type == 1) {
int div = 1;
for (i=0; i < len; ++i) {
int off = (z / div) % c->lookup_values;
float val = CODEBOOK_ELEMENT_FAST(c,off) + last;
output[i*step] += val;
if (c->sequence_p) last = val;
div *= c->lookup_values;
}
return TRUE;
}
#endif
z *= c->dimensions;
for (i = 0; i < len; ++i) {
float val = CODEBOOK_ELEMENT_FAST(c, z + i) + last;
output[i * step] += val;
if (c->sequence_p)
last = val;
}
return TRUE;
}
static int codebook_decode_deinterleave_repeat(vorb *f,
Codebook *c,
float **outputs,
int ch,
int *c_inter_p,
int *p_inter_p,
int len,
int total_decode) {
int c_inter = *c_inter_p;
int p_inter = *p_inter_p;
int i, z, effective = c->dimensions;
// type 0 is only legal in a scalar context
if (c->lookup_type == 0)
return error(f, VORBIS_invalid_stream);
while (total_decode > 0) {
float last = CODEBOOK_ELEMENT_BASE(c);
DECODE_VQ(z, f, c);
#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK
assert(!c->sparse || z < c->sorted_entries);
#endif
if (z < 0) {
if (!f->bytes_in_seg)
if (f->last_seg)
return FALSE;
return error(f, VORBIS_invalid_stream);
}
// if this will take us off the end of the buffers, stop short!
// we check by computing the length of the virtual interleaved
// buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter),
// and the length we'll be using (effective)
if (c_inter + p_inter * ch + effective > len * ch) {
effective = len * ch - (p_inter * ch - c_inter);
}
#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK
if (c->lookup_type == 1) {
int div = 1;
for (i=0; i < effective; ++i) {
int off = (z / div) % c->lookup_values;
float val = CODEBOOK_ELEMENT_FAST(c,off) + last;
if (outputs[c_inter])
outputs[c_inter][p_inter] += val;
if (++c_inter == ch) { c_inter = 0; ++p_inter; }
if (c->sequence_p) last = val;
div *= c->lookup_values;
}
} else
#endif
{
z *= c->dimensions;
if (c->sequence_p) {
for (i = 0; i < effective; ++i) {
float val = CODEBOOK_ELEMENT_FAST(c, z + i) + last;
if (outputs[c_inter])
outputs[c_inter][p_inter] += val;
if (++c_inter == ch) {
c_inter = 0;
++p_inter;
}
last = val;
}
} else {
for (i = 0; i < effective; ++i) {
float val = CODEBOOK_ELEMENT_FAST(c, z + i) + last;
if (outputs[c_inter])
outputs[c_inter][p_inter] += val;
if (++c_inter == ch) {
c_inter = 0;
++p_inter;
}
}
}
}
total_decode -= effective;
}
*c_inter_p = c_inter;
*p_inter_p = p_inter;
return TRUE;
}
static int predict_point(int x, int x0, int x1, int y0, int y1) {
int dy = y1 - y0;
int adx = x1 - x0;
// @OPTIMIZE: force int division to round in the right direction... is this necessary on x86?
int err = abs(dy) * (x - x0);
int off = err / adx;
return dy < 0 ? y0 - off : y0 + off;
}
// the following table is block-copied from the specification
static float inverse_db_table[256] =
{
1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f,
1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f,
1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f,
2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f,
2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f,
3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f,
4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f,
6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f,
7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f,
1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f,
1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f,
1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f,
2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f,
2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f,
3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f,
4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f,
5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f,
7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f,
9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f,
1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f,
1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f,
2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f,
2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f,
3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f,
4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f,
5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f,
7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f,
9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f,
0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f,
0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f,
0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f,
0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f,
0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f,
0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f,
0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f,
0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f,
0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f,
0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f,
0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f,
0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f,
0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f,
0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f,
0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f,
0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f,
0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f,
0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f,
0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f,
0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f,
0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f,
0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f,
0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f,
0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f,
0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f,
0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f,
0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f,
0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f,
0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f,
0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f,
0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f,
0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f,
0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f,
0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f,
0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f,
0.82788260f, 0.88168307f, 0.9389798f, 1.0f
};
// @OPTIMIZE: if you want to replace this bresenham line-drawing routine,
// note that you must produce bit-identical output to decode correctly;
// this specific sequence of operations is specified in the spec (it's
// drawing integer-quantized frequency-space lines that the encoder
// expects to be exactly the same)
// ... also, isn't the whole point of Bresenham's algorithm to NOT
// have to divide in the setup? sigh.
#ifndef STB_VORBIS_NO_DEFER_FLOOR
#define LINE_OP(a, b) a *= b
#else
#define LINE_OP(a,b) a = b
#endif
#ifdef STB_VORBIS_DIVIDE_TABLE
#define DIVTAB_NUMER 32
#define DIVTAB_DENOM 64
int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB
#endif
static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) {
int dy = y1 - y0;
int adx = x1 - x0;
int ady = abs(dy);
int base;
int x = x0, y = y0;
int err = 0;
int sy;
#ifdef STB_VORBIS_DIVIDE_TABLE
if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) {
if (dy < 0) {
base = -integer_divide_table[ady][adx];
sy = base-1;
} else {
base = integer_divide_table[ady][adx];
sy = base+1;
}
} else {
base = dy / adx;
if (dy < 0)
sy = base - 1;
else
sy = base+1;
}
#else
base = dy / adx;
if (dy < 0)
sy = base - 1;
else
sy = base + 1;
#endif
ady -= abs(base) * adx;
if (x1 > n)
x1 = n;
if (x < x1) {
LINE_OP(output[x], inverse_db_table[y & 255]);
for (++x; x < x1; ++x) {
err += ady;
if (err >= adx) {
err -= adx;
y += sy;
} else
y += base;
LINE_OP(output[x], inverse_db_table[y & 255]);
}
}
}
static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) {
int k;
if (rtype == 0) {
int step = n / book->dimensions;
for (k = 0; k < step; ++k)
if (!codebook_decode_step(f, book, target + offset + k, n - offset - k, step))
return FALSE;
} else {
for (k = 0; k < n;) {
if (!codebook_decode(f, book, target + offset, n - k))
return FALSE;
k += book->dimensions;
offset += book->dimensions;
}
}
return TRUE;
}
// n is 1/2 of the blocksize --
// specification: "Correct per-vector decode length is [n]/2"
static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) {
int i, j, pass;
Residue *r = f->residue_config + rn;
int rtype = f->residue_types[rn];
int c = r->classbook;
int classwords = f->codebooks[c].dimensions;
unsigned int actual_size = rtype == 2 ? n * 2 : n;
unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size);
unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size);
int n_read = limit_r_end - limit_r_begin;
int part_read = n_read / r->part_size;
int temp_alloc_point = temp_alloc_save(f);
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
uint8 ***part_classdata = (uint8 ***) temp_block_array(f, f->channels, part_read * sizeof(**part_classdata));
#else
int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications));
#endif
CHECK(f);
for (i = 0; i < ch; ++i)
if (!do_not_decode[i])
memset(residue_buffers[i], 0, sizeof(float) * n);
if (rtype == 2 && ch != 1) {
for (j = 0; j < ch; ++j)
if (!do_not_decode[j])
break;
if (j == ch)
goto done;
for (pass = 0; pass < 8; ++pass) {
int pcount = 0, class_set = 0;
if (ch == 2) {
while (pcount < part_read) {
int z = r->begin + pcount * r->part_size;
int c_inter = (z & 1), p_inter = z >> 1;
if (pass == 0) {
Codebook *c = f->codebooks + r->classbook;
int q;
DECODE(q, f, c);
if (q == EOP)
goto done;
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
part_classdata[0][class_set] = r->classdata[q];
#else
for (i=classwords-1; i >= 0; --i) {
classifications[0][i+pcount] = q % r->classifications;
q /= r->classifications;
}
#endif
}
for (i = 0; i < classwords && pcount < part_read; ++i, ++pcount) {
int z = r->begin + pcount * r->part_size;
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
int c = part_classdata[0][class_set][i];
#else
int c = classifications[0][pcount];
#endif
int b = r->residue_books[c][pass];
if (b >= 0) {
Codebook *book = f->codebooks + b;
#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK
if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size))
goto done;
#else
// saves 1%
if (!codebook_decode_deinterleave_repeat(f,
book,
residue_buffers,
ch,
&c_inter,
&p_inter,
n,
r->part_size))
goto done;
#endif
} else {
z += r->part_size;
c_inter = z & 1;
p_inter = z >> 1;
}
}
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
++class_set;
#endif
}
} else if (ch > 2) {
while (pcount < part_read) {
int z = r->begin + pcount * r->part_size;
int c_inter = z % ch, p_inter = z / ch;
if (pass == 0) {
Codebook *c = f->codebooks + r->classbook;
int q;
DECODE(q, f, c);
if (q == EOP)
goto done;
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
part_classdata[0][class_set] = r->classdata[q];
#else
for (i=classwords-1; i >= 0; --i) {
classifications[0][i+pcount] = q % r->classifications;
q /= r->classifications;
}
#endif
}
for (i = 0; i < classwords && pcount < part_read; ++i, ++pcount) {
int z = r->begin + pcount * r->part_size;
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
int c = part_classdata[0][class_set][i];
#else
int c = classifications[0][pcount];
#endif
int b = r->residue_books[c][pass];
if (b >= 0) {
Codebook *book = f->codebooks + b;
if (!codebook_decode_deinterleave_repeat(f,
book,
residue_buffers,
ch,
&c_inter,
&p_inter,
n,
r->part_size))
goto done;
} else {
z += r->part_size;
c_inter = z % ch;
p_inter = z / ch;
}
}
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
++class_set;
#endif
}
}
}
goto done;
}
CHECK(f);
for (pass = 0; pass < 8; ++pass) {
int pcount = 0, class_set = 0;
while (pcount < part_read) {
if (pass == 0) {
for (j = 0; j < ch; ++j) {
if (!do_not_decode[j]) {
Codebook *c = f->codebooks + r->classbook;
int temp;
DECODE(temp, f, c);
if (temp == EOP)
goto done;
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
part_classdata[j][class_set] = r->classdata[temp];
#else
for (i=classwords-1; i >= 0; --i) {
classifications[j][i+pcount] = temp % r->classifications;
temp /= r->classifications;
}
#endif
}
}
}
for (i = 0; i < classwords && pcount < part_read; ++i, ++pcount) {
for (j = 0; j < ch; ++j) {
if (!do_not_decode[j]) {
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
int c = part_classdata[j][class_set][i];
#else
int c = classifications[j][pcount];
#endif
int b = r->residue_books[c][pass];
if (b >= 0) {
float *target = residue_buffers[j];
int offset = r->begin + pcount * r->part_size;
int n = r->part_size;
Codebook *book = f->codebooks + b;
if (!residue_decode(f, book, target, offset, n, rtype))
goto done;
}
}
}
}
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
++class_set;
#endif
}
}
done:
CHECK(f);
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
temp_free(f, part_classdata);
#else
temp_free(f,classifications);
#endif
temp_alloc_restore(f, temp_alloc_point);
}
#if 0
// slow way for debugging
void inverse_mdct_slow(float *buffer, int n)
{
int i,j;
int n2 = n >> 1;
float *x = (float *) malloc(sizeof(*x) * n2);
memcpy(x, buffer, sizeof(*x) * n2);
for (i=0; i < n; ++i) {
float acc = 0;
for (j=0; j < n2; ++j)
// formula from paper:
//acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1));
// formula from wikipedia
//acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5));
// these are equivalent, except the formula from the paper inverts the multiplier!
// however, what actually works is NO MULTIPLIER!?!
//acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5));
acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1));
buffer[i] = acc;
}
free(x);
}
#elif 0
// same as above, but just barely able to run in real time on modern machines
void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype)
{
float mcos[16384];
int i,j;
int n2 = n >> 1, nmask = (n << 2) -1;
float *x = (float *) malloc(sizeof(*x) * n2);
memcpy(x, buffer, sizeof(*x) * n2);
for (i=0; i < 4*n; ++i)
mcos[i] = (float) cos(M_PI / 2 * i / n);
for (i=0; i < n; ++i) {
float acc = 0;
for (j=0; j < n2; ++j)
acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask];
buffer[i] = acc;
}
free(x);
}
#elif 0
// transform to use a slow dct-iv; this is STILL basically trivial,
// but only requires half as many ops
void dct_iv_slow(float *buffer, int n)
{
float mcos[16384];
float x[2048];
int i,j;
int n2 = n >> 1, nmask = (n << 3) - 1;
memcpy(x, buffer, sizeof(*x) * n);
for (i=0; i < 8*n; ++i)
mcos[i] = (float) cos(M_PI / 4 * i / n);
for (i=0; i < n; ++i) {
float acc = 0;
for (j=0; j < n; ++j)
acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask];
buffer[i] = acc;
}
}
void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype)
{
int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4;
float temp[4096];
memcpy(temp, buffer, n2 * sizeof(float));
dct_iv_slow(temp, n2); // returns -c'-d, a-b'
for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b'
for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d'
for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d
}
#endif
#ifndef LIBVORBIS_MDCT
#define LIBVORBIS_MDCT 0
#endif
#if LIBVORBIS_MDCT
// directly call the vorbis MDCT using an interface documented
// by Jeff Roberts... useful for performance comparison
typedef struct
{
int n;
int log2n;
float *trig;
int *bitrev;
float scale;
} mdct_lookup;
extern void mdct_init(mdct_lookup *lookup, int n);
extern void mdct_clear(mdct_lookup *l);
extern void mdct_backward(mdct_lookup *init, float *in, float *out);
mdct_lookup M1,M2;
void inverse_mdct(float *buffer, int n, vorb *f, int blocktype)
{
mdct_lookup *M;
if (M1.n == n) M = &M1;
else if (M2.n == n) M = &M2;
else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; }
else {
if (M2.n) __asm int 3;
mdct_init(&M2, n);
M = &M2;
}
mdct_backward(M, buffer, buffer);
}
#endif
// the following were split out into separate functions while optimizing;
// they could be pushed back up but eh. __forceinline showed no change;
// they're probably already being inlined.
static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) {
float *ee0 = e + i_off;
float *ee2 = ee0 + k_off;
int i;
assert((n & 3) == 0);
for (i = (n >> 2); i > 0; --i) {
float k00_20, k01_21;
k00_20 = ee0[0] - ee2[0];
k01_21 = ee0[-1] - ee2[-1];
ee0[0] += ee2[0];//ee0[ 0] = ee0[ 0] + ee2[ 0];
ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1];
ee2[0] = k00_20 * A[0] - k01_21 * A[1];
ee2[-1] = k01_21 * A[0] + k00_20 * A[1];
A += 8;
k00_20 = ee0[-2] - ee2[-2];
k01_21 = ee0[-3] - ee2[-3];
ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2];
ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3];
ee2[-2] = k00_20 * A[0] - k01_21 * A[1];
ee2[-3] = k01_21 * A[0] + k00_20 * A[1];
A += 8;
k00_20 = ee0[-4] - ee2[-4];
k01_21 = ee0[-5] - ee2[-5];
ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4];
ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5];
ee2[-4] = k00_20 * A[0] - k01_21 * A[1];
ee2[-5] = k01_21 * A[0] + k00_20 * A[1];
A += 8;
k00_20 = ee0[-6] - ee2[-6];
k01_21 = ee0[-7] - ee2[-7];
ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6];
ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7];
ee2[-6] = k00_20 * A[0] - k01_21 * A[1];
ee2[-7] = k01_21 * A[0] + k00_20 * A[1];
A += 8;
ee0 -= 8;
ee2 -= 8;
}
}
static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) {
int i;
float k00_20, k01_21;
float *e0 = e + d0;
float *e2 = e0 + k_off;
for (i = lim >> 2; i > 0; --i) {
k00_20 = e0[-0] - e2[-0];
k01_21 = e0[-1] - e2[-1];
e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0];
e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1];
e2[-0] = (k00_20) * A[0] - (k01_21) * A[1];
e2[-1] = (k01_21) * A[0] + (k00_20) * A[1];
A += k1;
k00_20 = e0[-2] - e2[-2];
k01_21 = e0[-3] - e2[-3];
e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2];
e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3];
e2[-2] = (k00_20) * A[0] - (k01_21) * A[1];
e2[-3] = (k01_21) * A[0] + (k00_20) * A[1];
A += k1;
k00_20 = e0[-4] - e2[-4];
k01_21 = e0[-5] - e2[-5];
e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4];
e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5];
e2[-4] = (k00_20) * A[0] - (k01_21) * A[1];
e2[-5] = (k01_21) * A[0] + (k00_20) * A[1];
A += k1;
k00_20 = e0[-6] - e2[-6];
k01_21 = e0[-7] - e2[-7];
e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6];
e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7];
e2[-6] = (k00_20) * A[0] - (k01_21) * A[1];
e2[-7] = (k01_21) * A[0] + (k00_20) * A[1];
e0 -= 8;
e2 -= 8;
A += k1;
}
}
static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) {
int i;
float A0 = A[0];
float A1 = A[0 + 1];
float A2 = A[0 + a_off];
float A3 = A[0 + a_off + 1];
float A4 = A[0 + a_off * 2 + 0];
float A5 = A[0 + a_off * 2 + 1];
float A6 = A[0 + a_off * 3 + 0];
float A7 = A[0 + a_off * 3 + 1];
float k00, k11;
float *ee0 = e + i_off;
float *ee2 = ee0 + k_off;
for (i = n; i > 0; --i) {
k00 = ee0[0] - ee2[0];
k11 = ee0[-1] - ee2[-1];
ee0[0] = ee0[0] + ee2[0];
ee0[-1] = ee0[-1] + ee2[-1];
ee2[0] = (k00) * A0 - (k11) * A1;
ee2[-1] = (k11) * A0 + (k00) * A1;
k00 = ee0[-2] - ee2[-2];
k11 = ee0[-3] - ee2[-3];
ee0[-2] = ee0[-2] + ee2[-2];
ee0[-3] = ee0[-3] + ee2[-3];
ee2[-2] = (k00) * A2 - (k11) * A3;
ee2[-3] = (k11) * A2 + (k00) * A3;
k00 = ee0[-4] - ee2[-4];
k11 = ee0[-5] - ee2[-5];
ee0[-4] = ee0[-4] + ee2[-4];
ee0[-5] = ee0[-5] + ee2[-5];
ee2[-4] = (k00) * A4 - (k11) * A5;
ee2[-5] = (k11) * A4 + (k00) * A5;
k00 = ee0[-6] - ee2[-6];
k11 = ee0[-7] - ee2[-7];
ee0[-6] = ee0[-6] + ee2[-6];
ee0[-7] = ee0[-7] + ee2[-7];
ee2[-6] = (k00) * A6 - (k11) * A7;
ee2[-7] = (k11) * A6 + (k00) * A7;
ee0 -= k0;
ee2 -= k0;
}
}
static __forceinline void iter_54(float *z) {
float k00, k11, k22, k33;
float y0, y1, y2, y3;
k00 = z[0] - z[-4];
y0 = z[0] + z[-4];
y2 = z[-2] + z[-6];
k22 = z[-2] - z[-6];
z[-0] = y0 + y2; // z0 + z4 + z2 + z6
z[-2] = y0 - y2; // z0 + z4 - z2 - z6
// done with y0,y2
k33 = z[-3] - z[-7];
z[-4] = k00 + k33; // z0 - z4 + z3 - z7
z[-6] = k00 - k33; // z0 - z4 - z3 + z7
// done with k33
k11 = z[-1] - z[-5];
y1 = z[-1] + z[-5];
y3 = z[-3] + z[-7];
z[-1] = y1 + y3; // z1 + z5 + z3 + z7
z[-3] = y1 - y3; // z1 + z5 - z3 - z7
z[-5] = k11 - k22; // z1 - z5 + z2 - z6
z[-7] = k11 + k22; // z1 - z5 - z2 + z6
}
static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) {
int a_off = base_n >> 3;
float A2 = A[0 + a_off];
float *z = e + i_off;
float *base = z - 16 * n;
while (z > base) {
float k00, k11;
float l00, l11;
k00 = z[-0] - z[-8];
k11 = z[-1] - z[-9];
l00 = z[-2] - z[-10];
l11 = z[-3] - z[-11];
z[-0] = z[-0] + z[-8];
z[-1] = z[-1] + z[-9];
z[-2] = z[-2] + z[-10];
z[-3] = z[-3] + z[-11];
z[-8] = k00;
z[-9] = k11;
z[-10] = (l00 + l11) * A2;
z[-11] = (l11 - l00) * A2;
k00 = z[-4] - z[-12];
k11 = z[-5] - z[-13];
l00 = z[-6] - z[-14];
l11 = z[-7] - z[-15];
z[-4] = z[-4] + z[-12];
z[-5] = z[-5] + z[-13];
z[-6] = z[-6] + z[-14];
z[-7] = z[-7] + z[-15];
z[-12] = k11;
z[-13] = -k00;
z[-14] = (l11 - l00) * A2;
z[-15] = (l00 + l11) * -A2;
iter_54(z);
iter_54(z - 8);
z -= 16;
}
}
static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) {
int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l;
int ld;
// @OPTIMIZE: reduce register pressure by using fewer variables?
int save_point = temp_alloc_save(f);
float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2));
float *u = NULL, *v = NULL;
// twiddle factors
float *A = f->A[blocktype];
// IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio"
// See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function.
// kernel from paper
// merged:
// copy and reflect spectral data
// step 0
// note that it turns out that the items added together during
// this step are, in fact, being added to themselves (as reflected
// by step 0). inexplicable inefficiency! this became obvious
// once I combined the passes.
// so there's a missing 'times 2' here (for adding X to itself).
// this propagates through linearly to the end, where the numbers
// are 1/2 too small, and need to be compensated for.
{
float *d, *e, *AA, *e_stop;
d = &buf2[n2 - 2];
AA = A;
e = &buffer[0];
e_stop = &buffer[n2];
while (e != e_stop) {
d[1] = (e[0] * AA[0] - e[2] * AA[1]);
d[0] = (e[0] * AA[1] + e[2] * AA[0]);
d -= 2;
AA += 2;
e += 4;
}
e = &buffer[n2 - 3];
while (d >= buf2) {
d[1] = (-e[2] * AA[0] - -e[0] * AA[1]);
d[0] = (-e[2] * AA[1] + -e[0] * AA[0]);
d -= 2;
AA += 2;
e -= 4;
}
}
// now we use symbolic names for these, so that we can
// possibly swap their meaning as we change which operations
// are in place
u = buffer;
v = buf2;
// step 2 (paper output is w, now u)
// this could be in place, but the data ends up in the wrong
// place... _somebody_'s got to swap it, so this is nominated
{
float *AA = &A[n2 - 8];
float *d0, *d1, *e0, *e1;
e0 = &v[n4];
e1 = &v[0];
d0 = &u[n4];
d1 = &u[0];
while (AA >= A) {
float v40_20, v41_21;
v41_21 = e0[1] - e1[1];
v40_20 = e0[0] - e1[0];
d0[1] = e0[1] + e1[1];
d0[0] = e0[0] + e1[0];
d1[1] = v41_21 * AA[4] - v40_20 * AA[5];
d1[0] = v40_20 * AA[4] + v41_21 * AA[5];
v41_21 = e0[3] - e1[3];
v40_20 = e0[2] - e1[2];
d0[3] = e0[3] + e1[3];
d0[2] = e0[2] + e1[2];
d1[3] = v41_21 * AA[0] - v40_20 * AA[1];
d1[2] = v40_20 * AA[0] + v41_21 * AA[1];
AA -= 8;
d0 += 4;
d1 += 4;
e0 += 4;
e1 += 4;
}
}
// step 3
ld = ilog(n) - 1; // ilog is off-by-one from normal definitions
// optimized step 3:
// the original step3 loop can be nested r inside s or s inside r;
// it's written originally as s inside r, but this is dumb when r
// iterates many times, and s few. So I have two copies of it and
// switch between them halfway.
// this is iteration 0 of step 3
imdct_step3_iter0_loop(n >> 4, u, n2 - 1 - n4 * 0, -(n >> 3), A);
imdct_step3_iter0_loop(n >> 4, u, n2 - 1 - n4 * 1, -(n >> 3), A);
// this is iteration 1 of step 3
imdct_step3_inner_r_loop(n >> 5, u, n2 - 1 - n8 * 0, -(n >> 4), A, 16);
imdct_step3_inner_r_loop(n >> 5, u, n2 - 1 - n8 * 1, -(n >> 4), A, 16);
imdct_step3_inner_r_loop(n >> 5, u, n2 - 1 - n8 * 2, -(n >> 4), A, 16);
imdct_step3_inner_r_loop(n >> 5, u, n2 - 1 - n8 * 3, -(n >> 4), A, 16);
l = 2;
for (; l < (ld - 3) >> 1; ++l) {
int k0 = n >> (l + 2), k0_2 = k0 >> 1;
int lim = 1 << (l + 1);
int i;
for (i = 0; i < lim; ++i)
imdct_step3_inner_r_loop(n >> (l + 4), u, n2 - 1 - k0 * i, -k0_2, A, 1 << (l + 3));
}
for (; l < ld - 6; ++l) {
int k0 = n >> (l + 2), k1 = 1 << (l + 3), k0_2 = k0 >> 1;
int rlim = n >> (l + 6), r;
int lim = 1 << (l + 1);
int i_off;
float *A0 = A;
i_off = n2 - 1;
for (r = rlim; r > 0; --r) {
imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0);
A0 += k1 * 4;
i_off -= 8;
}
}
// iterations with count:
// ld-6,-5,-4 all interleaved together
// the big win comes from getting rid of needless flops
// due to the constants on pass 5 & 4 being all 1 and 0;
// combining them to be simultaneous to improve cache made little difference
imdct_step3_inner_s_loop_ld654(n >> 5, u, n2 - 1, A, n);
// output is u
// step 4, 5, and 6
// cannot be in-place because of step 5
{
uint16 *bitrev = f->bit_reverse[blocktype];
// weirdly, I'd have thought reading sequentially and writing
// erratically would have been better than vice-versa, but in
// fact that's not what my testing showed. (That is, with
// j = bitreverse(i), do you read i and write j, or read j and write i.)
float *d0 = &v[n4 - 4];
float *d1 = &v[n2 - 4];
while (d0 >= v) {
int k4;
k4 = bitrev[0];
d1[3] = u[k4 + 0];
d1[2] = u[k4 + 1];
d0[3] = u[k4 + 2];
d0[2] = u[k4 + 3];
k4 = bitrev[1];
d1[1] = u[k4 + 0];
d1[0] = u[k4 + 1];
d0[1] = u[k4 + 2];
d0[0] = u[k4 + 3];
d0 -= 4;
d1 -= 4;
bitrev += 2;
}
}
// (paper output is u, now v)
// data must be in buf2
assert(v == buf2);
// step 7 (paper output is v, now v)
// this is now in place
{
float *C = f->C[blocktype];
float *d, *e;
d = v;
e = v + n2 - 4;
while (d < e) {
float a02, a11, b0, b1, b2, b3;
a02 = d[0] - e[2];
a11 = d[1] + e[3];
b0 = C[1] * a02 + C[0] * a11;
b1 = C[1] * a11 - C[0] * a02;
b2 = d[0] + e[2];
b3 = d[1] - e[3];
d[0] = b2 + b0;
d[1] = b3 + b1;
e[2] = b2 - b0;
e[3] = b1 - b3;
a02 = d[2] - e[0];
a11 = d[3] + e[1];
b0 = C[3] * a02 + C[2] * a11;
b1 = C[3] * a11 - C[2] * a02;
b2 = d[2] + e[0];
b3 = d[3] - e[1];
d[2] = b2 + b0;
d[3] = b3 + b1;
e[0] = b2 - b0;
e[1] = b1 - b3;
C += 4;
d += 4;
e -= 4;
}
}
// data must be in buf2
// step 8+decode (paper output is X, now buffer)
// this generates pairs of data a la 8 and pushes them directly through
// the decode kernel (pushing rather than pulling) to avoid having
// to make another pass later
// this cannot POSSIBLY be in place, so we refer to the buffers directly
{
float *d0, *d1, *d2, *d3;
float *B = f->B[blocktype] + n2 - 8;
float *e = buf2 + n2 - 8;
d0 = &buffer[0];
d1 = &buffer[n2 - 4];
d2 = &buffer[n2];
d3 = &buffer[n - 4];
while (e >= v) {
float p0, p1, p2, p3;
p3 = e[6] * B[7] - e[7] * B[6];
p2 = -e[6] * B[6] - e[7] * B[7];
d0[0] = p3;
d1[3] = -p3;
d2[0] = p2;
d3[3] = p2;
p1 = e[4] * B[5] - e[5] * B[4];
p0 = -e[4] * B[4] - e[5] * B[5];
d0[1] = p1;
d1[2] = -p1;
d2[1] = p0;
d3[2] = p0;
p3 = e[2] * B[3] - e[3] * B[2];
p2 = -e[2] * B[2] - e[3] * B[3];
d0[2] = p3;
d1[1] = -p3;
d2[2] = p2;
d3[1] = p2;
p1 = e[0] * B[1] - e[1] * B[0];
p0 = -e[0] * B[0] - e[1] * B[1];
d0[3] = p1;
d1[0] = -p1;
d2[3] = p0;
d3[0] = p0;
B -= 8;
e -= 8;
d0 += 4;
d2 += 4;
d1 -= 4;
d3 -= 4;
}
}
temp_free(f, buf2);
temp_alloc_restore(f, save_point);
}
#if 0
// this is the original version of the above code, if you want to optimize it from scratch
void inverse_mdct_naive(float *buffer, int n)
{
float s;
float A[1 << 12], B[1 << 12], C[1 << 11];
int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l;
int n3_4 = n - n4, ld;
// how can they claim this only uses N words?!
// oh, because they're only used sparsely, whoops
float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13];
// set up twiddle factors
for (k=k2=0; k < n4; ++k,k2+=2) {
A[k2 ] = (float) cos(4*k*M_PI/n);
A[k2+1] = (float) -sin(4*k*M_PI/n);
B[k2 ] = (float) cos((k2+1)*M_PI/n/2);
B[k2+1] = (float) sin((k2+1)*M_PI/n/2);
}
for (k=k2=0; k < n8; ++k,k2+=2) {
C[k2 ] = (float) cos(2*(k2+1)*M_PI/n);
C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n);
}
// IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio"
// Note there are bugs in that pseudocode, presumably due to them attempting
// to rename the arrays nicely rather than representing the way their actual
// implementation bounces buffers back and forth. As a result, even in the
// "some formulars corrected" version, a direct implementation fails. These
// are noted below as "paper bug".
// copy and reflect spectral data
for (k=0; k < n2; ++k) u[k] = buffer[k];
for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1];
// kernel from paper
// step 1
for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) {
v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1];
v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2];
}
// step 2
for (k=k4=0; k < n8; k+=1, k4+=4) {
w[n2+3+k4] = v[n2+3+k4] + v[k4+3];
w[n2+1+k4] = v[n2+1+k4] + v[k4+1];
w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4];
w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4];
}
// step 3
ld = ilog(n) - 1; // ilog is off-by-one from normal definitions
for (l=0; l < ld-3; ++l) {
int k0 = n >> (l+2), k1 = 1 << (l+3);
int rlim = n >> (l+4), r4, r;
int s2lim = 1 << (l+2), s2;
for (r=r4=0; r < rlim; r4+=4,++r) {
for (s2=0; s2 < s2lim; s2+=2) {
u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4];
u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4];
u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1]
- (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1];
u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1]
+ (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1];
}
}
if (l+1 < ld-3) {
// paper bug: ping-ponging of u&w here is omitted
memcpy(w, u, sizeof(u));
}
}
// step 4
for (i=0; i < n8; ++i) {
int j = bit_reverse(i) >> (32-ld+3);
assert(j < n8);
if (i == j) {
// paper bug: original code probably swapped in place; if copying,
// need to directly copy in this case
int i8 = i << 3;
v[i8+1] = u[i8+1];
v[i8+3] = u[i8+3];
v[i8+5] = u[i8+5];
v[i8+7] = u[i8+7];
} else if (i < j) {
int i8 = i << 3, j8 = j << 3;
v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1];
v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3];
v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5];
v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7];
}
}
// step 5
for (k=0; k < n2; ++k) {
w[k] = v[k*2+1];
}
// step 6
for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) {
u[n-1-k2] = w[k4];
u[n-2-k2] = w[k4+1];
u[n3_4 - 1 - k2] = w[k4+2];
u[n3_4 - 2 - k2] = w[k4+3];
}
// step 7
for (k=k2=0; k < n8; ++k, k2 += 2) {
v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2;
v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2;
v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2;
v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2;
}
// step 8
for (k=k2=0; k < n4; ++k,k2 += 2) {
X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1];
X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ];
}
// decode kernel to output
// determined the following value experimentally
// (by first figuring out what made inverse_mdct_slow work); then matching that here
// (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?)
s = 0.5; // theoretically would be n4
// [[[ note! the s value of 0.5 is compensated for by the B[] in the current code,
// so it needs to use the "old" B values to behave correctly, or else
// set s to 1.0 ]]]
for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4];
for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1];
for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4];
}
#endif
static float *get_window(vorb *f, int len) {
len <<= 1;
if (len == f->blocksize_0)
return f->window[0];
if (len == f->blocksize_1)
return f->window[1];
return NULL;
}
#ifndef STB_VORBIS_NO_DEFER_FLOOR
typedef int16 YTYPE;
#else
typedef int YTYPE;
#endif
static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) {
int n2 = n >> 1;
int s = map->chan[i].mux, floor;
floor = map->submap_floor[s];
if (f->floor_types[floor] == 0) {
return error(f, VORBIS_invalid_stream);
} else {
Floor1 *g = &f->floor_config[floor].floor1;
int j, q;
int lx = 0, ly = finalY[0] * g->floor1_multiplier;
for (q = 1; q < g->values; ++q) {
j = g->sorted_order[q];
#ifndef STB_VORBIS_NO_DEFER_FLOOR
STBV_NOTUSED(step2_flag);
if (finalY[j] >= 0)
#else
if (step2_flag[j])
#endif
{
int hy = finalY[j] * g->floor1_multiplier;
int hx = g->Xlist[j];
if (lx != hx)
draw_line(target, lx, ly, hx, hy, n2);
CHECK(f);
lx = hx, ly = hy;
}
}
if (lx < n2) {
// optimization of: draw_line(target, lx,ly, n,ly, n2);
for (j = lx; j < n2; ++j)
LINE_OP(target[j], inverse_db_table[ly]);
CHECK(f);
}
}
return TRUE;
}
// The meaning of "left" and "right"
//
// For a given frame:
// we compute samples from 0..n
// window_center is n/2
// we'll window and mix the samples from left_start to left_end with data from the previous frame
// all of the samples from left_end to right_start can be output without mixing; however,
// this interval is 0-length except when transitioning between short and long frames
// all of the samples from right_start to right_end need to be mixed with the next frame,
// which we don't have, so those get saved in a buffer
// frame N's right_end-right_start, the number of samples to mix with the next frame,
// has to be the same as frame N+1's left_end-left_start (which they are by
// construction)
static int vorbis_decode_initial(vorb *f,
int *p_left_start,
int *p_left_end,
int *p_right_start,
int *p_right_end,
int *mode) {
Mode *m;
int i, n, prev, next, window_center;
f->channel_buffer_start = f->channel_buffer_end = 0;
retry:
if (f->eof)
return FALSE;
if (!maybe_start_packet(f))
return FALSE;
// check packet type
if (get_bits(f, 1) != 0) {
if (IS_PUSH_MODE(f))
return error(f, VORBIS_bad_packet_type);
while (EOP != get8_packet(f));
goto retry;
}
if (f->alloc.alloc_buffer)
assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset);
i = get_bits(f, ilog(f->mode_count - 1));
if (i == EOP)
return FALSE;
if (i >= f->mode_count)
return FALSE;
*mode = i;
m = f->mode_config + i;
if (m->blockflag) {
n = f->blocksize_1;
prev = get_bits(f, 1);
next = get_bits(f, 1);
} else {
prev = next = 0;
n = f->blocksize_0;
}
// WINDOWING
window_center = n >> 1;
if (m->blockflag && !prev) {
*p_left_start = (n - f->blocksize_0) >> 2;
*p_left_end = (n + f->blocksize_0) >> 2;
} else {
*p_left_start = 0;
*p_left_end = window_center;
}
if (m->blockflag && !next) {
*p_right_start = (n * 3 - f->blocksize_0) >> 2;
*p_right_end = (n * 3 + f->blocksize_0) >> 2;
} else {
*p_right_start = window_center;
*p_right_end = n;
}
return TRUE;
}
static int vorbis_decode_packet_rest(vorb *f,
int *len,
Mode *m,
int left_start,
int left_end,
int right_start,
int right_end,
int *p_left) {
Mapping *map;
int i, j, k, n, n2;
int zero_channel[256];
int really_zero_channel[256];
// WINDOWING
STBV_NOTUSED(left_end);
n = f->blocksize[m->blockflag];
map = &f->mapping[m->mapping];
// FLOORS
n2 = n >> 1;
CHECK(f);
for (i = 0; i < f->channels; ++i) {
int s = map->chan[i].mux, floor;
zero_channel[i] = FALSE;
floor = map->submap_floor[s];
if (f->floor_types[floor] == 0) {
return error(f, VORBIS_invalid_stream);
} else {
Floor1 *g = &f->floor_config[floor].floor1;
if (get_bits(f, 1)) {
short *finalY;
uint8 step2_flag[256];
static int range_list[4] = {256, 128, 86, 64};
int range = range_list[g->floor1_multiplier - 1];
int offset = 2;
finalY = f->finalY[i];
finalY[0] = get_bits(f, ilog(range) - 1);
finalY[1] = get_bits(f, ilog(range) - 1);
for (j = 0; j < g->partitions; ++j) {
int pclass = g->partition_class_list[j];
int cdim = g->class_dimensions[pclass];
int cbits = g->class_subclasses[pclass];
int csub = (1 << cbits) - 1;
int cval = 0;
if (cbits) {
Codebook *c = f->codebooks + g->class_masterbooks[pclass];
DECODE(cval, f, c);
}
for (k = 0; k < cdim; ++k) {
int book = g->subclass_books[pclass][cval & csub];
cval = cval >> cbits;
if (book >= 0) {
int temp;
Codebook *c = f->codebooks + book;
DECODE(temp, f, c);
finalY[offset++] = temp;
} else
finalY[offset++] = 0;
}
}
if (f->valid_bits == INVALID_BITS)
goto error; // behavior according to spec
step2_flag[0] = step2_flag[1] = 1;
for (j = 2; j < g->values; ++j) {
int low, high, pred, highroom, lowroom, room, val;
low = g->neighbors[j][0];
high = g->neighbors[j][1];
//neighbors(g->Xlist, j, &low, &high);
pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]);
val = finalY[j];
highroom = range - pred;
lowroom = pred;
if (highroom < lowroom)
room = highroom * 2;
else
room = lowroom * 2;
if (val) {
step2_flag[low] = step2_flag[high] = 1;
step2_flag[j] = 1;
if (val >= room)
if (highroom > lowroom)
finalY[j] = val - lowroom + pred;
else
finalY[j] = pred - val + highroom - 1;
else if (val & 1)
finalY[j] = pred - ((val + 1) >> 1);
else
finalY[j] = pred + (val >> 1);
} else {
step2_flag[j] = 0;
finalY[j] = pred;
}
}
#ifdef STB_VORBIS_NO_DEFER_FLOOR
do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag);
#else
// defer final floor computation until _after_ residue
for (j = 0; j < g->values; ++j) {
if (!step2_flag[j])
finalY[j] = -1;
}
#endif
} else {
error:
zero_channel[i] = TRUE;
}
// So we just defer everything else to later
// at this point we've decoded the floor into buffer
}
}
CHECK(f);
// at this point we've decoded all floors
if (f->alloc.alloc_buffer)
assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset);
// re-enable coupled channels if necessary
memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels);
for (i = 0; i < map->coupling_steps; ++i)
if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) {
zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE;
}
CHECK(f);
// RESIDUE DECODE
for (i = 0; i < map->submaps; ++i) {
float *residue_buffers[STB_VORBIS_MAX_CHANNELS];
int r;
uint8 do_not_decode[256];
int ch = 0;
for (j = 0; j < f->channels; ++j) {
if (map->chan[j].mux == i) {
if (zero_channel[j]) {
do_not_decode[ch] = TRUE;
residue_buffers[ch] = NULL;
} else {
do_not_decode[ch] = FALSE;
residue_buffers[ch] = f->channel_buffers[j];
}
++ch;
}
}
r = map->submap_residue[i];
decode_residue(f, residue_buffers, ch, n2, r, do_not_decode);
}
if (f->alloc.alloc_buffer)
assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset);
CHECK(f);
// INVERSE COUPLING
for (i = map->coupling_steps - 1; i >= 0; --i) {
int n2 = n >> 1;
float *m = f->channel_buffers[map->chan[i].magnitude];
float *a = f->channel_buffers[map->chan[i].angle];
for (j = 0; j < n2; ++j) {
float a2, m2;
if (m[j] > 0)
if (a[j] > 0)
m2 = m[j], a2 = m[j] - a[j];
else
a2 = m[j], m2 = m[j] + a[j];
else if (a[j] > 0)
m2 = m[j], a2 = m[j] + a[j];
else
a2 = m[j], m2 = m[j] - a[j];
m[j] = m2;
a[j] = a2;
}
}
CHECK(f);
// finish decoding the floors
#ifndef STB_VORBIS_NO_DEFER_FLOOR
for (i = 0; i < f->channels; ++i) {
if (really_zero_channel[i]) {
memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2);
} else {
do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL);
}
}
#else
for (i=0; i < f->channels; ++i) {
if (really_zero_channel[i]) {
memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2);
} else {
for (j=0; j < n2; ++j)
f->channel_buffers[i][j] *= f->floor_buffers[i][j];
}
}
#endif
// INVERSE MDCT
CHECK(f);
for (i = 0; i < f->channels; ++i)
inverse_mdct(f->channel_buffers[i], n, f, m->blockflag);
CHECK(f);
// this shouldn't be necessary, unless we exited on an error
// and want to flush to get to the next packet
flush_packet(f);
if (f->first_decode) {
// assume we start so first non-discarded sample is sample 0
// this isn't to spec, but spec would require us to read ahead
// and decode the size of all current frames--could be done,
// but presumably it's not a commonly used feature
f->current_loc = 0u
- n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around)
// we might have to discard samples "from" the next frame too,
// if we're lapping a large block then a small at the start?
f->discard_samples_deferred = n - right_end;
f->current_loc_valid = TRUE;
f->first_decode = FALSE;
} else if (f->discard_samples_deferred) {
if (f->discard_samples_deferred >= right_start - left_start) {
f->discard_samples_deferred -= (right_start - left_start);
left_start = right_start;
*p_left = left_start;
} else {
left_start += f->discard_samples_deferred;
*p_left = left_start;
f->discard_samples_deferred = 0;
}
} else if (f->previous_length == 0 && f->current_loc_valid) {
// we're recovering from a seek... that means we're going to discard
// the samples from this packet even though we know our position from
// the last page header, so we need to update the position based on
// the discarded samples here
// but wait, the code below is going to add this in itself even
// on a discard, so we don't need to do it here...
}
// check if we have ogg information about the sample # for this packet
if (f->last_seg_which == f->end_seg_with_known_loc) {
// if we have a valid current loc, and this is final:
if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) {
uint32 current_end = f->known_loc_for_packet;
// then let's infer the size of the (probably) short final frame
if (current_end < f->current_loc + (right_end - left_start)) {
if (current_end < f->current_loc) {
// negative truncation, that's impossible!
*len = 0;
} else {
*len = current_end - f->current_loc;
}
*len += left_start; // this doesn't seem right, but has no ill effect on my test files
if (*len > right_end)
*len = right_end; // this should never happen
f->current_loc += *len;
return TRUE;
}
}
// otherwise, just set our sample loc
// guess that the ogg granule pos refers to the _middle_ of the
// last frame?
// set f->current_loc to the position of left_start
f->current_loc = f->known_loc_for_packet - (n2 - left_start);
f->current_loc_valid = TRUE;
}
if (f->current_loc_valid)
f->current_loc += (right_start - left_start);
if (f->alloc.alloc_buffer)
assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset);
*len = right_end; // ignore samples after the window goes to 0
CHECK(f);
return TRUE;
}
static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) {
int mode, left_end, right_end;
if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode))
return 0;
return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left);
}
static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) {
int prev, i, j;
// we use right&left (the start of the right- and left-window sin()-regions)
// to determine how much to return, rather than inferring from the rules
// (same result, clearer code); 'left' indicates where our sin() window
// starts, therefore where the previous window's right edge starts, and
// therefore where to start mixing from the previous buffer. 'right'
// indicates where our sin() ending-window starts, therefore that's where
// we start saving, and where our returned-data ends.
// mixin from previous window
if (f->previous_length) {
int i, j, n = f->previous_length;
float *w = get_window(f, n);
if (w == NULL)
return 0;
for (i = 0; i < f->channels; ++i) {
for (j = 0; j < n; ++j)
f->channel_buffers[i][left + j] =
f->channel_buffers[i][left + j] * w[j] +
f->previous_window[i][j] * w[n - 1 - j];
}
}
prev = f->previous_length;
// last half of this data becomes previous window
f->previous_length = len - right;
// @OPTIMIZE: could avoid this copy by double-buffering the
// output (flipping previous_window with channel_buffers), but
// then previous_window would have to be 2x as large, and
// channel_buffers couldn't be temp mem (although they're NOT
// currently temp mem, they could be (unless we want to level
// performance by spreading out the computation))
for (i = 0; i < f->channels; ++i)
for (j = 0; right + j < len; ++j)
f->previous_window[i][j] = f->channel_buffers[i][right + j];
if (!prev)
// there was no previous packet, so this data isn't valid...
// this isn't entirely true, only the would-have-overlapped data
// isn't valid, but this seems to be what the spec requires
return 0;
// truncate a short frame
if (len < right)
right = len;
f->samples_output += right - left;
return right - left;
}
static int vorbis_pump_first_frame(stb_vorbis *f) {
int len, right, left, res;
res = vorbis_decode_packet(f, &len, &left, &right);
if (res)
vorbis_finish_frame(f, len, left, right);
return res;
}
#ifndef STB_VORBIS_NO_PUSHDATA_API
static int is_whole_packet_present(stb_vorbis *f) {
// make sure that we have the packet available before continuing...
// this requires a full ogg parse, but we know we can fetch from f->stream
// instead of coding this out explicitly, we could save the current read state,
// read the next packet with get8() until end-of-packet, check f->eof, then
// reset the state? but that would be slower, esp. since we'd have over 256 bytes
// of state to restore (primarily the page segment table)
int s = f->next_seg, first = TRUE;
uint8 *p = f->stream;
if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag
for (; s < f->segment_count; ++s) {
p += f->segments[s];
if (f->segments[s] < 255) // stop at first short segment
break;
}
// either this continues, or it ends it...
if (s == f->segment_count)
s = -1; // set 'crosses page' flag
if (p > f->stream_end)
return error(f, VORBIS_need_more_data);
first = FALSE;
}
for (; s == -1;) {
uint8 *q;
int n;
// check that we have the page header ready
if (p + 26 >= f->stream_end)
return error(f, VORBIS_need_more_data);
// validate the page
if (memcmp(p, ogg_page_header, 4))
return error(f, VORBIS_invalid_stream);
if (p[4] != 0)
return error(f, VORBIS_invalid_stream);
if (first) { // the first segment must NOT have 'continued_packet', later ones MUST
if (f->previous_length)
if ((p[5] & PAGEFLAG_continued_packet))
return error(f, VORBIS_invalid_stream);
// if no previous length, we're resynching, so we can come in on a continued-packet,
// which we'll just drop
} else {
if (!(p[5] & PAGEFLAG_continued_packet))
return error(f, VORBIS_invalid_stream);
}
n = p[26]; // segment counts
q = p + 27; // q points to segment table
p = q + n; // advance past header
// make sure we've read the segment table
if (p > f->stream_end)
return error(f, VORBIS_need_more_data);
for (s = 0; s < n; ++s) {
p += q[s];
if (q[s] < 255)
break;
}
if (s == n)
s = -1; // set 'crosses page' flag
if (p > f->stream_end)
return error(f, VORBIS_need_more_data);
first = FALSE;
}
return TRUE;
}
#endif // !STB_VORBIS_NO_PUSHDATA_API
static int start_decoder(vorb *f) {
uint8 header[6], x, y;
int len, i, j, k, max_submaps = 0;
int longest_floorlist = 0;
// first page, first packet
f->first_decode = TRUE;
if (!start_page(f))
return FALSE;
// validate page flag
if (!(f->page_flag & PAGEFLAG_first_page))
return error(f, VORBIS_invalid_first_page);
if (f->page_flag & PAGEFLAG_last_page)
return error(f, VORBIS_invalid_first_page);
if (f->page_flag & PAGEFLAG_continued_packet)
return error(f, VORBIS_invalid_first_page);
// check for expected packet length
if (f->segment_count != 1)
return error(f, VORBIS_invalid_first_page);
if (f->segments[0] != 30) {
// check for the Ogg skeleton fishead identifying header to refine our error
if (f->segments[0] == 64 &&
getn(f, header, 6) &&
header[0] == 'f' &&
header[1] == 'i' &&
header[2] == 's' &&
header[3] == 'h' &&
header[4] == 'e' &&
header[5] == 'a' &&
get8(f) == 'd' &&
get8(f) == '\0')
return error(f, VORBIS_ogg_skeleton_not_supported);
else
return error(f, VORBIS_invalid_first_page);
}
// read packet
// check packet header
if (get8(f) != VORBIS_packet_id)
return error(f, VORBIS_invalid_first_page);
if (!getn(f, header, 6))
return error(f, VORBIS_unexpected_eof);
if (!vorbis_validate(header))
return error(f, VORBIS_invalid_first_page);
// vorbis_version
if (get32(f) != 0)
return error(f, VORBIS_invalid_first_page);
f->channels = get8(f);
if (!f->channels)
return error(f, VORBIS_invalid_first_page);
if (f->channels > STB_VORBIS_MAX_CHANNELS)
return error(f, VORBIS_too_many_channels);
f->sample_rate = get32(f);
if (!f->sample_rate)
return error(f, VORBIS_invalid_first_page);
get32(f); // bitrate_maximum
get32(f); // bitrate_nominal
get32(f); // bitrate_minimum
x = get8(f);
{
int log0, log1;
log0 = x & 15;
log1 = x >> 4;
f->blocksize_0 = 1 << log0;
f->blocksize_1 = 1 << log1;
if (log0 < 6 || log0 > 13)
return error(f, VORBIS_invalid_setup);
if (log1 < 6 || log1 > 13)
return error(f, VORBIS_invalid_setup);
if (log0 > log1)
return error(f, VORBIS_invalid_setup);
}
// framing_flag
x = get8(f);
if (!(x & 1))
return error(f, VORBIS_invalid_first_page);
// second packet!
if (!start_page(f))
return FALSE;
if (!start_packet(f))
return FALSE;
if (!next_segment(f))
return FALSE;
if (get8_packet(f) != VORBIS_packet_comment)
return error(f, VORBIS_invalid_setup);
for (i = 0; i < 6; ++i)
header[i] = get8_packet(f);
if (!vorbis_validate(header))
return error(f, VORBIS_invalid_setup);
//file vendor
len = get32_packet(f);
f->vendor = (char *) setup_malloc(f, sizeof(char) * (len + 1));
if (f->vendor == NULL)
return error(f, VORBIS_outofmem);
for (i = 0; i < len; ++i) {
f->vendor[i] = get8_packet(f);
}
f->vendor[len] = (char) '\0';
//user comments
f->comment_list_length = get32_packet(f);
f->comment_list = NULL;
if (f->comment_list_length > 0) {
f->comment_list = (char **) setup_malloc(f, sizeof(char *) * (f->comment_list_length));
if (f->comment_list == NULL)
return error(f, VORBIS_outofmem);
}
for (i = 0; i < f->comment_list_length; ++i) {
len = get32_packet(f);
f->comment_list[i] = (char *) setup_malloc(f, sizeof(char) * (len + 1));
if (f->comment_list[i] == NULL)
return error(f, VORBIS_outofmem);
for (j = 0; j < len; ++j) {
f->comment_list[i][j] = get8_packet(f);
}
f->comment_list[i][len] = (char) '\0';
}
// framing_flag
x = get8_packet(f);
if (!(x & 1))
return error(f, VORBIS_invalid_setup);
skip(f, f->bytes_in_seg);
f->bytes_in_seg = 0;
do {
len = next_segment(f);
skip(f, len);
f->bytes_in_seg = 0;
} while (len);
// third packet!
if (!start_packet(f))
return FALSE;
#ifndef STB_VORBIS_NO_PUSHDATA_API
if (IS_PUSH_MODE(f)) {
if (!is_whole_packet_present(f)) {
// convert error in ogg header to write type
if (f->error == VORBIS_invalid_stream)
f->error = VORBIS_invalid_setup;
return FALSE;
}
}
#endif
crc32_init(); // always init it, to avoid multithread race conditions
if (get8_packet(f) != VORBIS_packet_setup)
return error(f, VORBIS_invalid_setup);
for (i = 0; i < 6; ++i)
header[i] = get8_packet(f);
if (!vorbis_validate(header))
return error(f, VORBIS_invalid_setup);
// codebooks
f->codebook_count = get_bits(f, 8) + 1;
f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count);
if (f->codebooks == NULL)
return error(f, VORBIS_outofmem);
memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count);
for (i = 0; i < f->codebook_count; ++i) {
uint32 *values;
int ordered, sorted_count;
int total = 0;
uint8 *lengths;
Codebook *c = f->codebooks + i;
CHECK(f);
x = get_bits(f, 8);
if (x != 0x42)
return error(f, VORBIS_invalid_setup);
x = get_bits(f, 8);
if (x != 0x43)
return error(f, VORBIS_invalid_setup);
x = get_bits(f, 8);
if (x != 0x56)
return error(f, VORBIS_invalid_setup);
x = get_bits(f, 8);
c->dimensions = (get_bits(f, 8) << 8) + x;
x = get_bits(f, 8);
y = get_bits(f, 8);
c->entries = (get_bits(f, 8) << 16) + (y << 8) + x;
ordered = get_bits(f, 1);
c->sparse = ordered ? 0 : get_bits(f, 1);
if (c->dimensions == 0 && c->entries != 0)
return error(f, VORBIS_invalid_setup);
if (c->sparse)
lengths = (uint8 *) setup_temp_malloc(f, c->entries);
else
lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries);
if (!lengths)
return error(f, VORBIS_outofmem);
if (ordered) {
int current_entry = 0;
int current_length = get_bits(f, 5) + 1;
while (current_entry < c->entries) {
int limit = c->entries - current_entry;
int n = get_bits(f, ilog(limit));
if (current_length >= 32)
return error(f, VORBIS_invalid_setup);
if (current_entry + n > (int) c->entries) {
return error(f, VORBIS_invalid_setup);
}
memset(lengths + current_entry, current_length, n);
current_entry += n;
++current_length;
}
} else {
for (j = 0; j < c->entries; ++j) {
int present = c->sparse ? get_bits(f, 1) : 1;
if (present) {
lengths[j] = get_bits(f, 5) + 1;
++total;
if (lengths[j] == 32)
return error(f, VORBIS_invalid_setup);
} else {
lengths[j] = NO_CODE;
}
}
}
if (c->sparse && total >= c->entries >> 2) {
// convert sparse items to non-sparse!
if (c->entries > (int) f->setup_temp_memory_required)
f->setup_temp_memory_required = c->entries;
c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries);
if (c->codeword_lengths == NULL)
return error(f, VORBIS_outofmem);
memcpy(c->codeword_lengths, lengths, c->entries);
setup_temp_free(f,
lengths,
c->entries); // note this is only safe if there have been no intervening temp mallocs!
lengths = c->codeword_lengths;
c->sparse = 0;
}
// compute the size of the sorted tables
if (c->sparse) {
sorted_count = total;
} else {
sorted_count = 0;
#ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH
for (j = 0; j < c->entries; ++j)
if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE)
++sorted_count;
#endif
}
c->sorted_entries = sorted_count;
values = NULL;
CHECK(f);
if (!c->sparse) {
c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries);
if (!c->codewords)
return error(f, VORBIS_outofmem);
} else {
unsigned int size;
if (c->sorted_entries) {
c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries);
if (!c->codeword_lengths)
return error(f, VORBIS_outofmem);
c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries);
if (!c->codewords)
return error(f, VORBIS_outofmem);
values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries);
if (!values)
return error(f, VORBIS_outofmem);
}
size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries;
if (size > f->setup_temp_memory_required)
f->setup_temp_memory_required = size;
}
if (!compute_codewords(c, lengths, c->entries, values)) {
if (c->sparse)
setup_temp_free(f, values, 0);
return error(f, VORBIS_invalid_setup);
}
if (c->sorted_entries) {
// allocate an extra slot for sentinels
c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries + 1));
if (c->sorted_codewords == NULL)
return error(f, VORBIS_outofmem);
// allocate an extra slot at the front so that c->sorted_values[-1] is defined
// so that we can catch that case without an extra if
c->sorted_values = (int *) setup_malloc(f, sizeof(*c->sorted_values) * (c->sorted_entries + 1));
if (c->sorted_values == NULL)
return error(f, VORBIS_outofmem);
++c->sorted_values;
c->sorted_values[-1] = -1;
compute_sorted_huffman(c, lengths, values);
}
if (c->sparse) {
setup_temp_free(f, values, sizeof(*values) * c->sorted_entries);
setup_temp_free(f, c->codewords, sizeof(*c->codewords) * c->sorted_entries);
setup_temp_free(f, lengths, c->entries);
c->codewords = NULL;
}
compute_accelerated_huffman(c);
CHECK(f);
c->lookup_type = get_bits(f, 4);
if (c->lookup_type > 2)
return error(f, VORBIS_invalid_setup);
if (c->lookup_type > 0) {
uint16 *mults;
c->minimum_value = float32_unpack(get_bits(f, 32));
c->delta_value = float32_unpack(get_bits(f, 32));
c->value_bits = get_bits(f, 4) + 1;
c->sequence_p = get_bits(f, 1);
if (c->lookup_type == 1) {
int values = lookup1_values(c->entries, c->dimensions);
if (values < 0)
return error(f, VORBIS_invalid_setup);
c->lookup_values = (uint32) values;
} else {
c->lookup_values = c->entries * c->dimensions;
}
if (c->lookup_values == 0)
return error(f, VORBIS_invalid_setup);
mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values);
if (mults == NULL)
return error(f, VORBIS_outofmem);
for (j = 0; j < (int) c->lookup_values; ++j) {
int q = get_bits(f, c->value_bits);
if (q == EOP) {
setup_temp_free(f, mults, sizeof(mults[0]) * c->lookup_values);
return error(f, VORBIS_invalid_setup);
}
mults[j] = q;
}
#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK
if (c->lookup_type == 1) {
int len, sparse = c->sparse;
float last = 0;
// pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop
if (sparse) {
if (c->sorted_entries == 0)
goto skip;
c->multiplicands =
(codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions);
} else
c->multiplicands =
(codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions);
if (c->multiplicands == NULL) {
setup_temp_free(f, mults, sizeof(mults[0]) * c->lookup_values);
return error(f, VORBIS_outofmem);
}
len = sparse ? c->sorted_entries : c->entries;
for (j = 0; j < len; ++j) {
unsigned int z = sparse ? c->sorted_values[j] : j;
unsigned int div = 1;
for (k = 0; k < c->dimensions; ++k) {
int off = (z / div) % c->lookup_values;
float val = mults[off] * c->delta_value + c->minimum_value + last;
c->multiplicands[j * c->dimensions + k] = val;
if (c->sequence_p)
last = val;
if (k + 1 < c->dimensions) {
if (div > UINT_MAX / (unsigned int) c->lookup_values) {
setup_temp_free(f, mults, sizeof(mults[0]) * c->lookup_values);
return error(f, VORBIS_invalid_setup);
}
div *= c->lookup_values;
}
}
}
c->lookup_type = 2;
} else
#endif
{
float last = 0;
CHECK(f);
c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values);
if (c->multiplicands == NULL) {
setup_temp_free(f, mults, sizeof(mults[0]) * c->lookup_values);
return error(f, VORBIS_outofmem);
}
for (j = 0; j < (int) c->lookup_values; ++j) {
float val = mults[j] * c->delta_value + c->minimum_value + last;
c->multiplicands[j] = val;
if (c->sequence_p)
last = val;
}
}
#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK
skip:;
#endif
setup_temp_free(f, mults, sizeof(mults[0]) * c->lookup_values);
CHECK(f);
}
CHECK(f);
}
// time domain transfers (notused)
x = get_bits(f, 6) + 1;
for (i = 0; i < x; ++i) {
uint32 z = get_bits(f, 16);
if (z != 0)
return error(f, VORBIS_invalid_setup);
}
// Floors
f->floor_count = get_bits(f, 6) + 1;
f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config));
if (f->floor_config == NULL)
return error(f, VORBIS_outofmem);
for (i = 0; i < f->floor_count; ++i) {
f->floor_types[i] = get_bits(f, 16);
if (f->floor_types[i] > 1)
return error(f, VORBIS_invalid_setup);
if (f->floor_types[i] == 0) {
Floor0 *g = &f->floor_config[i].floor0;
g->order = get_bits(f, 8);
g->rate = get_bits(f, 16);
g->bark_map_size = get_bits(f, 16);
g->amplitude_bits = get_bits(f, 6);
g->amplitude_offset = get_bits(f, 8);
g->number_of_books = get_bits(f, 4) + 1;
for (j = 0; j < g->number_of_books; ++j)
g->book_list[j] = get_bits(f, 8);
return error(f, VORBIS_feature_not_supported);
} else {
stbv__floor_ordering p[31 * 8 + 2];
Floor1 *g = &f->floor_config[i].floor1;
int max_class = -1;
g->partitions = get_bits(f, 5);
for (j = 0; j < g->partitions; ++j) {
g->partition_class_list[j] = get_bits(f, 4);
if (g->partition_class_list[j] > max_class)
max_class = g->partition_class_list[j];
}
for (j = 0; j <= max_class; ++j) {
g->class_dimensions[j] = get_bits(f, 3) + 1;
g->class_subclasses[j] = get_bits(f, 2);
if (g->class_subclasses[j]) {
g->class_masterbooks[j] = get_bits(f, 8);
if (g->class_masterbooks[j] >= f->codebook_count)
return error(f, VORBIS_invalid_setup);
}
for (k = 0; k < 1 << g->class_subclasses[j]; ++k) {
g->subclass_books[j][k] = (int16) get_bits(f, 8) - 1;
if (g->subclass_books[j][k] >= f->codebook_count)
return error(f, VORBIS_invalid_setup);
}
}
g->floor1_multiplier = get_bits(f, 2) + 1;
g->rangebits = get_bits(f, 4);
g->Xlist[0] = 0;
g->Xlist[1] = 1 << g->rangebits;
g->values = 2;
for (j = 0; j < g->partitions; ++j) {
int c = g->partition_class_list[j];
for (k = 0; k < g->class_dimensions[c]; ++k) {
g->Xlist[g->values] = get_bits(f, g->rangebits);
++g->values;
}
}
// precompute the sorting
for (j = 0; j < g->values; ++j) {
p[j].x = g->Xlist[j];
p[j].id = j;
}
qsort(p, g->values, sizeof(p[0]), point_compare);
for (j = 0; j < g->values - 1; ++j)
if (p[j].x == p[j + 1].x)
return error(f, VORBIS_invalid_setup);
for (j = 0; j < g->values; ++j)
g->sorted_order[j] = (uint8) p[j].id;
// precompute the neighbors
for (j = 2; j < g->values; ++j) {
int low = 0, hi = 0;
neighbors(g->Xlist, j, &low, &hi);
g->neighbors[j][0] = low;
g->neighbors[j][1] = hi;
}
if (g->values > longest_floorlist)
longest_floorlist = g->values;
}
}
// Residue
f->residue_count = get_bits(f, 6) + 1;
f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0]));
if (f->residue_config == NULL)
return error(f, VORBIS_outofmem);
memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0]));
for (i = 0; i < f->residue_count; ++i) {
uint8 residue_cascade[64];
Residue *r = f->residue_config + i;
f->residue_types[i] = get_bits(f, 16);
if (f->residue_types[i] > 2)
return error(f, VORBIS_invalid_setup);
r->begin = get_bits(f, 24);
r->end = get_bits(f, 24);
if (r->end < r->begin)
return error(f, VORBIS_invalid_setup);
r->part_size = get_bits(f, 24) + 1;
r->classifications = get_bits(f, 6) + 1;
r->classbook = get_bits(f, 8);
if (r->classbook >= f->codebook_count)
return error(f, VORBIS_invalid_setup);
for (j = 0; j < r->classifications; ++j) {
uint8 high_bits = 0;
uint8 low_bits = get_bits(f, 3);
if (get_bits(f, 1))
high_bits = get_bits(f, 5);
residue_cascade[j] = high_bits * 8 + low_bits;
}
r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications);
if (r->residue_books == NULL)
return error(f, VORBIS_outofmem);
for (j = 0; j < r->classifications; ++j) {
for (k = 0; k < 8; ++k) {
if (residue_cascade[j] & (1 << k)) {
r->residue_books[j][k] = get_bits(f, 8);
if (r->residue_books[j][k] >= f->codebook_count)
return error(f, VORBIS_invalid_setup);
} else {
r->residue_books[j][k] = -1;
}
}
}
// precompute the classifications[] array to avoid inner-loop mod/divide
// call it 'classdata' since we already have r->classifications
r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries);
if (!r->classdata)
return error(f, VORBIS_outofmem);
memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries);
for (j = 0; j < f->codebooks[r->classbook].entries; ++j) {
int classwords = f->codebooks[r->classbook].dimensions;
int temp = j;
r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords);
if (r->classdata[j] == NULL)
return error(f, VORBIS_outofmem);
for (k = classwords - 1; k >= 0; --k) {
r->classdata[j][k] = temp % r->classifications;
temp /= r->classifications;
}
}
}
f->mapping_count = get_bits(f, 6) + 1;
f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping));
if (f->mapping == NULL)
return error(f, VORBIS_outofmem);
memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping));
for (i = 0; i < f->mapping_count; ++i) {
Mapping *m = f->mapping + i;
int mapping_type = get_bits(f, 16);
if (mapping_type != 0)
return error(f, VORBIS_invalid_setup);
m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan));
if (m->chan == NULL)
return error(f, VORBIS_outofmem);
if (get_bits(f, 1))
m->submaps = get_bits(f, 4) + 1;
else
m->submaps = 1;
if (m->submaps > max_submaps)
max_submaps = m->submaps;
if (get_bits(f, 1)) {
m->coupling_steps = get_bits(f, 8) + 1;
if (m->coupling_steps > f->channels)
return error(f, VORBIS_invalid_setup);
for (k = 0; k < m->coupling_steps; ++k) {
m->chan[k].magnitude = get_bits(f, ilog(f->channels - 1));
m->chan[k].angle = get_bits(f, ilog(f->channels - 1));
if (m->chan[k].magnitude >= f->channels)
return error(f, VORBIS_invalid_setup);
if (m->chan[k].angle >= f->channels)
return error(f, VORBIS_invalid_setup);
if (m->chan[k].magnitude == m->chan[k].angle)
return error(f, VORBIS_invalid_setup);
}
} else
m->coupling_steps = 0;
// reserved field
if (get_bits(f, 2))
return error(f, VORBIS_invalid_setup);
if (m->submaps > 1) {
for (j = 0; j < f->channels; ++j) {
m->chan[j].mux = get_bits(f, 4);
if (m->chan[j].mux >= m->submaps)
return error(f, VORBIS_invalid_setup);
}
} else
// @SPECIFICATION: this case is missing from the spec
for (j = 0; j < f->channels; ++j)
m->chan[j].mux = 0;
for (j = 0; j < m->submaps; ++j) {
get_bits(f, 8); // discard
m->submap_floor[j] = get_bits(f, 8);
m->submap_residue[j] = get_bits(f, 8);
if (m->submap_floor[j] >= f->floor_count)
return error(f, VORBIS_invalid_setup);
if (m->submap_residue[j] >= f->residue_count)
return error(f, VORBIS_invalid_setup);
}
}
// Modes
f->mode_count = get_bits(f, 6) + 1;
for (i = 0; i < f->mode_count; ++i) {
Mode *m = f->mode_config + i;
m->blockflag = get_bits(f, 1);
m->windowtype = get_bits(f, 16);
m->transformtype = get_bits(f, 16);
m->mapping = get_bits(f, 8);
if (m->windowtype != 0)
return error(f, VORBIS_invalid_setup);
if (m->transformtype != 0)
return error(f, VORBIS_invalid_setup);
if (m->mapping >= f->mapping_count)
return error(f, VORBIS_invalid_setup);
}
flush_packet(f);
f->previous_length = 0;
for (i = 0; i < f->channels; ++i) {
f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1);
f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1 / 2);
f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist);
if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL)
return error(f, VORBIS_outofmem);
memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1);
#ifdef STB_VORBIS_NO_DEFER_FLOOR
f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2);
if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem);
#endif
}
if (!init_blocksize(f, 0, f->blocksize_0))
return FALSE;
if (!init_blocksize(f, 1, f->blocksize_1))
return FALSE;
f->blocksize[0] = f->blocksize_0;
f->blocksize[1] = f->blocksize_1;
#ifdef STB_VORBIS_DIVIDE_TABLE
if (integer_divide_table[1][1]==0)
for (i=0; i < DIVTAB_NUMER; ++i)
for (j=1; j < DIVTAB_DENOM; ++j)
integer_divide_table[i][j] = i / j;
#endif
// compute how much temporary memory is needed
// 1.
{
uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1);
uint32 classify_mem;
int i, max_part_read = 0;
for (i = 0; i < f->residue_count; ++i) {
Residue *r = f->residue_config + i;
unsigned int actual_size = f->blocksize_1 / 2;
unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size;
unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size;
int n_read = limit_r_end - limit_r_begin;
int part_read = n_read / r->part_size;
if (part_read > max_part_read)
max_part_read = part_read;
}
#ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
classify_mem = f->channels * (sizeof(void *) + max_part_read * sizeof(uint8 *));
#else
classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *));
#endif
// maximum reasonable partition size is f->blocksize_1
f->temp_memory_required = classify_mem;
if (imdct_mem > f->temp_memory_required)
f->temp_memory_required = imdct_mem;
}
if (f->alloc.alloc_buffer) {
assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes);
// check if there's enough temp memory so we don't error later
if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset)
return error(f, VORBIS_outofmem);
}
// @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page
// without PAGEFLAG_continued_packet, so this either points to the first page, or
// the page after the end of the headers. It might be cleaner to point to a page
// in the middle of the headers, when that's the page where the first audio packet
// starts, but we'd have to also correctly skip the end of any continued packet in
// stb_vorbis_seek_start.
if (f->next_seg == -1) {
f->first_audio_page_offset = stb_vorbis_get_file_offset(f);
} else {
f->first_audio_page_offset = 0;
}
return TRUE;
}
static void vorbis_deinit(stb_vorbis *p) {
int i, j;
setup_free(p, p->vendor);
for (i = 0; i < p->comment_list_length; ++i) {
setup_free(p, p->comment_list[i]);
}
setup_free(p, p->comment_list);
if (p->residue_config) {
for (i = 0; i < p->residue_count; ++i) {
Residue *r = p->residue_config + i;
if (r->classdata) {
for (j = 0; j < p->codebooks[r->classbook].entries; ++j)
setup_free(p, r->classdata[j]);
setup_free(p, r->classdata);
}
setup_free(p, r->residue_books);
}
}
if (p->codebooks) {
CHECK(p);
for (i = 0; i < p->codebook_count; ++i) {
Codebook *c = p->codebooks + i;
setup_free(p, c->codeword_lengths);
setup_free(p, c->multiplicands);
setup_free(p, c->codewords);
setup_free(p, c->sorted_codewords);
// c->sorted_values[-1] is the first entry in the array
setup_free(p, c->sorted_values ? c->sorted_values - 1 : NULL);
}
setup_free(p, p->codebooks);
}
setup_free(p, p->floor_config);
setup_free(p, p->residue_config);
if (p->mapping) {
for (i = 0; i < p->mapping_count; ++i)
setup_free(p, p->mapping[i].chan);
setup_free(p, p->mapping);
}
CHECK(p);
for (i = 0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) {
setup_free(p, p->channel_buffers[i]);
setup_free(p, p->previous_window[i]);
#ifdef STB_VORBIS_NO_DEFER_FLOOR
setup_free(p, p->floor_buffers[i]);
#endif
setup_free(p, p->finalY[i]);
}
for (i = 0; i < 2; ++i) {
setup_free(p, p->A[i]);
setup_free(p, p->B[i]);
setup_free(p, p->C[i]);
setup_free(p, p->window[i]);
setup_free(p, p->bit_reverse[i]);
}
#ifndef STB_VORBIS_NO_STDIO
if (p->close_on_free)
fclose(p->f);
#endif
}
void stb_vorbis_close(stb_vorbis *p) {
if (p == NULL)
return;
vorbis_deinit(p);
setup_free(p, p);
}
static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) {
memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start
if (z) {
p->alloc = *z;
p->alloc.alloc_buffer_length_in_bytes &= ~7;
p->temp_offset = p->alloc.alloc_buffer_length_in_bytes;
}
p->eof = 0;
p->error = VORBIS__no_error;
p->stream = NULL;
p->codebooks = NULL;
p->page_crc_tests = -1;
#ifndef STB_VORBIS_NO_STDIO
p->close_on_free = FALSE;
p->f = NULL;
#endif
}
int stb_vorbis_get_sample_offset(stb_vorbis *f) {
if (f->current_loc_valid)
return f->current_loc;
else
return -1;
}
stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) {
stb_vorbis_info d;
d.channels = f->channels;
d.sample_rate = f->sample_rate;
d.setup_memory_required = f->setup_memory_required;
d.setup_temp_memory_required = f->setup_temp_memory_required;
d.temp_memory_required = f->temp_memory_required;
d.max_frame_size = f->blocksize_1 >> 1;
return d;
}
stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) {
stb_vorbis_comment d;
d.vendor = f->vendor;
d.comment_list_length = f->comment_list_length;
d.comment_list = f->comment_list;
return d;
}
int stb_vorbis_get_error(stb_vorbis *f) {
int e = f->error;
f->error = VORBIS__no_error;
return e;
}
static stb_vorbis *vorbis_alloc(stb_vorbis *f) {
stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p));
return p;
}
#ifndef STB_VORBIS_NO_PUSHDATA_API
void stb_vorbis_flush_pushdata(stb_vorbis *f) {
f->previous_length = 0;
f->page_crc_tests = 0;
f->discard_samples_deferred = 0;
f->current_loc_valid = FALSE;
f->first_decode = FALSE;
f->samples_output = 0;
f->channel_buffer_start = 0;
f->channel_buffer_end = 0;
}
static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) {
int i, n;
for (i = 0; i < f->page_crc_tests; ++i)
f->scan[i].bytes_done = 0;
// if we have room for more scans, search for them first, because
// they may cause us to stop early if their header is incomplete
if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) {
if (data_len < 4)
return 0;
data_len -= 3; // need to look for 4-byte sequence, so don't miss
// one that straddles a boundary
for (i = 0; i < data_len; ++i) {
if (data[i] == 0x4f) {
if (0 == memcmp(data + i, ogg_page_header, 4)) {
int j, len;
uint32 crc;
// make sure we have the whole page header
if (i + 26 >= data_len || i + 27 + data[i + 26] >= data_len) {
// only read up to this page start, so hopefully we'll
// have the whole page header start next time
data_len = i;
break;
}
// ok, we have it all; compute the length of the page
len = 27 + data[i + 26];
for (j = 0; j < data[i + 26]; ++j)
len += data[i + 27 + j];
// scan everything up to the embedded crc (which we must 0)
crc = 0;
for (j = 0; j < 22; ++j)
crc = crc32_update(crc, data[i + j]);
// now process 4 0-bytes
for (; j < 26; ++j)
crc = crc32_update(crc, 0);
// len is the total number of bytes we need to scan
n = f->page_crc_tests++;
f->scan[n].bytes_left = len - j;
f->scan[n].crc_so_far = crc;
f->scan[n].goal_crc =
data[i + 22] + (data[i + 23] << 8) + (data[i + 24] << 16) + (data[i + 25] << 24);
// if the last frame on a page is continued to the next, then
// we can't recover the sample_loc immediately
if (data[i + 27 + data[i + 26] - 1] == 255)
f->scan[n].sample_loc = ~0;
else
f->scan[n].sample_loc =
data[i + 6] + (data[i + 7] << 8) + (data[i + 8] << 16) + (data[i + 9] << 24);
f->scan[n].bytes_done = i + j;
if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT)
break;
// keep going if we still have room for more
}
}
}
}
for (i = 0; i < f->page_crc_tests;) {
uint32 crc;
int j;
int n = f->scan[i].bytes_done;
int m = f->scan[i].bytes_left;
if (m > data_len - n)
m = data_len - n;
// m is the bytes to scan in the current chunk
crc = f->scan[i].crc_so_far;
for (j = 0; j < m; ++j)
crc = crc32_update(crc, data[n + j]);
f->scan[i].bytes_left -= m;
f->scan[i].crc_so_far = crc;
if (f->scan[i].bytes_left == 0) {
// does it match?
if (f->scan[i].crc_so_far == f->scan[i].goal_crc) {
// Houston, we have page
data_len = n + m; // consumption amount is wherever that scan ended
f->page_crc_tests = -1; // drop out of page scan mode
f->previous_length = 0; // decode-but-don't-output one frame
f->next_seg = -1; // start a new page
f->current_loc = f->scan[i].sample_loc; // set the current sample location
// to the amount we'd have decoded had we decoded this page
f->current_loc_valid = f->current_loc != ~0U;
return data_len;
}
// delete entry
f->scan[i] = f->scan[--f->page_crc_tests];
} else {
++i;
}
}
return data_len;
}
// return value: number of bytes we used
int stb_vorbis_decode_frame_pushdata(
stb_vorbis *f, // the file we're decoding
const uint8 *data, int data_len, // the memory available for decoding
int *channels, // place to write number of float * buffers
float ***output, // place to write float ** array of float * buffers
int *samples // place to write number of output samples
) {
int i;
int len, right, left;
if (!IS_PUSH_MODE(f))
return error(f, VORBIS_invalid_api_mixing);
if (f->page_crc_tests >= 0) {
*samples = 0;
return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len);
}
f->stream = (uint8 *) data;
f->stream_end = (uint8 *) data + data_len;
f->error = VORBIS__no_error;
// check that we have the entire packet in memory
if (!is_whole_packet_present(f)) {
*samples = 0;
return 0;
}
if (!vorbis_decode_packet(f, &len, &left, &right)) {
// save the actual error we encountered
enum STBVorbisError error = f->error;
if (error == VORBIS_bad_packet_type) {
// flush and resynch
f->error = VORBIS__no_error;
while (get8_packet(f) != EOP)
if (f->eof)
break;
*samples = 0;
return (int) (f->stream - data);
}
if (error == VORBIS_continued_packet_flag_invalid) {
if (f->previous_length == 0) {
// we may be resynching, in which case it's ok to hit one
// of these; just discard the packet
f->error = VORBIS__no_error;
while (get8_packet(f) != EOP)
if (f->eof)
break;
*samples = 0;
return (int) (f->stream - data);
}
}
// if we get an error while parsing, what to do?
// well, it DEFINITELY won't work to continue from where we are!
stb_vorbis_flush_pushdata(f);
// restore the error that actually made us bail
f->error = error;
*samples = 0;
return 1;
}
// success!
len = vorbis_finish_frame(f, len, left, right);
for (i = 0; i < f->channels; ++i)
f->outputs[i] = f->channel_buffers[i] + left;
if (channels)
*channels = f->channels;
*samples = len;
*output = f->outputs;
return (int) (f->stream - data);
}
stb_vorbis *stb_vorbis_open_pushdata(
const unsigned char *data, int data_len, // the memory available for decoding
int *data_used, // only defined if result is not NULL
int *error, const stb_vorbis_alloc *alloc) {
stb_vorbis *f, p;
vorbis_init(&p, alloc);
p.stream = (uint8 *) data;
p.stream_end = (uint8 *) data + data_len;
p.push_mode = TRUE;
if (!start_decoder(&p)) {
if (p.eof)
*error = VORBIS_need_more_data;
else
*error = p.error;
vorbis_deinit(&p);
return NULL;
}
f = vorbis_alloc(&p);
if (f) {
*f = p;
*data_used = (int) (f->stream - data);
*error = 0;
return f;
} else {
vorbis_deinit(&p);
return NULL;
}
}
#endif // STB_VORBIS_NO_PUSHDATA_API
unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) {
#ifndef STB_VORBIS_NO_PUSHDATA_API
if (f->push_mode)
return 0;
#endif
if (USE_MEMORY(f))
return (unsigned int) (f->stream - f->stream_start);
#ifndef STB_VORBIS_NO_STDIO
return (unsigned int) (ftell(f->f) - f->f_start);
#endif
}
#ifndef STB_VORBIS_NO_PULLDATA_API
//
// DATA-PULLING API
//
static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) {
for (;;) {
int n;
if (f->eof)
return 0;
n = get8(f);
if (n == 0x4f) { // page header candidate
unsigned int retry_loc = stb_vorbis_get_file_offset(f);
int i;
// check if we're off the end of a file_section stream
if (retry_loc - 25 > f->stream_len)
return 0;
// check the rest of the header
for (i = 1; i < 4; ++i)
if (get8(f) != ogg_page_header[i])
break;
if (f->eof)
return 0;
if (i == 4) {
uint8 header[27];
uint32 i, crc, goal, len;
for (i = 0; i < 4; ++i)
header[i] = ogg_page_header[i];
for (; i < 27; ++i)
header[i] = get8(f);
if (f->eof)
return 0;
if (header[4] != 0)
goto invalid;
goal = header[22] + (header[23] << 8) + (header[24] << 16) + ((uint32) header[25] << 24);
for (i = 22; i < 26; ++i)
header[i] = 0;
crc = 0;
for (i = 0; i < 27; ++i)
crc = crc32_update(crc, header[i]);
len = 0;
for (i = 0; i < header[26]; ++i) {
int s = get8(f);
crc = crc32_update(crc, s);
len += s;
}
if (len && f->eof)
return 0;
for (i = 0; i < len; ++i)
crc = crc32_update(crc, get8(f));
// finished parsing probable page
if (crc == goal) {
// we could now check that it's either got the last
// page flag set, OR it's followed by the capture
// pattern, but I guess TECHNICALLY you could have
// a file with garbage between each ogg page and recover
// from it automatically? So even though that paranoia
// might decrease the chance of an invalid decode by
// another 2^32, not worth it since it would hose those
// invalid-but-useful files?
if (end)
*end = stb_vorbis_get_file_offset(f);
if (last) {
if (header[5] & 0x04)
*last = 1;
else
*last = 0;
}
set_file_offset(f, retry_loc - 1);
return 1;
}
}
invalid:
// not a valid page, so rewind and look for next one
set_file_offset(f, retry_loc);
}
}
}
#define SAMPLE_unknown 0xffffffff
// seeking is implemented with a binary search, which narrows down the range to
// 64K, before using a linear search (because finding the synchronization
// pattern can be expensive, and the chance we'd find the end page again is
// relatively high for small ranges)
//
// two initial interpolation-style probes are used at the start of the search
// to try to bound either side of the binary search sensibly, while still
// working in O(log n) time if they fail.
static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) {
uint8 header[27], lacing[255];
int i, len;
// record where the page starts
z->page_start = stb_vorbis_get_file_offset(f);
// parse the header
getn(f, header, 27);
if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S')
return 0;
getn(f, lacing, header[26]);
// determine the length of the payload
len = 0;
for (i = 0; i < header[26]; ++i)
len += lacing[i];
// this implies where the page ends
z->page_end = z->page_start + 27 + header[26] + len;
// read the last-decoded sample out of the data
z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24);
// restore file state to where we were
set_file_offset(f, z->page_start);
return 1;
}
// rarely used function to seek back to the preceding page while finding the
// start of a packet
static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) {
unsigned int previous_safe, end;
// now we want to seek back 64K from the limit
if (limit_offset >= 65536 && limit_offset - 65536 >= f->first_audio_page_offset)
previous_safe = limit_offset - 65536;
else
previous_safe = f->first_audio_page_offset;
set_file_offset(f, previous_safe);
while (vorbis_find_page(f, &end, NULL)) {
if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset)
return 1;
set_file_offset(f, end);
}
return 0;
}
// implements the search logic for finding a page and starting decoding. if
// the function succeeds, current_loc_valid will be true and current_loc will
// be less than or equal to the provided sample number (the closer the
// better).
static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) {
ProbedPage left, right, mid;
int i, start_seg_with_known_loc, end_pos, page_start;
uint32 delta, stream_length, padding, last_sample_limit;
double offset = 0.0, bytes_per_sample = 0.0;
int probe = 0;
// find the last page and validate the target sample
stream_length = stb_vorbis_stream_length_in_samples(f);
if (stream_length == 0)
return error(f, VORBIS_seek_without_length);
if (sample_number > stream_length)
return error(f, VORBIS_seek_invalid);
// this is the maximum difference between the window-center (which is the
// actual granule position value), and the right-start (which the spec
// indicates should be the granule position (give or take one)).
padding = ((f->blocksize_1 - f->blocksize_0) >> 2);
if (sample_number < padding)
last_sample_limit = 0;
else
last_sample_limit = sample_number - padding;
left = f->p_first;
while (left.last_decoded_sample == ~0U) {
// (untested) the first page does not have a 'last_decoded_sample'
set_file_offset(f, left.page_end);
if (!get_seek_page_info(f, &left))
goto error;
}
right = f->p_last;
assert(right.last_decoded_sample != ~0U);
// starting from the start is handled differently
if (last_sample_limit <= left.last_decoded_sample) {
if (stb_vorbis_seek_start(f)) {
if (f->current_loc > sample_number)
return error(f, VORBIS_seek_failed);
return 1;
}
return 0;
}
while (left.page_end != right.page_start) {
assert(left.page_end < right.page_start);
// search range in bytes
delta = right.page_start - left.page_end;
if (delta <= 65536) {
// there's only 64K left to search - handle it linearly
set_file_offset(f, left.page_end);
} else {
if (probe < 2) {
if (probe == 0) {
// first probe (interpolate)
double data_bytes = right.page_end - left.page_start;
bytes_per_sample = data_bytes / right.last_decoded_sample;
offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample);
} else {
// second probe (try to bound the other side)
double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample;
if (error >= 0 && error < 8000)
error = 8000;
if (error < 0 && error > -8000)
error = -8000;
offset += error * 2;
}
// ensure the offset is valid
if (offset < left.page_end)
offset = left.page_end;
if (offset > right.page_start - 65536)
offset = right.page_start - 65536;
set_file_offset(f, (unsigned int) offset);
} else {
// binary search for large ranges (offset by 32K to ensure
// we don't hit the right page)
set_file_offset(f, left.page_end + (delta / 2) - 32768);
}
if (!vorbis_find_page(f, NULL, NULL))
goto error;
}
for (;;) {
if (!get_seek_page_info(f, &mid))
goto error;
if (mid.last_decoded_sample != ~0U)
break;
// (untested) no frames end on this page
set_file_offset(f, mid.page_end);
assert(mid.page_start < right.page_start);
}
// if we've just found the last page again then we're in a tricky file,
// and we're close enough (if it wasn't an interpolation probe).
if (mid.page_start == right.page_start) {
if (probe >= 2 || delta <= 65536)
break;
} else {
if (last_sample_limit < mid.last_decoded_sample)
right = mid;
else
left = mid;
}
++probe;
}
// seek back to start of the last packet
page_start = left.page_start;
set_file_offset(f, page_start);
if (!start_page(f))
return error(f, VORBIS_seek_failed);
end_pos = f->end_seg_with_known_loc;
assert(end_pos >= 0);
for (;;) {
for (i = end_pos; i > 0; --i)
if (f->segments[i - 1] != 255)
break;
start_seg_with_known_loc = i;
if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet))
break;
// (untested) the final packet begins on an earlier page
if (!go_to_page_before(f, page_start))
goto error;
page_start = stb_vorbis_get_file_offset(f);
if (!start_page(f))
goto error;
end_pos = f->segment_count - 1;
}
// prepare to start decoding
f->current_loc_valid = FALSE;
f->last_seg = FALSE;
f->valid_bits = 0;
f->packet_bytes = 0;
f->bytes_in_seg = 0;
f->previous_length = 0;
f->next_seg = start_seg_with_known_loc;
for (i = 0; i < start_seg_with_known_loc; i++)
skip(f, f->segments[i]);
// start decoding (optimizable - this frame is generally discarded)
if (!vorbis_pump_first_frame(f))
return 0;
if (f->current_loc > sample_number)
return error(f, VORBIS_seek_failed);
return 1;
error:
// try to restore the file to a valid state
stb_vorbis_seek_start(f);
return error(f, VORBIS_seek_failed);
}
// the same as vorbis_decode_initial, but without advancing
static int peek_decode_initial(vorb *f,
int *p_left_start,
int *p_left_end,
int *p_right_start,
int *p_right_end,
int *mode) {
int bits_read, bytes_read;
if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode))
return 0;
// either 1 or 2 bytes were read, figure out which so we can rewind
bits_read = 1 + ilog(f->mode_count - 1);
if (f->mode_config[*mode].blockflag)
bits_read += 2;
bytes_read = (bits_read + 7) / 8;
f->bytes_in_seg += bytes_read;
f->packet_bytes -= bytes_read;
skip(f, -bytes_read);
if (f->next_seg == -1)
f->next_seg = f->segment_count - 1;
else
f->next_seg--;
f->valid_bits = 0;
return 1;
}
int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) {
uint32 max_frame_samples;
if (IS_PUSH_MODE(f))
return error(f, VORBIS_invalid_api_mixing);
// fast page-level search
if (!seek_to_sample_coarse(f, sample_number))
return 0;
assert(f->current_loc_valid);
assert(f->current_loc <= sample_number);
// linear search for the relevant packet
max_frame_samples = (f->blocksize_1 * 3 - f->blocksize_0) >> 2;
while (f->current_loc < sample_number) {
int left_start, left_end, right_start, right_end, mode, frame_samples;
if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode))
return error(f, VORBIS_seek_failed);
// calculate the number of samples returned by the next frame
frame_samples = right_start - left_start;
if (f->current_loc + frame_samples > sample_number) {
return 1; // the next frame will contain the sample
} else if (f->current_loc + frame_samples + max_frame_samples > sample_number) {
// there's a chance the frame after this could contain the sample
vorbis_pump_first_frame(f);
} else {
// this frame is too early to be relevant
f->current_loc += frame_samples;
f->previous_length = 0;
maybe_start_packet(f);
flush_packet(f);
}
}
// the next frame should start with the sample
if (f->current_loc != sample_number)
return error(f, VORBIS_seek_failed);
return 1;
}
int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) {
if (!stb_vorbis_seek_frame(f, sample_number))
return 0;
if (sample_number != f->current_loc) {
int n;
uint32 frame_start = f->current_loc;
stb_vorbis_get_frame_float(f, &n, NULL);
assert(sample_number > frame_start);
assert(f->channel_buffer_start + (int) (sample_number - frame_start) <= f->channel_buffer_end);
f->channel_buffer_start += (sample_number - frame_start);
}
return 1;
}
int stb_vorbis_seek_start(stb_vorbis *f) {
if (IS_PUSH_MODE(f)) {
return error(f, VORBIS_invalid_api_mixing);
}
set_file_offset(f, f->first_audio_page_offset);
f->previous_length = 0;
f->first_decode = TRUE;
f->next_seg = -1;
return vorbis_pump_first_frame(f);
}
unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) {
unsigned int restore_offset, previous_safe;
unsigned int end, last_page_loc;
if (IS_PUSH_MODE(f))
return error(f, VORBIS_invalid_api_mixing);
if (!f->total_samples) {
unsigned int last;
uint32 lo, hi;
char header[6];
// first, store the current decode position so we can restore it
restore_offset = stb_vorbis_get_file_offset(f);
// now we want to seek back 64K from the end (the last page must
// be at most a little less than 64K, but let's allow a little slop)
if (f->stream_len >= 65536 && f->stream_len - 65536 >= f->first_audio_page_offset)
previous_safe = f->stream_len - 65536;
else
previous_safe = f->first_audio_page_offset;
set_file_offset(f, previous_safe);
// previous_safe is now our candidate 'earliest known place that seeking
// to will lead to the final page'
if (!vorbis_find_page(f, &end, &last)) {
// if we can't find a page, we're hosed!
f->error = VORBIS_cant_find_last_page;
f->total_samples = 0xffffffff;
goto done;
}
// check if there are more pages
last_page_loc = stb_vorbis_get_file_offset(f);
// stop when the last_page flag is set, not when we reach eof;
// this allows us to stop short of a 'file_section' end without
// explicitly checking the length of the section
while (!last) {
set_file_offset(f, end);
if (!vorbis_find_page(f, &end, &last)) {
// the last page we found didn't have the 'last page' flag
// set. whoops!
break;
}
//previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging
last_page_loc = stb_vorbis_get_file_offset(f);
}
set_file_offset(f, last_page_loc);
// parse the header
getn(f, (unsigned char *) header, 6);
// extract the absolute granule position
lo = get32(f);
hi = get32(f);
if (lo == 0xffffffff && hi == 0xffffffff) {
f->error = VORBIS_cant_find_last_page;
f->total_samples = SAMPLE_unknown;
goto done;
}
if (hi)
lo = 0xfffffffe; // saturate
f->total_samples = lo;
f->p_last.page_start = last_page_loc;
f->p_last.page_end = end;
f->p_last.last_decoded_sample = lo;
done:
set_file_offset(f, restore_offset);
}
return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples;
}
float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) {
return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate;
}
int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) {
int len, right, left, i;
if (IS_PUSH_MODE(f))
return error(f, VORBIS_invalid_api_mixing);
if (!vorbis_decode_packet(f, &len, &left, &right)) {
f->channel_buffer_start = f->channel_buffer_end = 0;
return 0;
}
len = vorbis_finish_frame(f, len, left, right);
for (i = 0; i < f->channels; ++i)
f->outputs[i] = f->channel_buffers[i] + left;
f->channel_buffer_start = left;
f->channel_buffer_end = left + len;
if (channels)
*channels = f->channels;
if (output)
*output = f->outputs;
return len;
}
#ifndef STB_VORBIS_NO_STDIO
stb_vorbis *stb_vorbis_open_file_section(FILE *file,
int close_on_free,
int *error,
const stb_vorbis_alloc *alloc,
unsigned int length) {
stb_vorbis *f, p;
vorbis_init(&p, alloc);
p.f = file;
p.f_start = (uint32) ftell(file);
p.stream_len = length;
p.close_on_free = close_on_free;
if (start_decoder(&p)) {
f = vorbis_alloc(&p);
if (f) {
*f = p;
vorbis_pump_first_frame(f);
return f;
}
}
if (error)
*error = p.error;
vorbis_deinit(&p);
return NULL;
}
stb_vorbis *stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) {
unsigned int len, start;
start = (unsigned int) ftell(file);
fseek(file, 0, SEEK_END);
len = (unsigned int) (ftell(file) - start);
fseek(file, start, SEEK_SET);
return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len);
}
stb_vorbis *stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) {
FILE *f;
#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__)
if (0 != fopen_s(&f, filename, "rb"))
f = NULL;
#else
f = fopen(filename, "rb");
#endif
if (f)
return stb_vorbis_open_file(f, TRUE, error, alloc);
if (error)
*error = VORBIS_file_open_failure;
return NULL;
}
#endif // STB_VORBIS_NO_STDIO
stb_vorbis *stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) {
stb_vorbis *f, p;
if (!data) {
if (error)
*error = VORBIS_unexpected_eof;
return NULL;
}
vorbis_init(&p, alloc);
p.stream = (uint8 *) data;
p.stream_end = (uint8 *) data + len;
p.stream_start = (uint8 *) p.stream;
p.stream_len = len;
p.push_mode = FALSE;
if (start_decoder(&p)) {
f = vorbis_alloc(&p);
if (f) {
*f = p;
vorbis_pump_first_frame(f);
if (error)
*error = VORBIS__no_error;
return f;
}
}
if (error)
*error = p.error;
vorbis_deinit(&p);
return NULL;
}
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
#define PLAYBACK_MONO 1
#define PLAYBACK_LEFT 2
#define PLAYBACK_RIGHT 4
#define L (PLAYBACK_LEFT | PLAYBACK_MONO)
#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO)
#define R (PLAYBACK_RIGHT | PLAYBACK_MONO)
static int8 channel_position[7][6] =
{
{0},
{C},
{L, R},
{L, C, R},
{L, R, L, R},
{L, C, R, L, R},
{L, C, R, L, R, C},
};
#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT
typedef union {
float f;
int i;
} float_conv;
typedef char stb_vorbis_float_size_test[sizeof(float) == 4 && sizeof(int) == 4];
#define FASTDEF(x) float_conv x
// add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round
#define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT))
#define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22))
#define FAST_SCALED_FLOAT_TO_INT(temp, x, s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s))
#define check_endianness()
#else
#define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s))))
#define check_endianness()
#define FASTDEF(x)
#endif
static void copy_samples(short *dest, float *src, int len) {
int i;
check_endianness();
for (i = 0; i < len; ++i) {
FASTDEF(temp);
int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i], 15);
if ((unsigned int) (v + 32768) > 65535)
v = v < 0 ? -32768 : 32767;
dest[i] = v;
}
}
static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) {
#define STB_BUFFER_SIZE 32
float buffer[STB_BUFFER_SIZE];
int i, j, o, n = STB_BUFFER_SIZE;
check_endianness();
for (o = 0; o < len; o += STB_BUFFER_SIZE) {
memset(buffer, 0, sizeof(buffer));
if (o + n > len)
n = len - o;
for (j = 0; j < num_c; ++j) {
if (channel_position[num_c][j] & mask) {
for (i = 0; i < n; ++i)
buffer[i] += data[j][d_offset + o + i];
}
}
for (i = 0; i < n; ++i) {
FASTDEF(temp);
int v = FAST_SCALED_FLOAT_TO_INT(temp, buffer[i], 15);
if ((unsigned int) (v + 32768) > 65535)
v = v < 0 ? -32768 : 32767;
output[o + i] = v;
}
}
#undef STB_BUFFER_SIZE
}
static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) {
#define STB_BUFFER_SIZE 32
float buffer[STB_BUFFER_SIZE];
int i, j, o, n = STB_BUFFER_SIZE >> 1;
// o is the offset in the source data
check_endianness();
for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) {
// o2 is the offset in the output data
int o2 = o << 1;
memset(buffer, 0, sizeof(buffer));
if (o + n > len)
n = len - o;
for (j = 0; j < num_c; ++j) {
int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT);
if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) {
for (i = 0; i < n; ++i) {
buffer[i * 2 + 0] += data[j][d_offset + o + i];
buffer[i * 2 + 1] += data[j][d_offset + o + i];
}
} else if (m == PLAYBACK_LEFT) {
for (i = 0; i < n; ++i) {
buffer[i * 2 + 0] += data[j][d_offset + o + i];
}
} else if (m == PLAYBACK_RIGHT) {
for (i = 0; i < n; ++i) {
buffer[i * 2 + 1] += data[j][d_offset + o + i];
}
}
}
for (i = 0; i < (n << 1); ++i) {
FASTDEF(temp);
int v = FAST_SCALED_FLOAT_TO_INT(temp, buffer[i], 15);
if ((unsigned int) (v + 32768) > 65535)
v = v < 0 ? -32768 : 32767;
output[o2 + i] = v;
}
}
#undef STB_BUFFER_SIZE
}
static void convert_samples_short(int buf_c,
short **buffer,
int b_offset,
int data_c,
float **data,
int d_offset,
int samples) {
int i;
if (buf_c != data_c && buf_c <= 2 && data_c <= 6) {
static int channel_selector[3][2] = {{0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT}};
for (i = 0; i < buf_c; ++i)
compute_samples(channel_selector[buf_c][i], buffer[i] + b_offset, data_c, data, d_offset, samples);
} else {
int limit = buf_c < data_c ? buf_c : data_c;
for (i = 0; i < limit; ++i)
copy_samples(buffer[i] + b_offset, data[i] + d_offset, samples);
for (; i < buf_c; ++i)
memset(buffer[i] + b_offset, 0, sizeof(short) * samples);
}
}
int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) {
float **output = NULL;
int len = stb_vorbis_get_frame_float(f, NULL, &output);
if (len > num_samples)
len = num_samples;
if (len)
convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len);
return len;
}
static void convert_channels_short_interleaved(int buf_c,
short *buffer,
int data_c,
float **data,
int d_offset,
int len) {
int i;
check_endianness();
if (buf_c != data_c && buf_c <= 2 && data_c <= 6) {
assert(buf_c == 2);
for (i = 0; i < buf_c; ++i)
compute_stereo_samples(buffer, data_c, data, d_offset, len);
} else {
int limit = buf_c < data_c ? buf_c : data_c;
int j;
for (j = 0; j < len; ++j) {
for (i = 0; i < limit; ++i) {
FASTDEF(temp);
float f = data[i][d_offset + j];
int v = FAST_SCALED_FLOAT_TO_INT(temp, f, 15);//data[i][d_offset+j],15);
if ((unsigned int) (v + 32768) > 65535)
v = v < 0 ? -32768 : 32767;
*buffer++ = v;
}
for (; i < buf_c; ++i)
*buffer++ = 0;
}
}
}
int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) {
float **output;
int len;
if (num_c == 1)
return stb_vorbis_get_frame_short(f, num_c, &buffer, num_shorts);
len = stb_vorbis_get_frame_float(f, NULL, &output);
if (len) {
if (len * num_c > num_shorts)
len = num_shorts / num_c;
convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len);
}
return len;
}
int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) {
float **outputs;
int len = num_shorts / channels;
int n = 0;
while (n < len) {
int k = f->channel_buffer_end - f->channel_buffer_start;
if (n + k >= len)
k = len - n;
if (k)
convert_channels_short_interleaved(channels,
buffer,
f->channels,
f->channel_buffers,
f->channel_buffer_start,
k);
buffer += k * channels;
n += k;
f->channel_buffer_start += k;
if (n == len)
break;
if (!stb_vorbis_get_frame_float(f, NULL, &outputs))
break;
}
return n;
}
int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) {
float **outputs;
int n = 0;
while (n < len) {
int k = f->channel_buffer_end - f->channel_buffer_start;
if (n + k >= len)
k = len - n;
if (k)
convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k);
n += k;
f->channel_buffer_start += k;
if (n == len)
break;
if (!stb_vorbis_get_frame_float(f, NULL, &outputs))
break;
}
return n;
}
#ifndef STB_VORBIS_NO_STDIO
int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) {
int data_len, offset, total, limit, error;
short *data;
stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL);
if (v == NULL)
return -1;
limit = v->channels * 4096;
*channels = v->channels;
if (sample_rate)
*sample_rate = v->sample_rate;
offset = data_len = 0;
total = limit;
data = (short *) malloc(total * sizeof(*data));
if (data == NULL) {
stb_vorbis_close(v);
return -2;
}
for (;;) {
int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data + offset, total - offset);
if (n == 0)
break;
data_len += n;
offset += n * v->channels;
if (offset + limit > total) {
short *data2;
total *= 2;
data2 = (short *) realloc(data, total * sizeof(*data));
if (data2 == NULL) {
free(data);
stb_vorbis_close(v);
return -2;
}
data = data2;
}
}
*output = data;
stb_vorbis_close(v);
return data_len;
}
#endif // NO_STDIO
int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) {
int data_len, offset, total, limit, error;
short *data;
stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL);
if (v == NULL)
return -1;
limit = v->channels * 4096;
*channels = v->channels;
if (sample_rate)
*sample_rate = v->sample_rate;
offset = data_len = 0;
total = limit;
data = (short *) malloc(total * sizeof(*data));
if (data == NULL) {
stb_vorbis_close(v);
return -2;
}
for (;;) {
int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data + offset, total - offset);
if (n == 0)
break;
data_len += n;
offset += n * v->channels;
if (offset + limit > total) {
short *data2;
total *= 2;
data2 = (short *) realloc(data, total * sizeof(*data));
if (data2 == NULL) {
free(data);
stb_vorbis_close(v);
return -2;
}
data = data2;
}
}
*output = data;
stb_vorbis_close(v);
return data_len;
}
#endif // STB_VORBIS_NO_INTEGER_CONVERSION
int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) {
float **outputs;
int len = num_floats / channels;
int n = 0;
int z = f->channels;
if (z > channels)
z = channels;
while (n < len) {
int i, j;
int k = f->channel_buffer_end - f->channel_buffer_start;
if (n + k >= len)
k = len - n;
for (j = 0; j < k; ++j) {
for (i = 0; i < z; ++i)
*buffer++ = f->channel_buffers[i][f->channel_buffer_start + j];
for (; i < channels; ++i)
*buffer++ = 0;
}
n += k;
f->channel_buffer_start += k;
if (n == len)
break;
if (!stb_vorbis_get_frame_float(f, NULL, &outputs))
break;
}
return n;
}
int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) {
float **outputs;
int n = 0;
int z = f->channels;
if (z > channels)
z = channels;
while (n < num_samples) {
int i;
int k = f->channel_buffer_end - f->channel_buffer_start;
if (n + k >= num_samples)
k = num_samples - n;
if (k) {
for (i = 0; i < z; ++i)
memcpy(buffer[i] + n, f->channel_buffers[i] + f->channel_buffer_start, sizeof(float) * k);
for (; i < channels; ++i)
memset(buffer[i] + n, 0, sizeof(float) * k);
}
n += k;
f->channel_buffer_start += k;
if (n == num_samples)
break;
if (!stb_vorbis_get_frame_float(f, NULL, &outputs))
break;
}
return n;
}
#endif // STB_VORBIS_NO_PULLDATA_API
/* Version history
1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223
found with Mayhem by ForAllSecure
1.16 - 2019-03-04 - fix warnings
1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found
1.14 - 2018-02-11 - delete bogus dealloca usage
1.13 - 2018-01-29 - fix truncation of last frame (hopefully)
1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files
1.11 - 2017-07-23 - fix MinGW compilation
1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory
1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version
1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks;
avoid discarding last frame of audio data
1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API
some more crash fixes when out of memory or with corrupt files
1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson)
some crash fixes when out of memory or with corrupt files
1.05 - 2015-04-19 - don't define __forceinline if it's redundant
1.04 - 2014-08-27 - fix missing const-correct case in API
1.03 - 2014-08-07 - Warning fixes
1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows
1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float
1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel
(API change) report sample rate for decode-full-file funcs
0.99996 - bracket #include <malloc.h> for macintosh compilation by Laurent Gomila
0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem
0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence
0.99993 - remove assert that fired on legal files with empty tables
0.99992 - rewind-to-start
0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo
0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++
0.9998 - add a full-decode function with a memory source
0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition
0.9996 - query length of vorbis stream in samples/seconds
0.9995 - bugfix to another optimization that only happened in certain files
0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors
0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation
0.9992 - performance improvement of IMDCT; now performs close to reference implementation
0.9991 - performance improvement of IMDCT
0.999 - (should have been 0.9990) performance improvement of IMDCT
0.998 - no-CRT support from Casey Muratori
0.997 - bugfixes for bugs found by Terje Mathisen
0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen
0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen
0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen
0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen
0.992 - fixes for MinGW warning
0.991 - turn fast-float-conversion on by default
0.990 - fix push-mode seek recovery if you seek into the headers
0.98b - fix to bad release of 0.98
0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode
0.97 - builds under c++ (typecasting, don't use 'class' keyword)
0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code
0.95 - clamping code for 16-bit functions
0.94 - not publically released
0.93 - fixed all-zero-floor case (was decoding garbage)
0.92 - fixed a memory leak
0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION
0.90 - first public release
*/
#endif // STB_VORBIS_HEADER_ONLY
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
target_sources(fggl
PRIVATE
guid.cpp
)
\ No newline at end of file
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 23/07/22.
//
#include <map>
#include <string>
#include <cassert>
#include "fggl/util/guid.hpp"
namespace fggl::util {
namespace {
std::map<GUID, std::string> guidTable;
}
auto intern_string(const char *str) -> GUID {
assert(str != nullptr);
GUID guid = make_guid(str);
auto tableValue = guidTable.find(guid);
if (tableValue != guidTable.end()) {
assert(tableValue->second == str);
} else {
guidTable[guid] = str;
}
return guid;
}
auto guid_to_string(GUID guid) -> std::string {
auto tableValue = guidTable.find(guid);
if (tableValue != guidTable.end()) {
return tableValue->second;
}
// it's not in the table...
return "UNKNOWN_GUID(" + std::to_string(guid.get()) + ")";
}
}
auto operator "" _fid(const char *str) -> fggl::util::GUID {
fggl::util::intern_string(str);
return fggl::util::make_guid(str);
}
auto operator "" _fid(const char *str, std::size_t) -> fggl::util::GUID {
fggl::util::intern_string(str);
return fggl::util::make_guid(str);
}
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 03/09/22.
//
#ifndef FGGL_ANIMATION_ANIMATOR_H
#define FGGL_ANIMATION_ANIMATOR_H
#include <functional>
#include <map>
#include <cstdint>
#include <cassert>
namespace fggl::animation {
using AnimationCallback = std::function<void(void)>;
using CallbackHandle = uint32_t;
/**
* Frame-based animation.
*
* Tries to maintain a constant framerate for things that care about that.
*/
class FrameAnimator {
public:
explicit inline FrameAnimator(float targetFPS) : m_target( 1.0F / targetFPS) {
assert( 0 <= m_target );
}
inline void reset() {
m_current = 0;
}
inline void update(float dt) {
assert(0 <= dt);
m_current += dt;
while ( m_current >= m_target) {
tick();
m_current -= m_target;
}
assert(0 <= m_current);
}
// tick the animation system, should be handled by update
inline void tick() {
for (auto& [k,v] : m_callbacks) {
v();
}
}
inline CallbackHandle add(AnimationCallback callback) {
auto myHandle = m_lastCallback++;
m_callbacks[myHandle] = callback;
return myHandle;
}
inline void remove(CallbackHandle handle) {
auto itr = m_callbacks.find(handle);
if ( itr != m_callbacks.end() ) {
m_callbacks.erase(itr);
}
}
private:
const float m_target;
float m_current = 0.0F;
CallbackHandle m_lastCallback = 0;
std::map<CallbackHandle, AnimationCallback> m_callbacks;
};
} // namespace fggl::animation
#endif //FGGL_ANIMATION_ANIMATOR_H
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 19/06/22.
// Derived from Game Engine Architecture, 3rd Edition, Chapter 12
//
#ifndef FGGL_ANIMATION_SKELETON_HPP
#define FGGL_ANIMATION_SKELETON_HPP
#include <array>
#include "fggl/math/types.hpp"
namespace fggl::anim {
struct Joint {
math::mat4 m_invBindPose;
math::uint8 m_parent;
};
struct Skeleton {
std::size_t m_size;
std::array<Joint, 255> m_joints;
};
struct JointPose {
math::quat m_rot;
math::vec3 m_trans;
float m_scale;
};
struct SkeletonPose {
Skeleton *m_skel;
JointPose *m_localPose;
math::mat4 *m_globalPose;
};
struct SkinnedVertex {
math::vec3 m_pos;
math::vec3 m_normal;
math::vec2 m_tex;
std::array<math::uint8, 4> m_joint_idx;
std::array<float, 3> m_joint_weights;
};
} // namespace fggl::anim
#endif //FGGL_ANIMATION_SKELETON_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_INCLUDE_FGGL_APP_HPP
#define FGGL_INCLUDE_FGGL_APP_HPP
#define ASSERT_MSG(exp, msg) assert(((void)(msg), exp))
#include <cassert>
#include <string>
#include <memory>
#include <unordered_map>
#include "fggl/display/glfw/window.hpp"
#include <fggl/gfx/paint.hpp>
#include <fggl/util/states.hpp>
#include "fggl/modules/manager.hpp"
namespace fggl {
class App;
class AppState;
using Identifer = std::string;
using AppMachine = util::StateMachine<AppState, Identifer>;
class AppState {
public:
/**
* Create an Application State.
*
* A state is responsible for managing user interaction with the app. When created, the appstate
* is passed a reference to the application that owns it. The lifetime of the state is bounded
* by the lifetype of this object.
*
* Ie. the lifetime of reference to the App is at least that of the AppState.
*
* @param owner a non-owned reference to the owner of the state.
*/
explicit AppState(App &owner) : m_owner(owner) {}
virtual ~AppState() = default;
/**
* Update the underlying model of this state.
*
* States should not assume that one update means one render call, as the game loop may issue
* multiple updates per render or vice-versa depending on requriements. Update is intended for
* dispatching game-system related infomation.
*/
virtual void update(float dt) = 0;
/**
* Perform actions neccerary for rendering the scene.
*
* When this method is invoked it is safe to assume that rendering of some form will take place
* after it returns and the rendering state should be updated to reflect this. The rendering
* environment will be passed in as an argument.
*
* It is not safe to assume the render target will always be the same, as the scene may be
* rendered in mutliple passes (eg, for VR requirements).
*/
virtual void render(gfx::Graphics &paint) = 0;
/**
* Notify the State it has just been switched to.
*
* This state should perform any setup it requires (eg, registering callbacks, requesting
* resources, etc...).
*
* FIXME: This should probably be RAII
*/
virtual void activate() {}
/**
* Notify the State it is about to be switched from.
*
* This state should perform any cleanup it requires (eg, unregistering callbacks, freeing
* resources, etc...).
*
* FIXME: This should probably be RAII
*/
virtual void deactivate() {}
[[nodiscard]] inline auto owner() const -> App& {
return m_owner;
}
protected:
App &m_owner;
};
/*! \class App app.hpp fggl/app.hpp
*
* Main entrypoint to the game framework.
*/
class App {
public:
/**
* Create an instance of an application, with a set of modules and name.
*
* The name is used to derrive verious settings, such as the location of same games and user
* configuration and content.
*/
explicit App(modules::Manager *manager, const Identifer &name);
/**
* Create an instance of an application, with a set of modules and name.
*
* This version of the constructor allows explicitly setting the name used for user-data rather
* than derriving it from the application name. This is useful if you want to use a shortened
* version of your application name for configuration.
*/
App(modules::Manager *manager, const Identifer &name, const Identifer &folderName);
// class is non copy-able
App(const App &app) = delete;
App(const App &&app) = delete;
auto operator=(const App &other) -> App & = delete;
auto operator=(App &&other) -> App & = delete;
/**
* Set the currently active window.
*
* FIXME: this is a nasty hack to get round the setup order for windows, graphics APIs and
* screen refreshes. Should be fixed with observer pattern.
*/
inline void setWindow(display::Window *window) {
m_window = window;
}
/**
* Perform main game loop functions.
*
* You should pass in argc and argv used to invoke main to this method. At the moment it does
* not use these, but in the future they will be used to provide game-agnostic options.
*/
auto run(int argc, const char **argv) -> int;
/**
* Register a new state with the application.
*
* States are intended to be a block of code which can be started, will execute a single frame
* of work, and can be invoked repeatedly. For example, a main menu, options menu,
* single-player game and multi-player game might be all be suitable to be states. When a state
* change is requested, the currently running state is stopped and garabage collected.
*
* This is similar to the concept of an 'activity' in Android.
*/
template<typename T>
auto addState(const Identifer &name) -> T * {
static_assert(std::is_base_of<AppState, T>::value, "States must be AppStates");
return m_states.put<T>(name, *this);
}
/**
* Request the game changes states.
*
* This will be executed at the next opporunity (most likley next iteration of the game loop).
* Identifer should be an identifier used when calling addState, and the state should previouslly
* been registered using addState.
*/
inline void change_state(const Identifer &name) {
m_expectedScene = name;
}
/**
* Return the currently active state (the state that is currently executing).
*/
inline auto active_state() const -> AppState & {
return m_states.active();
}
/**
* Get a pointer to a service.
*
* This is the primary way in which states can get access to resources they require.
*
* returns nullptr if the service does not exist, or cannot be provided.
*/
template<typename T>
inline auto service() -> T * {
try {
return m_subsystems->template get<T>();
} catch (std::out_of_range &e) {
debug::log(debug::Level::error, "Service not found: {}", T::service.get());
return nullptr;
}
}
inline auto services() -> modules::Services& {
return m_subsystems->services();
}
inline auto running() const -> bool {
return m_running;
}
inline void running(bool state) {
m_running = state;
}
private:
bool m_running;
display::Window *m_window;
AppMachine m_states;
Identifer m_expectedScene;
modules::Manager *m_subsystems;
};
}
#endif
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 03/07/22.
//
#ifndef FGGL_ASSETS_LOADER_HPP
#define FGGL_ASSETS_LOADER_HPP
#include <map>
#include <string>
#include <memory>
#include <functional>
#include <queue>
#include <variant>
#include "fggl/assets/types.hpp"
#include "fggl/assets/packed/adapter.hpp"
#include "fggl/data/storage.hpp"
namespace fggl::assets {
enum class LoadType {
DIRECT, // given pointer to persistent memory
STAGED, // given pointer to temp memory
PATH // given filesystem::path
};
struct ResourceRequest {
AssetGUID m_guid;
AssetTypeID m_type;
};
class Loader {
public:
constexpr const static auto service = modules::make_service("fggl::assets::Loader");
explicit inline Loader(data::Storage *storage, CheckinAdapted *checkin) : m_storage(storage), m_checkin(checkin) {}
explicit Loader(Loader *parent, data::Storage *storage) : m_parent(parent), m_storage(storage) {};
// no move, no copy.
Loader(const Loader &) = delete;
Loader &operator=(const Loader &) = delete;
Loader(Loader &&) = delete;
Loader &operator=(Loader &&) = delete;
inline void setFactory(AssetTypeID type, Checkin fn, LoadType loading = LoadType::DIRECT) {
m_factories[type] = std::make_pair(fn, loading);
}
inline void unsetFactory(AssetTypeID type) {
m_factories.erase(type);
}
inline void request(const AssetGUID &guid, const AssetTypeID &type) {
m_requests.push(ResourceRequest{guid, type});
}
void loadChain(const AssetGUID &guid, const std::string& pack = "core") {
loadChain( assets::make_asset_id_rt(pack, guid) );
}
void loadChain(AssetID asset, void* userPtr = nullptr) {
if (!m_checkin->exists(asset)) {
debug::warning("attempted to chain load unknown assetID {}", asset);
return;
}
std::queue<AssetID> loadOrder;
m_checkin->loadOrder(asset, loadOrder);
processChain(loadOrder, userPtr);
}
void processChain( std::queue<AssetID>& loadOrder, void* userPtr = nullptr ) {
if ( loadOrder.empty() ) {
return;
}
debug::info("Starting chain load");
while( !loadOrder.empty() ) {
auto it = loadOrder.front();
debug::info(" CHAIN -> loading {}", it );
load( it, userPtr );
loadOrder.pop();
}
debug::info("Ended chain loader");
}
inline void load(const AssetGUID& guid, const AssetTypeID& type, void* userPtr = nullptr, const std::string& pack = "core" ) {
auto assetID = assets::make_asset_id_rt(pack, guid);
// try checkin load first, falling back if it fails
bool checkinLoad = load( assetID, userPtr );
if ( !checkinLoad ) {
debug::info("could not perform checkin load for {} - missing loaders?", guid);
auto path = m_storage->resolvePath(data::StorageType::Data, guid);
loadDirect(path, assetID, type, pack, userPtr);
}
}
bool loadDirect( const std::filesystem::path& path, AssetID asset, AssetTypeID assetType, const std::string& pack, void* userPtr = nullptr ){
// check if we know how to load this asset (using old loaders)
auto factoryItr = m_factories.find( assetType );
if ( factoryItr == m_factories.end() ) {
debug::error("attempted to load asset with unknown type: {}", assetType.get() );
return false;
}
// perform loading
LoaderContext ctx {
.pack = pack,
.packRoot = m_checkin->getPackPath(pack),
.assetPath = path
};
// perform loading
const auto& [callback, callbackType] = factoryItr->second;
if ( callbackType == LoadType::PATH ) {
callback(this, asset, ctx, userPtr);
return true;
}
debug::log(debug::Level::error, "Tried to use old/unsupported loading method");
return false;
}
bool load(const AssetID &assetId, void* userPtr = nullptr) {
if ( !m_checkin->exists(assetId) ) {
#ifndef NDEBUG
debug::warning("asked to load unknown asset: {}", util::guid_to_string( util::GUID::make(assetId.get()) ) );
#else
debug::warning("asked to load unknown asset: {}", assetId.get());
#endif
return false;
}
const auto& record = m_checkin->find( assetId );
return loadDirect( record.m_path, assetId, record.m_assetType, record.m_pack, userPtr );
}
void progress() {
if (m_requests.empty()) {
return;
}
auto &request = m_requests.front();
load(request.m_guid, request.m_type);
m_requests.pop();
}
[[nodiscard]]
inline bool done() const {
return m_requests.empty();
}
/**
* Complete all remaining loading requests, blocking until complete.
*/
inline void finish() {
while (!done()) {
progress();
}
}
private:
Loader *m_parent = nullptr;
using Config = std::pair<Checkin, LoadType>;
data::Storage *m_storage;
CheckinAdapted *m_checkin;
std::queue<ResourceRequest> m_requests;
std::map<AssetTypeID, Config> m_factories;
};
} // namespace fggl::assets
#endif //FGGL_ASSETS_LOADER_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 27/06/22.
//
#ifndef FGGL_ASSETS_MANAGER_HPP
#define FGGL_ASSETS_MANAGER_HPP
#include <string_view>
#include <map>
#include <functional>
#include <memory>
#include <vector>
#include "fggl/debug/logging.hpp"
#include "fggl/assets/types.hpp"
#include "fggl/util/safety.hpp"
#include "fggl/modules/module.hpp"
namespace fggl::assets {
struct AssetBox {
virtual ~AssetBox() = default;
virtual void release() = 0;
};
template<typename T>
struct AssetBoxT : public AssetBox {
T* asset = nullptr;
explicit inline AssetBoxT(T* aasset) : asset(aasset) {}
// no copy: we own our resource!
AssetBoxT(const AssetBoxT&) = delete;
AssetBoxT& operator=(const AssetBoxT&) = delete;
// move OK - we can steal the asset
AssetBoxT(AssetBoxT&& other) : asset(other.asset) {
other.asset = nullptr;
}
inline ~AssetBoxT() override {
if ( asset != nullptr ) {
delete asset;
asset = nullptr;
}
}
inline void release() override {
asset = nullptr;
}
};
class AssetManager {
public:
constexpr const static modules::ServiceName service = modules::make_service("fggl::assets::Manager");
AssetManager() = default;
virtual ~AssetManager() = default;
// no move, no copy.
AssetManager(const AssetManager &) = delete;
AssetManager &operator=(const AssetManager &) = delete;
AssetManager(AssetManager &&) = delete;
AssetManager &operator=(AssetManager &&) = delete;
template<typename T>
T* get(const AssetID &guid) const {
try {
const auto &assetRecord = m_registry.at(guid);
std::shared_ptr<AssetBoxT<T>> casted = std::dynamic_pointer_cast<AssetBoxT<T>>(assetRecord);
if ( casted == nullptr ) {
debug::error("Asset type requested did not match loaded asset type!");
return nullptr;
}
return casted->asset;
} catch (std::out_of_range& e) {
return nullptr;
}
}
/**
* Pass ownership of the asset to the asset system.
*
* Once this method is called, the asset system owns the asset, and will free
* it when it is no longer required. The asset system assumes it is fully aware
* of all usages of the assets it manages via its graph.
*
* @tparam T the asset type to be managed
* @param guid the asset name
* @param assetRef the asset itself
* @return the owned asset pointer
*/
template<typename T>
T* set(const AssetID &guid, T* assetRef) {
auto ptr = std::make_shared<AssetBoxT<T>>(assetRef);
m_registry[guid] = ptr;
return (*ptr).asset;
}
inline void require(const AssetID &/*guid*/) {
//m_registry.at(guid).refCount++;
}
inline bool has(const AssetID &guid) {
return m_registry.contains(guid);
}
inline void release(const AssetGUID &/*guid*/) {
//m_registry.at(guid).refCount--;
}
inline std::vector<AssetID> known() {
std::vector<AssetID> assetList;
for ( auto& itr : m_registry ) {
assetList.push_back(itr.first);
}
return assetList;
}
private:
std::map<AssetID, std::shared_ptr<AssetBox>> m_registry;
};
} // namespace fggl::assets
#endif //FGGL_ASSETS_MANAGER_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 27/06/22.
//
#ifndef FGGL_ASSETS_MODULE_HPP
#define FGGL_ASSETS_MODULE_HPP
#include "fggl/modules/module.hpp"
#include "fggl/data/module.hpp"
#include "fggl/assets/packed/module.hpp"
#include "fggl/assets/manager.hpp"
#include "fggl/assets/loader.hpp"
namespace fggl::assets {
struct AssetFolders {
constexpr static const char *name = "fggl::assets::Folders";
constexpr static const std::array<modules::ServiceName, 2> provides = {
Loader::service,
AssetManager::service
};
constexpr static const std::array<modules::ServiceName, 2> depends = {
data::Storage::service,
CheckinAdapted::service
};
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
} // namespace fggl::assets
#endif //FGGL_ASSETS_MODULE_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 19/11/22.
//
#ifndef FGGL_ASSETS_PACKED_ADAPTER_HPP
#define FGGL_ASSETS_PACKED_ADAPTER_HPP
#include <stack>
#include <ranges>
#include <algorithm>
#include "fggl/assets/packed/direct.hpp"
#include "fggl/data/storage.hpp"
#include "fggl/ds/graph.hpp"
namespace fggl::assets {
using ResourceType = util::OpaqueName<uint64_t, struct ResourceTypeStruct>;
constexpr ResourceType from_mime(const char* mime) {
return ResourceType::make( util::hash_fnv1a_64(mime) );
};
struct ResourceRecord {
std::filesystem::path m_path;
AssetID assetID;
ResourceType m_fileType;
AssetTypeID m_assetType;
std::string m_pack;
std::vector<AssetID> m_requires;
};
struct ManifestHeader {
uint64_t assetID;
uint64_t fileType;
uint64_t assetType;
uint64_t stringSize;
};
[[maybe_unused]]
inline bool NEEDS_CHECKIN(const std::filesystem::path&, MemoryBlock) {
debug::error("attempted to load asset which does not have a valid checkin yet");
return false;
}
/**
* Adapter for Raw Checkin.
*
* For debugging/development it's a pain to have to pack assets directly. Although its much slower, it can be useful
* to be able to load non-optimised formats at runtime. This adapter allows injecting these non-optimised formats
* into the production checkin system.
*/
class CheckinAdapted {
public:
constexpr const static auto service = modules::make_service("fggl::assets::checkin::debug");
using FilePredicate = std::function<AssetTypeID(const std::filesystem::path&)>;
using FileLoader = std::function<bool(const std::filesystem::path&, MemoryBlock& block)>;
using AssetMetadata = std::function<bool(const std::string& pack, const std::filesystem::path& packRoot, ResourceRecord&)>;
CheckinAdapted(data::Storage* storage, RawCheckin* checkSvc) : m_storage(storage), m_checkin(checkSvc) {};
// asset loading
void load(AssetID asset) {
auto& assetRef = m_files.at(asset);
auto& loader = m_loaders.at(assetRef.m_fileType);
MemoryBlock block;
auto result = loader(assetRef.m_path, block);
if ( !result ) {
return;
}
m_checkin->check(assetRef.assetID, assetRef.m_assetType, block);
}
inline bool exists(AssetID asset) const {
return m_files.find(asset) != m_files.end();
}
inline std::filesystem::path getPath(AssetID asset) const {
const auto& file = m_files.at(asset);
return file.m_path;
}
void loadOrder( AssetID id, std::queue<AssetID>& order) {
m_requires.getOrderPartialRev(id, order);
}
void discover( const char* packName, bool useCache = false, bool updateCache = true) {
if ( m_packs.contains(packName) ) {
return;
}
std::string packRoot = "packs/";
auto packDir = m_storage->resolvePath( data::StorageType::Data, packRoot + packName );
discover(packDir, useCache, updateCache);
}
// asset discovery
void discover( std::filesystem::path& packDir, bool useCache=true, bool updateCache=false ) {
// note we're loading this pack
auto packName = packDir.filename();
m_packs[packName].rootDir = packDir;
if ( useCache && has_manifest(packDir)) {
// check if we've cached the search
load_manifest(packName, packDir);
return;
}
std::vector<AssetID> packFiles;
std::stack< std::filesystem::path > paths;
paths.push(packDir);
while ( !paths.empty() ) {
auto path = paths.top();
paths.pop();
if ( std::filesystem::is_directory(path) ) {
std::ranges::for_each( std::filesystem::directory_iterator{path}, [&paths](auto& path) {
paths.push(path);
});
} else if ( std::filesystem::is_regular_file(path) ) {
process_file( path, packDir, packFiles );
}
}
// update the cache and remember what pack maps to what asset
if ( updateCache ) {
save_manifest(packDir.filename(), packDir, packFiles);
}
m_packs[ packDir.filename() ].assets = packFiles;
}
inline void setLoader(ResourceType type, const CheckinAdapted::FileLoader& loader, CheckinAdapted::FilePredicate predicate = nullptr) {
assert( loader != nullptr );
m_loaders[type] = loader;
if ( predicate != nullptr ) {
m_predicates[type] = predicate;
}
}
inline void setProcessor(ResourceType type, AssetMetadata metaFunc) {
m_metadata[type] = metaFunc;
}
inline const ResourceRecord& find(AssetID asset) const {
return m_files.at(asset);
}
inline std::filesystem::path getPackPath(const std::string& name) const {
auto& info = m_packs.at(name);
return info.rootDir;
}
private:
data::Storage* m_storage;
RawCheckin* m_checkin;
std::map<AssetID, ResourceRecord> m_files;
ds::DirectedGraph<AssetID> m_requires;
struct PackInfo {
std::filesystem::path rootDir;
std::vector<AssetID> assets;
};
std::map<ResourceType, FilePredicate> m_predicates;
std::map<ResourceType, FileLoader> m_loaders;
std::map<ResourceType, AssetMetadata> m_metadata;
std::map<std::string, PackInfo> m_packs;
void process_file(std::filesystem::path path, std::filesystem::path packDir, std::vector<AssetID> packFiles) {
for( auto& [rType, pred] : m_predicates ) {
// check the predicate, is this valid?
auto aType = pred(path);
if ( aType != INVALID_ASSET_TYPE ) {
// it was, so we can finish processing
auto packName = packDir.filename();
auto relPath = std::filesystem::relative(path, packDir);
ResourceRecord rr{
.m_path = path,
.assetID = make_asset_id_rt(packName, relPath.generic_string()),
.m_fileType = rType,
.m_assetType = aType,
.m_pack = packName,
.m_requires = {}
};
// processors (for stuff like dependencies)
auto metaProc = m_metadata.find(rType);
if ( metaProc != m_metadata.end() ) {
metaProc->second( packName, packDir, rr );
}
// store the resulting data
m_files[rr.assetID] = rr;
packFiles.push_back( rr.assetID );
m_requires.addEdges( rr.assetID, rr.m_requires );
debug::trace("discovered {} ({}) from pack '{}'", rr.assetID, relPath.c_str(), packName.c_str() );
break;
}
}
}
inline bool has_manifest(const std::string& packName) {
auto packManifest = m_storage->resolvePath( data::StorageType::Cache, packName + "_manifest.bin" );
return std::filesystem::exists(packManifest);
}
void load_manifest_entry(FILE* file, const std::string& packName, const std::filesystem::path& packRoot) {
// read our entry ( id, ftype, atype, pathLen )
ManifestHeader header{};
std::fread(&header, sizeof(ManifestHeader), 1, file);
// read the relative asset path
char* relPath = new char[header.stringSize + 1];
std::fread( relPath, sizeof(char), header.stringSize, file );
relPath[ header.stringSize + 1 ] = '\0';
// read the dependency list
uint64_t nDeps = 0;
std::fread( &nDeps, sizeof(uint64_t), 1, file);
uint64_t *deps = new uint64_t[nDeps];
std::fread( deps, sizeof(uint64_t), nDeps, file );
std::vector<AssetID> depList;
for ( uint64_t i = 0; i < nDeps; ++i ) {
depList.push_back( AssetID::make(deps[i]) );
}
// calculate and verify path
std::filesystem::path fullPath = packRoot / relPath;
delete[] relPath;
if ( !std::filesystem::exists(fullPath) ) {
debug::warning("pack manifest for {} contained invalid path {}", packName, fullPath.c_str());
return;
}
// entry seems valid, load it
ResourceRecord rr {
.m_path = fullPath,
.assetID = AssetID::make(header.assetID),
.m_fileType = ResourceType::make(header.fileType),
.m_assetType = AssetTypeID::make(header.assetType),
.m_pack = packName,
.m_requires = depList
};
m_files[ rr.assetID ] = rr;
m_packs[ packRoot.filename() ].assets.push_back( rr.assetID );
m_requires.addEdges( rr.assetID, depList );
debug::trace("discovered {} ({}) from pack {}", rr.assetID.get(), fullPath.c_str(), packRoot.filename().c_str() );
}
void load_manifest(const std::string& packName, const std::filesystem::path& packRoot) {
auto packManifest = m_storage->resolvePath( data::StorageType::Cache, packName + "_manifest.bin" );
if ( !std::filesystem::exists(packManifest) ) {
return;
}
// open the manifest file and start extracting entries
FILE* file = std::fopen(packManifest.c_str(), "r");
if ( file == nullptr ) {
debug::warning("error opening manifest: {}", packManifest.c_str());
return;
}
// read the number of entries
uint64_t entries{0};
std::fread(&entries, sizeof(uint64_t), 1, file);
for ( uint64_t i = 0; i < entries; ++i) {
load_manifest_entry(file, packName, packRoot);
}
std::fclose( file );
}
void save_manifest(const std::string& packName, const std::filesystem::path& packRoot, const std::vector<AssetID>& assets) {
auto packManifest = m_storage->resolvePath( data::StorageType::Cache, packName + "_manifest.bin", true);
FILE* file = std::fopen(packManifest.c_str(), "w");
if ( file == nullptr) {
debug::warning("error saving manifest {}, missing dir?", packManifest.c_str());
return;
}
const uint64_t entries{ assets.size() };
std::fwrite( &entries, sizeof(uint64_t), 1, file);
// write the entries
for ( uint64_t i = 0; i < entries; ++i ) {
auto& assetID = assets[i];
auto& assetRecord = m_files.at(assetID);
auto relPath = std::filesystem::relative(assetRecord.m_path, packRoot);
auto relPathStr = relPath.generic_string();
ManifestHeader mh {
.assetID = assetRecord.assetID.get(),
.fileType = assetRecord.m_fileType.get(),
.assetType = assetRecord.m_assetType.get(),
.stringSize = relPathStr.size()
};
std::fwrite( &mh, sizeof(ManifestHeader), 1, file );
std::fwrite( relPathStr.c_str(), sizeof(char), relPathStr.size(), file );
}
std::fclose(file);
}
};
}
#endif //FGGL_ASSETS_PACKED_ADAPTER_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 19/11/22.
//
#ifndef FGGL_ASSETS_PACKED_DIRECT_HPP
#define FGGL_ASSETS_PACKED_DIRECT_HPP
#include <functional>
#include <map>
#include "fggl/assets/types.hpp"
#include "fggl/util/safety.hpp"
#include "fggl/util/guid.hpp"
#include "fggl/modules/module.hpp"
/**
* Raw Checkin.
*
* This is a version of the checkin system where the check-in functions are shown a raw block of memory and its their
* job to parse and load something meaningful from that.
*/
namespace fggl::assets {
class RawCheckin {
public:
constexpr const static auto service = modules::make_service("fggl::assets::checkin");
using DecodeAndCheckFunc = std::function<void(AssetGUID, MemoryBlock& block)>;
void check(AssetID, AssetTypeID, MemoryBlock& block) const;
inline void check(int64_t id, uint64_t type, MemoryBlock& block ) const {
check( AssetID::make(id), AssetTypeID::make(type), block );
}
inline void setCheckin(AssetTypeID type, DecodeAndCheckFunc func) {
m_check[type] = func;
}
private:
std::map<AssetTypeID, DecodeAndCheckFunc> m_check;
};
}
#endif //FGGL_ASSETS_PACKED_DIRECT_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 27/06/22.
//
#ifndef FGGL_ASSETS_PACKED_MODULE_HPP
#define FGGL_ASSETS_PACKED_MODULE_HPP
#include "fggl/modules/module.hpp"
#include "fggl/data/module.hpp"
#include "fggl/assets/loader.hpp"
#include "fggl/assets/packed/adapter.hpp"
#include "fggl/assets/packed/direct.hpp"
namespace fggl::assets {
struct PackedAssets {
constexpr static const char *name = "fggl::assets::packed";
constexpr static const std::array<modules::ServiceName, 2> provides = {
RawCheckin::service,
CheckinAdapted::service
};
constexpr static const std::array<modules::ServiceName, 1> depends = {
data::Storage::service
};
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
} // namespace fggl::assets
#endif //FGGL_ASSETS_PACKED_MODULE_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 19/11/22.
//
#ifndef FGGL_ASSETS_PACKED_PACKED_HPP
#define FGGL_ASSETS_PACKED_PACKED_HPP
#include <cstdint>
#include <cstdio>
#include <memory>
#include "fggl/assets/types.hpp"
#include "fggl/assets/packed/direct.hpp"
/**
* Packed file reader.
*
* Read assets stored as sequential [header,data] blocks. This reader does not care about dependencies, it assumes this
* was handled before storage (ie, asset dependencies are assumed to be stored before the asset that relies on them).
* The caller is also responsible for ensuring that assets in other archives are already loaded by the time the system
* assembles the composite assets into something usable.
*
* If either of these constraints are violated, the results are undefined.
*/
namespace fggl::assets {
struct Header {
uint64_t name;
uint64_t type;
std::size_t size;
};
bool read_header(std::FILE* stream, Header* header) {
constexpr auto headerSize = sizeof(Header);
auto readBytes = std::fread(header, headerSize, 1, stream);
return readBytes == headerSize;
}
bool read_data(std::FILE* stream, void* block, std::size_t size) {
auto readBytes = std::fread(block, size, 1, stream);
return readBytes == size;
}
void read_archive(RawCheckin* checkin, std::FILE* stream) {
while ( !std::feof(stream) ) {
Header header;
bool headRead = read_header(stream, &header);
if ( headRead && header.size != 0 ) {
// header has data
void* memBlock = std::malloc( header.size );
bool valid = read_data( stream, memBlock, header.size );
// read the data, check it in
if (valid) {
MemoryBlock block{
.data = (std::byte*)memBlock,
.size = header.size
};
checkin->check(header.name, header.type, block);
}
}
}
}
}
#endif //FGGL_ASSETS_PACKED_PACKED_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 03/07/22.
//
#ifndef FGGL_ASSETS_TYPES_HPP
#define FGGL_ASSETS_TYPES_HPP
#include <filesystem>
#include <variant>
#include <functional>
#include "fggl/util/safety.hpp"
#include "fggl/util/guid.hpp"
namespace fggl::assets {
using AssetType = util::OpaqueName<std::string_view, struct AssetTag>;
using AssetGUID = std::string;
using AssetPath = std::filesystem::path;
using AssetID = util::OpaqueName<uint64_t, struct AssetIDTag>;
template<unsigned L1, unsigned L2>
constexpr AssetID make_asset_id(const char (&pack)[L1], const char (&path)[L2]) {
auto hash = util::hash_fnv1a_64( util::cat( pack, ":", path ).c );
return AssetID::make( hash );
}
template<unsigned L1, unsigned L2, unsigned L3>
constexpr AssetID make_asset_id(const char (&pack)[L1], const char (&path)[L2], const char (&view)[L3]) {
auto hash = util::hash_fnv1a_64( util::cat( pack, ":", path, "[", view, "]").c );
return AssetID::make( hash );
}
AssetID make_asset_id_rt(const std::string &pack, const std::string &path, const std::string &view = "");
AssetID asset_from_user(const std::string &input, const std::string &pack = "core");
using AssetTypeID = util::OpaqueName<uint64_t, struct AssetTypeTag>;
constexpr auto INVALID_ASSET_TYPE = AssetTypeID::make(0);
constexpr AssetTypeID make_asset_type(const char* type) {
return AssetTypeID::make( util::hash_fnv1a_64(type) );
}
struct MemoryBlock {
std::byte *data;
std::size_t size;
template<typename T>
T* viewAs(std::size_t offset = 0) {
static_assert( std::is_standard_layout<T>::value );
return (T*)( data[offset] );
}
};
using AssetRefRaw = std::shared_ptr<void>;
struct LoaderContext {
std::string pack;
std::filesystem::path packRoot;
std::filesystem::path assetPath;
inline std::filesystem::path relParent() const {
return std::filesystem::relative( assetPath, packRoot ).parent_path();
}
inline assets::AssetID makeRef(const char* name) const {
return assets::make_asset_id_rt(pack, relParent() / name );
}
};
class Loader;
using Checkin = std::function<AssetRefRaw(Loader* loader, const AssetID &, const LoaderContext &, void* userPtr)>;
}
// formatter
template<> struct fmt::formatter<fggl::assets::AssetID> {
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const fggl::assets::AssetID & guid, FormatContext& ctx) const -> decltype(ctx.out()) {
#ifndef NDEBUG
return fmt::format_to(ctx.out(), "ASSET[{}]", guid_to_string( fggl::util::GUID::make( guid.get() ) ));
#else
return fmt::format_to(ctx.out(), "ASSET[{}]", guid.get());
#endif
}
};
#endif //FGGL_ASSETS_TYPES_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_AUDIO_AUDIO_HPP
#define FGGL_AUDIO_AUDIO_HPP
#include <string>
#include "fggl/data/storage.hpp"
#include "fggl/modules/module.hpp"
#include "fggl/assets/module.hpp"
#include "fggl/assets/packed/module.hpp"
//! backend independent audio interface
namespace fggl::audio {
/**
* AudioClip is bit of audio loaded into memory.
*
* If the sampleCount is -1, the clip is invalid.
*/
template<typename T>
struct AudioClip {
int channels = 0;
int sampleRate = 0;
int sampleCount = -1;
T *data = nullptr;
AudioClip() = default;
AudioClip(const AudioClip&) = delete;
inline ~AudioClip() {
std::free(data);
data = nullptr;
}
[[nodiscard]]
inline int size() const {
return sampleCount * sizeof(T);
}
};
using AudioClipShort = AudioClip<short>;
using AudioClipByte = AudioClip<char>;
constexpr auto ASSET_CLIP_SHORT = assets::make_asset_type("Audio:Clip:Short");
constexpr auto ASSET_CLIP_BYTE = assets::make_asset_type("Audio:Clip:Byte");
constexpr auto SERVICE_AUDIO_PLAYBACK = modules::make_service("fggl::audio::AudioService");
/**
*
* \ingroup services
*/
class AudioService {
public:
constexpr static const modules::ServiceName service = SERVICE_AUDIO_PLAYBACK;
virtual void play(const assets::AssetGUID &asset, bool looping = false) = 0;
virtual void play(const AudioClipShort &clip, bool looping = false) = 0;
virtual ~AudioService() = default;
};
} // namespace fggl::audio
#endif //FGGL_AUDIO_AUDIO_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 27/06/22.
//
#ifndef FGGL_AUDIO_NULL_AUDIO_HPP
#define FGGL_AUDIO_NULL_AUDIO_HPP
#include "fggl/audio/audio.hpp"
namespace fggl::audio {
class NullAudioService : public AudioService {
public:
NullAudioService() = default;
~NullAudioService() override = default;
NullAudioService(NullAudioService&) = delete;
NullAudioService(NullAudioService&&) = delete;
NullAudioService& operator=(const NullAudioService&) = delete;
NullAudioService& operator=(NullAudioService&&) = delete;
void play(const std::string & /*filename*/, bool /*looping = false*/) override;
void play(const AudioClipShort & /*clip*/, bool /*looping = false*/) override;
};
//! A dummy audio module that does nothing
struct NullAudio {
constexpr static const char *name = "fggl::audio::NULL";
constexpr static const std::array<modules::ServiceName, 1> provides = {
SERVICE_AUDIO_PLAYBACK
};
constexpr static const std::array<modules::ServiceName, 0> depends = {};
bool factory(modules::ServiceName, modules::Services&);
};
} // namespace fggl::audio
#endif //FGGL_AUDIO_NULL_AUDIO_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 01/05/22.
//
#ifndef FGGL_AUDIO_OPENAL_AUDIO_HPP
#define FGGL_AUDIO_OPENAL_AUDIO_HPP
#include <AL/al.h>
#include <AL/alc.h>
#include "fggl/audio/audio.hpp"
#include "fggl/assets/manager.hpp"
#include "fggl/data/storage.hpp"
#include "fggl/math/types.hpp"
#include <string>
#include <iostream>
#include <memory>
//! Audio backed by openal-soft
namespace fggl::audio::openal {
constexpr uint32_t NULL_BUFFER_ID = 0;
assets::AssetRefRaw load_vorbis(assets::Loader* loader, const assets::AssetID &, const assets::LoaderContext &, void* userPtr);
bool load_vorbis_short(std::filesystem::path path, assets::MemoryBlock& block);
assets::AssetTypeID check_vorbis( const std::filesystem::path& path );
enum class AudioType {
MONO_8 = AL_FORMAT_MONO8,
MONO_16 = AL_FORMAT_MONO16,
STEREO_8 = AL_FORMAT_STEREO8,
STEREO_16 = AL_FORMAT_STEREO16
};
static void check_error(const std::string& context) {
auto code = alGetError();
if (code == AL_NO_ERROR) {
return;
}
// now we check the error message
std::string error = "unknown";
switch (code) {
case ALC_INVALID_DEVICE: error = "Invalid Device";
break;
case ALC_INVALID_CONTEXT: error = "Invalid Context";
break;
case ALC_INVALID_ENUM: error = "Invalid enum";
break;
case ALC_INVALID_VALUE: error = "Invalid value";
break;
case ALC_OUT_OF_MEMORY: error = "Out of memory";
break;
default: error = "unknown error";
}
debug::error("OpenAL error: context={}, error={}", context, error);
}
class AudioBuffer {
public:
AudioBuffer() : m_buffer(NULL_BUFFER_ID) {
alGenBuffers(1, &m_buffer);
}
~AudioBuffer() {
alDeleteBuffers(1, &m_buffer);
}
AudioBuffer(const AudioBuffer&) = delete;
AudioBuffer(const AudioBuffer&&) = delete;
AudioBuffer& operator=(const AudioBuffer&) = delete;
AudioBuffer& operator=(const AudioBuffer&&) = delete;
inline void setData(AudioType type, void *data, ALsizei size, ALsizei frequency) {
assert( m_buffer != 0 );
assert( data != nullptr );
alBufferData(m_buffer, (ALenum) type, data, size, frequency);
}
inline ALuint value() const {
return m_buffer;
}
private:
ALuint m_buffer = 0;
};
class AudioSource {
public:
AudioSource() : m_source(0), m_splat() {
alGenSources(1, &m_source);
}
~AudioSource() {
alDeleteSources(1, &m_source);
}
AudioSource(const AudioSource& source) = delete;
AudioSource(const AudioSource&& source) = delete;
AudioSource& operator=(const AudioSource&) = delete;
AudioSource& operator=(AudioSource&&) = delete;
inline void play() {
alSourcePlay(m_source);
}
inline void stop() {
alSourceStop(m_source);
}
inline void pause() {
alSourcePause(m_source);
}
inline void rewind() {
alSourceRewind(m_source);
}
inline void play(AudioBuffer &buffer, bool looping) {
alSourcei(m_source, AL_BUFFER, buffer.value());
alSourcei(m_source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
alSourcePlay(m_source);
}
inline void play(const AudioClipShort &clip, bool looping = false);
inline void velocity(math::vec3 &value) {
alSource3f(m_source, AL_VELOCITY, value.x, value.y, value.z);
}
inline void position(math::vec3 &value) {
alSource3f(m_source, AL_POSITION, value.x, value.y, value.z);
}
void direction(math::vec3 &value) {
alSource3f(m_source, AL_DIRECTION, value.x, value.y, value.z);
}
private:
ALuint m_source;
AudioBuffer m_splat;
};
class AudioServiceOAL : public AudioService {
public:
explicit AudioServiceOAL(assets::AssetManager *assets) : m_device(alcOpenDevice(nullptr)), m_assets(assets) {
if (m_device != nullptr) {
m_context = alcCreateContext(m_device, nullptr);
alcMakeContextCurrent(m_context);
check_error("context setup");
m_defaultSource = std::make_unique<AudioSource>();
check_error("default source setup");
}
}
~AudioServiceOAL() override {
if (m_device != nullptr) {
release();
}
}
void play(const assets::AssetGUID &filename, bool looping = false) override;
void play(const AudioClipShort &clip, bool looping = false) override;
private:
ALCdevice *m_device;
ALCcontext *m_context{nullptr};
std::unique_ptr<AudioSource> m_defaultSource{nullptr};
assets::AssetManager* m_assets;
void release();
};
} // namespace fggl::audio::openal
namespace fggl::audio {
using openAL = openal::AudioServiceOAL;
} // namepace fggl::audio
#endif //FGGL_AUDIO_OPENAL_AUDIO_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 27/06/22.
//
#ifndef FGGL_AUDIO_OPENAL_MODULE_HPP
#define FGGL_AUDIO_OPENAL_MODULE_HPP
#include <array>
#include <string>
#include "fggl/assets/module.hpp"
#include "fggl/assets/packed/module.hpp"
#include "fggl/audio/audio.hpp"
#include "fggl/audio/openal/audio.hpp"
namespace fggl::audio {
constexpr auto OGG_VORBIS = assets::AssetType::make("audio/vorbis");
constexpr auto RES_OGG_VORBIS = assets::from_mime("audio/vorbis");
//! an audio module which uses openal(-soft) as a backend.
struct OpenAL {
constexpr static const char *name = "fggl::audio::OpenAL";
constexpr static const std::array<modules::ServiceName, 1> provides = {
SERVICE_AUDIO_PLAYBACK
};
constexpr static const std::array<modules::ServiceName, 2> depends = {
assets::AssetManager::service,
assets::CheckinAdapted::service
};
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
} // namespace fggl::audio
#endif //FGGL_AUDIO_OPENAL_MODULE_HPP
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 18/10/22.
//
#ifndef FGGL_DATA_ASSIMP_MODULE_HPP
#define FGGL_DATA_ASSIMP_MODULE_HPP
#include "fggl/modules/module.hpp"
#include "fggl/assets/loader.hpp"
#include "fggl/assets/packed/module.hpp"
#include "fggl/data/texture.hpp"
namespace fggl::data::models {
constexpr auto MODEL_PROVIDER = modules::make_service("fggl::data::Model");
constexpr auto ASSIMP_MODEL = assets::AssetType::make("model::assimp");
constexpr auto MIME_JPG = assets::from_mime("image/jpeg");
constexpr auto MIME_PNG = assets::from_mime("image/png");
constexpr auto MIME_OBJ = assets::from_mime("model/obj");
constexpr auto MIME_FBX = assets::from_mime("model/fbx");
constexpr auto MODEL_MULTI3D = assets::make_asset_type("model/multi3D");
constexpr auto TEXTURE_RGBA = assets::make_asset_type("texture/rgba");
// old-style loaders
assets::AssetRefRaw load_assimp_model(assets::Loader* loader, const assets::AssetID& guid, const assets::LoaderContext& data, void* userPtr);
assets::AssetRefRaw load_assimp_texture(assets::Loader* loader, const assets::AssetID& guid, const assets::LoaderContext& data, void* userPtr);
// new style loaders (textures)
bool load_tex_stb(const std::filesystem::path& filePath, assets::MemoryBlock& block);
assets::AssetTypeID is_tex_stb(const std::filesystem::path& filePath);
// new style loaders (models)
assets::AssetTypeID is_model_assimp(const std::filesystem::path& filePath);
bool extract_requirements(const std::string& packName, const std::filesystem::path& packRoot, assets::ResourceRecord& rr);
struct AssimpModule {
constexpr static const char *name = "fggl::data::Assimp";
constexpr static const std::array<modules::ServiceName, 1> provides = {
MODEL_PROVIDER
};
constexpr static const std::array<modules::ServiceName, 2> depends = {
assets::Loader::service,
assets::CheckinAdapted::service
};
static bool factory(modules::ServiceName service, modules::Services &serviceManager);
};
}
namespace fggl::data {
using AssimpLoader = models::AssimpModule;
}
#endif //FGGL_DATA_ASSIMP_MODULE_HPP