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