4253 lines
148 KiB
C
4253 lines
148 KiB
C
/*
|
|
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
|
|
Copyright (C) 2001-2023 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
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 restrictions:
|
|
|
|
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.
|
|
*/
|
|
|
|
#include "SDL.h"
|
|
#include "SDL_cpuinfo.h"
|
|
#include "SDL_endian.h"
|
|
|
|
#include "SDL_ttf.h"
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_OUTLINE_H
|
|
#include FT_STROKER_H
|
|
#include FT_GLYPH_H
|
|
#include FT_TRUETYPE_IDS_H
|
|
#include FT_IMAGE_H
|
|
|
|
/* Enable rendering with color
|
|
* Freetype may need to be compiled with FT_CONFIG_OPTION_USE_PNG */
|
|
#if defined(FT_HAS_COLOR)
|
|
# define TTF_USE_COLOR 1
|
|
#else
|
|
# define TTF_USE_COLOR 0
|
|
#endif
|
|
|
|
/* Enable Signed Distance Field rendering (requires latest FreeType version) */
|
|
#if defined(FT_RASTER_FLAG_SDF)
|
|
# define TTF_USE_SDF 1
|
|
#else
|
|
# define TTF_USE_SDF 0
|
|
#endif
|
|
|
|
#if TTF_USE_SDF
|
|
#include FT_MODULE_H
|
|
#endif
|
|
|
|
/* Enable HarfBuzz for Complex text rendering
|
|
* Freetype may need to be compiled with FT_CONFIG_OPTION_USE_HARFBUZZ */
|
|
#ifndef TTF_USE_HARFBUZZ
|
|
# define TTF_USE_HARFBUZZ 0
|
|
#endif
|
|
|
|
#if defined(SDL_BUILD_MAJOR_VERSION) && defined(SDL_COMPILE_TIME_ASSERT)
|
|
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION,
|
|
SDL_TTF_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION,
|
|
SDL_TTF_MINOR_VERSION == SDL_BUILD_MINOR_VERSION);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION,
|
|
SDL_TTF_PATCHLEVEL == SDL_BUILD_MICRO_VERSION);
|
|
#endif
|
|
|
|
#if defined(SDL_COMPILE_TIME_ASSERT)
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MAJOR_VERSION_min, SDL_TTF_MAJOR_VERSION >= 0);
|
|
/* Limited only by the need to fit in SDL_version */
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MAJOR_VERSION_max, SDL_TTF_MAJOR_VERSION <= 255);
|
|
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MINOR_VERSION_min, SDL_TTF_MINOR_VERSION >= 0);
|
|
/* Limited only by the need to fit in SDL_version */
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MINOR_VERSION_max, SDL_TTF_MINOR_VERSION <= 255);
|
|
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_PATCHLEVEL_min, SDL_TTF_PATCHLEVEL >= 0);
|
|
/* Limited by its encoding in SDL_VERSIONNUM and in the ABI versions */
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_PATCHLEVEL_max, SDL_TTF_PATCHLEVEL <= 99);
|
|
#endif
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
#include <hb.h>
|
|
#include <hb-ft.h>
|
|
|
|
/* Default configuration */
|
|
static hb_direction_t g_hb_direction = HB_DIRECTION_LTR;
|
|
static hb_script_t g_hb_script = HB_SCRIPT_UNKNOWN;
|
|
#endif
|
|
|
|
/* Harfbuzz */
|
|
int TTF_SetDirection(int direction) /* hb_direction_t */
|
|
{
|
|
#if TTF_USE_HARFBUZZ
|
|
g_hb_direction = direction;
|
|
return 0;
|
|
#else
|
|
(void) direction;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int TTF_SetScript(int script) /* hb_script_t */
|
|
{
|
|
#if TTF_USE_HARFBUZZ
|
|
g_hb_script = script;
|
|
return 0;
|
|
#else
|
|
(void) script;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
/* Round glyph to 16 bytes width and use SSE2 instructions */
|
|
#if defined(__SSE2__)
|
|
# define HAVE_SSE2_INTRINSICS 1
|
|
#endif
|
|
|
|
/* Round glyph width to 16 bytes use NEON instructions */
|
|
#if 0 /*defined(__ARM_NEON)*/
|
|
# define HAVE_NEON_INTRINSICS 1
|
|
#endif
|
|
|
|
/* Round glyph width to 8 bytes */
|
|
#define HAVE_BLIT_GLYPH_64
|
|
|
|
/* Android armeabi-v7a doesn't like int64 (Maybe all other __ARM_ARCH < 7 ?),
|
|
* un-activate it, especially if NEON isn't detected */
|
|
#if defined(__ARM_ARCH)
|
|
# if __ARM_ARCH < 8
|
|
# if defined(HAVE_BLIT_GLYPH_64)
|
|
# undef HAVE_BLIT_GLYPH_64
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
/* Default: round glyph width to 4 bytes to copy them faster */
|
|
#define HAVE_BLIT_GLYPH_32
|
|
|
|
/* Use Duff's device to unroll loops */
|
|
//#define USE_DUFFS_LOOP
|
|
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
static SDL_INLINE int hasSSE2(void)
|
|
{
|
|
static int val = -1;
|
|
if (val != -1) {
|
|
return val;
|
|
}
|
|
val = SDL_HasSSE2();
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_NEON_INTRINSICS)
|
|
static SDL_INLINE int hasNEON(void)
|
|
{
|
|
static int val = -1;
|
|
if (val != -1) {
|
|
return val;
|
|
}
|
|
val = SDL_HasNEON();
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
/* FIXME: Right now we assume the gray-scale renderer Freetype is using
|
|
supports 256 shades of gray, but we should instead key off of num_grays
|
|
in the result FT_Bitmap after the FT_Render_Glyph() call. */
|
|
#define NUM_GRAYS 256
|
|
|
|
/* x offset = cos(((90.0-12)/360) * 2 * M_PI), or 12 degree angle */
|
|
/* same value as in FT_GlyphSlot_Oblique, fixed point 16.16 */
|
|
#define GLYPH_ITALICS 0x0366AL
|
|
|
|
/* Handy routines for converting from fixed point 26.6 */
|
|
#define FT_FLOOR(X) (int)(((X) & -64) / 64)
|
|
#define FT_CEIL(X) FT_FLOOR((X) + 63)
|
|
|
|
/* Handy routine for converting to fixed point 26.6 */
|
|
#define F26Dot6(X) ((X) << 6)
|
|
|
|
/* Faster divide by 255, with same result
|
|
* in range [0; 255]: (x + 1 + (x >> 8)) >> 8
|
|
* in range [-255; 0]: (x + 255 + (x >> 8)) >> 8 */
|
|
#define DIVIDE_BY_255_SIGNED(x, sign_val) (((x) + (sign_val) + ((x)>>8)) >> 8)
|
|
|
|
/* When x positive */
|
|
#define DIVIDE_BY_255(x) DIVIDE_BY_255_SIGNED(x, 1)
|
|
|
|
|
|
#define CACHED_METRICS 0x20
|
|
|
|
#define CACHED_BITMAP 0x01
|
|
#define CACHED_PIXMAP 0x02
|
|
#define CACHED_COLOR 0x04
|
|
#define CACHED_LCD 0x08
|
|
#define CACHED_SUBPIX 0x10
|
|
|
|
|
|
typedef struct {
|
|
unsigned char *buffer; /* aligned */
|
|
int left;
|
|
int top;
|
|
int width;
|
|
int rows;
|
|
int pitch;
|
|
int is_color;
|
|
} TTF_Image;
|
|
|
|
/* Cached glyph information */
|
|
typedef struct cached_glyph {
|
|
int stored;
|
|
FT_UInt index;
|
|
TTF_Image bitmap;
|
|
TTF_Image pixmap;
|
|
int sz_left;
|
|
int sz_top;
|
|
int sz_width;
|
|
int sz_rows;
|
|
int advance;
|
|
union {
|
|
/* TTF_HINTING_LIGHT_SUBPIXEL (only pixmap) */
|
|
struct {
|
|
int lsb_minus_rsb;
|
|
int translation;
|
|
} subpixel;
|
|
/* Other hinting */
|
|
struct {
|
|
int rsb_delta;
|
|
int lsb_delta;
|
|
} kerning_smart;
|
|
};
|
|
} c_glyph;
|
|
|
|
/* Internal buffer to store positions computed by TTF_Size_Internal()
|
|
* for rendered string by Render_Line() */
|
|
typedef struct PosBuf {
|
|
FT_UInt index;
|
|
int x;
|
|
int y;
|
|
} PosBuf_t;
|
|
|
|
/* The structure used to hold internal font information */
|
|
struct _TTF_Font {
|
|
/* Freetype2 maintains all sorts of useful info itself */
|
|
FT_Face face;
|
|
|
|
/* We'll cache these ourselves */
|
|
int height;
|
|
int ascent;
|
|
int descent;
|
|
int lineskip;
|
|
|
|
/* The font style */
|
|
int style;
|
|
int outline_val;
|
|
|
|
/* Whether kerning is desired */
|
|
int allow_kerning;
|
|
int use_kerning;
|
|
|
|
/* Extra width in glyph bounds for text styles */
|
|
int glyph_overhang;
|
|
|
|
/* Information in the font for underlining */
|
|
int line_thickness;
|
|
int underline_top_row;
|
|
int strikethrough_top_row;
|
|
|
|
/* Cache for style-transformed glyphs */
|
|
c_glyph cache[256];
|
|
FT_UInt cache_index[128];
|
|
|
|
/* We are responsible for closing the font stream */
|
|
SDL_RWops *src;
|
|
int freesrc;
|
|
FT_Open_Args args;
|
|
|
|
/* Internal buffer to store positions computed by TTF_Size_Internal()
|
|
* for rendered string by Render_Line() */
|
|
PosBuf_t *pos_buf;
|
|
Uint32 pos_len;
|
|
Uint32 pos_max;
|
|
|
|
/* Hinting modes */
|
|
int ft_load_target;
|
|
int render_subpixel;
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_font_t *hb_font;
|
|
/* If HB_SCRIPT_INVALID, use global default script */
|
|
hb_script_t hb_script;
|
|
/* If HB_DIRECTION_INVALID, use global default direction */
|
|
hb_direction_t hb_direction;
|
|
#endif
|
|
int render_sdf;
|
|
|
|
/* Extra layout setting for wrapped text */
|
|
int horizontal_align;
|
|
};
|
|
|
|
/* Tell if SDL_ttf has to handle the style */
|
|
#define TTF_HANDLE_STYLE_BOLD(font) ((font)->style & TTF_STYLE_BOLD)
|
|
#define TTF_HANDLE_STYLE_ITALIC(font) ((font)->style & TTF_STYLE_ITALIC)
|
|
#define TTF_HANDLE_STYLE_UNDERLINE(font) ((font)->style & TTF_STYLE_UNDERLINE)
|
|
#define TTF_HANDLE_STYLE_STRIKETHROUGH(font) ((font)->style & TTF_STYLE_STRIKETHROUGH)
|
|
|
|
/* Font styles that does not impact glyph drawing */
|
|
#define TTF_STYLE_NO_GLYPH_CHANGE (TTF_STYLE_UNDERLINE | TTF_STYLE_STRIKETHROUGH)
|
|
|
|
/* The FreeType font engine/library */
|
|
static FT_Library library = NULL;
|
|
static int TTF_initialized = 0;
|
|
static SDL_bool TTF_byteswapped = SDL_FALSE;
|
|
|
|
#define TTF_CHECK_INITIALIZED(errval) \
|
|
if (!TTF_initialized) { \
|
|
TTF_SetError("Library not initialized"); \
|
|
return errval; \
|
|
}
|
|
|
|
#define TTF_CHECK_POINTER(p, errval) \
|
|
if (!(p)) { \
|
|
TTF_SetError("Passed a NULL pointer"); \
|
|
return errval; \
|
|
}
|
|
|
|
typedef enum {
|
|
RENDER_SOLID = 0,
|
|
RENDER_SHADED,
|
|
RENDER_BLENDED,
|
|
RENDER_LCD
|
|
} render_mode_t;
|
|
|
|
typedef enum {
|
|
STR_UTF8 = 0,
|
|
STR_TEXT,
|
|
STR_UNICODE
|
|
} str_type_t;
|
|
|
|
static int TTF_initFontMetrics(TTF_Font *font);
|
|
|
|
static int TTF_Size_Internal(TTF_Font *font, const char *text, str_type_t str_type,
|
|
int *w, int *h, int *xstart, int *ystart, int measure_width, int *extent, int *count);
|
|
|
|
#define NO_MEASUREMENT \
|
|
0, NULL, NULL
|
|
|
|
|
|
static SDL_Surface* TTF_Render_Internal(TTF_Font *font, const char *text, str_type_t str_type,
|
|
SDL_Color fg, SDL_Color bg, render_mode_t render_mode);
|
|
|
|
static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text, str_type_t str_type,
|
|
SDL_Color fg, SDL_Color bg, Uint32 wrapLength, render_mode_t render_mode);
|
|
|
|
static SDL_INLINE int Find_GlyphByIndex(TTF_Font *font, FT_UInt idx,
|
|
int want_bitmap, int want_pixmap, int want_color, int want_lcd, int want_subpixel,
|
|
int translation, c_glyph **out_glyph, TTF_Image **out_image);
|
|
|
|
static void Flush_Cache(TTF_Font *font);
|
|
|
|
#if defined(USE_DUFFS_LOOP)
|
|
|
|
/* 4-times unrolled loop */
|
|
#define DUFFS_LOOP4(pixel_copy_increment, width) \
|
|
{ int n = (width+3)/4; \
|
|
switch (width & 3) { \
|
|
case 0: do { pixel_copy_increment; /* fallthrough */ \
|
|
case 3: pixel_copy_increment; /* fallthrough */ \
|
|
case 2: pixel_copy_increment; /* fallthrough */ \
|
|
case 1: pixel_copy_increment; /* fallthrough */ \
|
|
} while (--n > 0); \
|
|
} \
|
|
}
|
|
|
|
#else
|
|
|
|
/* Don't use Duff's device to unroll loops */
|
|
#define DUFFS_LOOP(pixel_copy_increment, width) \
|
|
{ int n; \
|
|
for ( n=width; n > 0; --n ) { \
|
|
pixel_copy_increment; \
|
|
} \
|
|
}
|
|
|
|
#define DUFFS_LOOP4(pixel_copy_increment, width) \
|
|
DUFFS_LOOP(pixel_copy_increment, width)
|
|
|
|
#endif
|
|
|
|
|
|
/* Blend colored glyphs */
|
|
static SDL_INLINE void BG_Blended_Color(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
|
|
{
|
|
const Uint32 *src = (Uint32 *)image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
if (fg_alpha == 0) { /* SDL_ALPHA_OPAQUE */
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
*dst++ = *src++;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
} else {
|
|
Uint32 alpha;
|
|
Uint32 tmp;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
tmp = *src++;
|
|
alpha = tmp >> 24;
|
|
tmp &= ~0xFF000000;
|
|
alpha = fg_alpha * alpha;
|
|
alpha = DIVIDE_BY_255(alpha) << 24;
|
|
*dst++ = tmp | alpha
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Blend with LCD rendering */
|
|
static SDL_INLINE void BG_Blended_LCD(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, SDL_Color *fg)
|
|
{
|
|
const Uint32 *src = (Uint32 *)image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
Uint32 tmp, bg;
|
|
Uint32 r, g, b;
|
|
Uint8 fg_r, fg_g, fg_b;
|
|
Uint8 bg_r, bg_g, bg_b;
|
|
Uint32 bg_a;
|
|
|
|
fg_r = fg->r;
|
|
fg_g = fg->g;
|
|
fg_b = fg->b;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
tmp = *src++;
|
|
|
|
if (tmp) {
|
|
bg = *dst;
|
|
|
|
bg_a = bg & 0xff000000;
|
|
bg_r = (bg >> 16) & 0xff;
|
|
bg_g = (bg >> 8) & 0xff;
|
|
bg_b = (bg >> 0) & 0xff;
|
|
|
|
r = (tmp >> 16) & 0xff;
|
|
g = (tmp >> 8) & 0xff;
|
|
b = (tmp >> 0) & 0xff;
|
|
|
|
r = fg_r * r + bg_r * (255 - r) + 127;
|
|
r = DIVIDE_BY_255(r);
|
|
|
|
g = fg_g * g + bg_g * (255 - g) + 127;
|
|
g = DIVIDE_BY_255(g);
|
|
|
|
b = fg_b * b + bg_b * (255 - b) + 127;
|
|
b = DIVIDE_BY_255(b);
|
|
|
|
r <<= 16;
|
|
g <<= 8;
|
|
b <<= 0;
|
|
|
|
*dst = r | g | b | bg_a;
|
|
}
|
|
dst++;
|
|
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
|
|
}
|
|
|
|
#if TTF_USE_SDF
|
|
|
|
/* Blended Opaque SDF */
|
|
static SDL_INLINE void BG_Blended_Opaque_SDF(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
Uint32 s;
|
|
Uint32 d;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
d = *dst;
|
|
s = *src++ << 24;
|
|
if (s > d) {
|
|
*dst = s;
|
|
}
|
|
dst++;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
/* Blended non-opaque SDF */
|
|
static SDL_INLINE void BG_Blended_SDF(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
Uint32 s;
|
|
Uint32 d;
|
|
|
|
Uint32 tmp;
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
d = *dst;
|
|
tmp = fg_alpha * (*src++);
|
|
s = DIVIDE_BY_255(tmp) << 24;
|
|
if (s > d) {
|
|
*dst = s;
|
|
}
|
|
dst++;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
#endif /* TTF_USE_SDF */
|
|
|
|
/* Blended Opaque */
|
|
static SDL_INLINE void BG_Blended_Opaque(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
*dst++ |= *src++ << 24;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
/* Blended non-opaque */
|
|
static SDL_INLINE void BG_Blended(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
Uint32 tmp;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
tmp = fg_alpha * (*src++);
|
|
*dst++ |= DIVIDE_BY_255(tmp) << 24;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_BLIT_GLYPH_32) || defined(HAVE_BLIT_GLYPH_64)
|
|
static SDL_INLINE void BG_Blended_Opaque_32(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width / 4;
|
|
Uint32 height = image->rows;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
*dst++ |= *src++ << 24;
|
|
*dst++ |= *src++ << 24;
|
|
*dst++ |= *src++ << 24;
|
|
*dst++ |= *src++ << 24;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
static SDL_INLINE void BG_Blended_32(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width / 4;
|
|
Uint32 height = image->rows;
|
|
|
|
Uint32 tmp0, tmp1, tmp2, tmp3;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
tmp0 = fg_alpha * (*src++);
|
|
tmp1 = fg_alpha * (*src++);
|
|
tmp2 = fg_alpha * (*src++);
|
|
tmp3 = fg_alpha * (*src++);
|
|
*dst++ |= DIVIDE_BY_255(tmp0) << 24;
|
|
*dst++ |= DIVIDE_BY_255(tmp1) << 24;
|
|
*dst++ |= DIVIDE_BY_255(tmp2) << 24;
|
|
*dst++ |= DIVIDE_BY_255(tmp3) << 24;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
/* Apply: alpha_table[i] = i << 24; */
|
|
static SDL_INLINE void BG_Blended_Opaque_SSE(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const __m128i *src = (__m128i *)image->buffer;
|
|
__m128i *dst = (__m128i *)destination;
|
|
Uint32 width = image->width / 16;
|
|
Uint32 height = image->rows;
|
|
|
|
__m128i s, s0, s1, s2, s3, d0, d1, d2, d3, r0, r1, r2, r3, L, H;
|
|
const __m128i zero = _mm_setzero_si128();
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
/* Read 16 Uint8 at once and put into 4 __m128i */
|
|
s = _mm_loadu_si128(src); // load unaligned
|
|
d0 = _mm_load_si128(dst); // load
|
|
d1 = _mm_load_si128(dst + 1); // load
|
|
d2 = _mm_load_si128(dst + 2); // load
|
|
d3 = _mm_load_si128(dst + 3); // load
|
|
|
|
L = _mm_unpacklo_epi8(zero, s);
|
|
H = _mm_unpackhi_epi8(zero, s);
|
|
|
|
s0 = _mm_unpacklo_epi8(zero, L);
|
|
s1 = _mm_unpackhi_epi8(zero, L);
|
|
s2 = _mm_unpacklo_epi8(zero, H);
|
|
s3 = _mm_unpackhi_epi8(zero, H);
|
|
// already shifted by 24
|
|
r0 = _mm_or_si128(d0, s0); // or
|
|
r1 = _mm_or_si128(d1, s1); // or
|
|
r2 = _mm_or_si128(d2, s2); // or
|
|
r3 = _mm_or_si128(d3, s3); // or
|
|
|
|
_mm_store_si128(dst, r0); // store
|
|
_mm_store_si128(dst + 1, r1); // store
|
|
_mm_store_si128(dst + 2, r2); // store
|
|
_mm_store_si128(dst + 3, r3); // store
|
|
|
|
dst += 4;
|
|
src += 1;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const __m128i *)((const Uint8 *)src + srcskip);
|
|
dst = (__m128i *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
static SDL_INLINE void BG_Blended_SSE(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
|
|
{
|
|
const __m128i *src = (__m128i *)image->buffer;
|
|
__m128i *dst = (__m128i *)destination;
|
|
Uint32 width = image->width / 16;
|
|
Uint32 height = image->rows;
|
|
|
|
const __m128i alpha = _mm_set1_epi16(fg_alpha);
|
|
const __m128i one = _mm_set1_epi16(1);
|
|
const __m128i zero = _mm_setzero_si128();
|
|
__m128i s, s0, s1, s2, s3, d0, d1, d2, d3, r0, r1, r2, r3, L, H, Ls8, Hs8;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
/* Read 16 Uint8 at once and put into 4 __m128i */
|
|
s = _mm_loadu_si128(src); // load unaligned
|
|
d0 = _mm_load_si128(dst); // load
|
|
d1 = _mm_load_si128(dst + 1); // load
|
|
d2 = _mm_load_si128(dst + 2); // load
|
|
d3 = _mm_load_si128(dst + 3); // load
|
|
|
|
L = _mm_unpacklo_epi8(s, zero); // interleave, no shifting
|
|
H = _mm_unpackhi_epi8(s, zero); // enough room to multiply
|
|
|
|
/* Apply: alpha_table[i] = ((i * fg.a / 255) << 24; */
|
|
/* Divide by 255 is done as: (x + 1 + (x >> 8)) >> 8 */
|
|
|
|
L = _mm_mullo_epi16(L, alpha); // x := i * fg.a
|
|
H = _mm_mullo_epi16(H, alpha);
|
|
|
|
Ls8 = _mm_srli_epi16(L, 8); // x >> 8
|
|
Hs8 = _mm_srli_epi16(H, 8);
|
|
L = _mm_add_epi16(L, one); // x + 1
|
|
H = _mm_add_epi16(H, one);
|
|
L = _mm_add_epi16(L, Ls8); // x + 1 + (x >> 8)
|
|
H = _mm_add_epi16(H, Hs8);
|
|
L = _mm_srli_epi16(L, 8); // ((x + 1 + (x >> 8)) >> 8
|
|
H = _mm_srli_epi16(H, 8);
|
|
|
|
L = _mm_slli_epi16(L, 8); // shift << 8, so we're prepared
|
|
H = _mm_slli_epi16(H, 8); // to have final format << 24
|
|
|
|
s0 = _mm_unpacklo_epi8(zero, L);
|
|
s1 = _mm_unpackhi_epi8(zero, L);
|
|
s2 = _mm_unpacklo_epi8(zero, H);
|
|
s3 = _mm_unpackhi_epi8(zero, H);
|
|
// already shifted by 24
|
|
|
|
r0 = _mm_or_si128(d0, s0); // or
|
|
r1 = _mm_or_si128(d1, s1); // or
|
|
r2 = _mm_or_si128(d2, s2); // or
|
|
r3 = _mm_or_si128(d3, s3); // or
|
|
|
|
_mm_store_si128(dst, r0); // store
|
|
_mm_store_si128(dst + 1, r1); // store
|
|
_mm_store_si128(dst + 2, r2); // store
|
|
_mm_store_si128(dst + 3, r3); // store
|
|
|
|
dst += 4;
|
|
src += 1;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const __m128i *)((const Uint8 *)src + srcskip);
|
|
dst = (__m128i *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_NEON_INTRINSICS)
|
|
/* Apply: alpha_table[i] = i << 24; */
|
|
static SDL_INLINE void BG_Blended_Opaque_NEON(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint32 *src = (Uint32 *)image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width / 16;
|
|
Uint32 height = image->rows;
|
|
|
|
uint32x4_t s, d0, d1, d2, d3, r0, r1, r2, r3;
|
|
uint8x16x2_t sx, sx01, sx23;
|
|
uint32x4_t zero = vmovq_n_u32(0);
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
/* Read 4 Uint32 and put 16 Uint8 into uint32x4x2_t (uint8x16x2_t)
|
|
* takes advantage of vzipq_u8 which produces two lanes */
|
|
|
|
s = vld1q_u32(src); // load
|
|
d0 = vld1q_u32(dst); // load
|
|
d1 = vld1q_u32(dst + 4); // load
|
|
d2 = vld1q_u32(dst + 8); // load
|
|
d3 = vld1q_u32(dst + 12); // load
|
|
|
|
sx = vzipq_u8(zero, s); // interleave
|
|
sx01 = vzipq_u8(zero, sx.val[0]); // interleave
|
|
sx23 = vzipq_u8(zero, sx.val[1]); // interleave
|
|
// already shifted by 24
|
|
r0 = vorrq_u32(d0, sx01.val[0]); // or
|
|
r1 = vorrq_u32(d1, sx01.val[1]); // or
|
|
r2 = vorrq_u32(d2, sx23.val[0]); // or
|
|
r3 = vorrq_u32(d3, sx23.val[1]); // or
|
|
|
|
vst1q_u32(dst, r0); // store
|
|
vst1q_u32(dst + 4, r1); // store
|
|
vst1q_u32(dst + 8, r2); // store
|
|
vst1q_u32(dst + 12, r3); // store
|
|
|
|
dst += 16;
|
|
src += 4;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
/* Non-opaque, computes alpha blending on the fly */
|
|
static SDL_INLINE void BG_Blended_NEON(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
|
|
{
|
|
const Uint32 *src = (Uint32 *)image->buffer;
|
|
Uint32 *dst = destination;
|
|
Uint32 width = image->width / 16;
|
|
Uint32 height = image->rows;
|
|
|
|
uint32x4_t s, d0, d1, d2, d3, r0, r1, r2, r3;
|
|
uint16x8_t Ls8, Hs8;
|
|
uint8x16x2_t sx, sx01, sx23;
|
|
|
|
const uint16x8_t alpha = vmovq_n_u16(fg_alpha);
|
|
const uint16x8_t one = vmovq_n_u16(1);
|
|
const uint32x4_t zero = vmovq_n_u32(0);
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
/* Read 4 Uint32 and put 16 Uint8 into uint32x4x2_t (uint8x16x2_t)
|
|
* takes advantage of vzipq_u8 which produces two lanes */
|
|
|
|
s = vld1q_u32(src); // load
|
|
d0 = vld1q_u32(dst); // load
|
|
d1 = vld1q_u32(dst + 4); // load
|
|
d2 = vld1q_u32(dst + 8); // load
|
|
d3 = vld1q_u32(dst + 12); // load
|
|
|
|
sx = vzipq_u8(s, zero); // interleave, no shifting
|
|
// enough room to multiply
|
|
|
|
/* Apply: alpha_table[i] = ((i * fg.a / 255) << 24; */
|
|
/* Divide by 255 is done as: (x + 1 + (x >> 8)) >> 8 */
|
|
|
|
sx.val[0] = vmulq_u16(sx.val[0], alpha); // x := i * fg.a
|
|
sx.val[1] = vmulq_u16(sx.val[1], alpha);
|
|
|
|
Ls8 = vshrq_n_u16(sx.val[0], 8); // x >> 8
|
|
Hs8 = vshrq_n_u16(sx.val[1], 8);
|
|
|
|
sx.val[0] = vaddq_u16(sx.val[0], one); // x + 1
|
|
sx.val[1] = vaddq_u16(sx.val[1], one);
|
|
|
|
sx.val[0] = vaddq_u16(sx.val[0], Ls8); // x + 1 + (x >> 8)
|
|
sx.val[1] = vaddq_u16(sx.val[1], Hs8);
|
|
|
|
sx.val[0] = vshrq_n_u16(sx.val[0], 8); // ((x + 1 + (x >> 8)) >> 8
|
|
sx.val[1] = vshrq_n_u16(sx.val[1], 8);
|
|
|
|
sx.val[0] = vshlq_n_u16(sx.val[0], 8); // shift << 8, so we're prepared
|
|
sx.val[1] = vshlq_n_u16(sx.val[1], 8); // to have final format << 24
|
|
|
|
sx01 = vzipq_u8(zero, sx.val[0]); // interleave
|
|
sx23 = vzipq_u8(zero, sx.val[1]); // interleave
|
|
// already shifted by 24
|
|
|
|
r0 = vorrq_u32(d0, sx01.val[0]); // or
|
|
r1 = vorrq_u32(d1, sx01.val[1]); // or
|
|
r2 = vorrq_u32(d2, sx23.val[0]); // or
|
|
r3 = vorrq_u32(d3, sx23.val[1]); // or
|
|
|
|
vst1q_u32(dst, r0); // store
|
|
vst1q_u32(dst + 4, r1); // store
|
|
vst1q_u32(dst + 8, r2); // store
|
|
vst1q_u32(dst + 12, r3); // store
|
|
|
|
dst += 16;
|
|
src += 4;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static SDL_INLINE void BG(const TTF_Image *image, Uint8 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint8 *dst = destination;
|
|
Uint32 width = image->width;
|
|
Uint32 height = image->rows;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
*dst++ |= *src++;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst += dstskip;
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_BLIT_GLYPH_64)
|
|
static SDL_INLINE void BG_64(const TTF_Image *image, Uint8 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint64 *src = (Uint64 *)image->buffer;
|
|
Uint64 *dst = (Uint64 *)destination;
|
|
Uint32 width = image->width / 8;
|
|
Uint32 height = image->rows;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
*dst++ |= *src++;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint64 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint64 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#elif defined(HAVE_BLIT_GLYPH_32)
|
|
static SDL_INLINE void BG_32(const TTF_Image *image, Uint8 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint32 *src = (Uint32 *)image->buffer;
|
|
Uint32 *dst = (Uint32 *)destination;
|
|
Uint32 width = image->width / 4;
|
|
Uint32 height = image->rows;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
*dst++ |= *src++;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
static SDL_INLINE void BG_SSE(const TTF_Image *image, Uint8 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const __m128i *src = (__m128i *)image->buffer;
|
|
__m128i *dst = (__m128i *)destination;
|
|
Uint32 width = image->width / 16;
|
|
Uint32 height = image->rows;
|
|
|
|
__m128i s, d, r;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
s = _mm_loadu_si128(src); // load unaligned
|
|
d = _mm_load_si128(dst); // load
|
|
r = _mm_or_si128(d, s); // or
|
|
_mm_store_si128(dst, r); // store
|
|
src += 1;
|
|
dst += 1;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const __m128i *)((const Uint8 *)src + srcskip);
|
|
dst = (__m128i *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_NEON_INTRINSICS)
|
|
static SDL_INLINE void BG_NEON(const TTF_Image *image, Uint8 *destination, Sint32 srcskip, Uint32 dstskip)
|
|
{
|
|
const Uint8 *src = image->buffer;
|
|
Uint8 *dst = destination;
|
|
Uint32 width = image->width / 16;
|
|
Uint32 height = image->rows;
|
|
|
|
uint8x16_t s, d, r;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
s = vld1q_u8(src); // load
|
|
d = vld1q_u8(dst); // load
|
|
r = vorrq_u8(d, s); // or
|
|
vst1q_u8(dst, r); // store
|
|
src += 16;
|
|
dst += 16;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint8 *)((const Uint8 *)src + srcskip);
|
|
dst += dstskip;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Underline and Strikethrough style. Draw a line at the given row. */
|
|
static void Draw_Line(TTF_Font *font, const SDL_Surface *textbuf, int column, int row, int line_width, int line_thickness, Uint32 color, const render_mode_t render_mode)
|
|
{
|
|
int tmp = row + line_thickness - textbuf->h;
|
|
int x_offset = column * textbuf->format->BytesPerPixel;
|
|
Uint8 *dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch + x_offset;
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_direction_t hb_direction = font->hb_direction;
|
|
|
|
if (hb_direction == HB_DIRECTION_INVALID) {
|
|
hb_direction = g_hb_direction;
|
|
}
|
|
|
|
/* No Underline/Strikethrough style if direction is vertical */
|
|
if (hb_direction == HB_DIRECTION_TTB || hb_direction == HB_DIRECTION_BTT) {
|
|
return;
|
|
}
|
|
#else
|
|
(void) font;
|
|
#endif
|
|
|
|
/* Not needed because of "font->height = SDL_max(font->height, bottom_row);".
|
|
* But if you patch to render textshaping and break line in middle of a cluster,
|
|
* (which is a bad usage and a corner case), you need this to prevent out of bounds.
|
|
* You can get an "ystart" for the "whole line", which is different (and smaller)
|
|
* than the ones of the "splitted lines". */
|
|
if (tmp > 0) {
|
|
line_thickness -= tmp;
|
|
}
|
|
/* Previous case also happens with SDF (render_sdf) , because 'spread' property
|
|
* requires to increase 'ystart'
|
|
* Check for valid value anyway. */
|
|
if (line_thickness <= 0) {
|
|
return;
|
|
}
|
|
|
|
/* Wrapped mode with an unbroken line: 'line_width' is greater that 'textbuf->w' */
|
|
line_width = SDL_min(line_width, textbuf->w);
|
|
|
|
if (render_mode == RENDER_BLENDED || render_mode == RENDER_LCD) {
|
|
while (line_thickness--) {
|
|
SDL_memset4(dst, color, line_width);
|
|
dst += textbuf->pitch;
|
|
}
|
|
} else {
|
|
while (line_thickness--) {
|
|
SDL_memset(dst, color, line_width);
|
|
dst += textbuf->pitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void clip_glyph(int *_x, int *_y, TTF_Image *image, const SDL_Surface *textbuf, int is_lcd)
|
|
{
|
|
int above_w;
|
|
int above_h;
|
|
int x = *_x;
|
|
int y = *_y;
|
|
|
|
int srcbpp = 1;
|
|
if (image->is_color || is_lcd) {
|
|
srcbpp = 4;
|
|
}
|
|
|
|
/* Don't go below x=0 */
|
|
if (x < 0) {
|
|
int tmp = -x;
|
|
x = 0;
|
|
image->width -= tmp;
|
|
image->buffer += srcbpp * tmp;
|
|
}
|
|
/* Don't go above textbuf->w */
|
|
above_w = x + image->width - textbuf->w;
|
|
if (above_w > 0) {
|
|
image->width -= above_w;
|
|
}
|
|
/* Don't go below y=0 */
|
|
if (y < 0) {
|
|
int tmp = -y;
|
|
y = 0;
|
|
image->rows -= tmp;
|
|
image->buffer += tmp * image->pitch;
|
|
}
|
|
/* Don't go above textbuf->h */
|
|
above_h = y + image->rows - textbuf->h;
|
|
if (above_h > 0) {
|
|
image->rows -= above_h;
|
|
}
|
|
/* Could be negative if (x > textbuf->w), or if (x + width < 0) */
|
|
image->width = SDL_max(0, image->width);
|
|
image->rows = SDL_max(0, image->rows);
|
|
|
|
/* After 'image->width' clipping:
|
|
* Make sure 'rows' is also 0, so it doesn't break USE_DUFFS_LOOP */
|
|
if (image->width == 0) {
|
|
image->rows = 0;
|
|
}
|
|
|
|
*_x = x;
|
|
*_y = y;
|
|
}
|
|
|
|
/* Glyph width is rounded, dst addresses are aligned, src addresses are not aligned */
|
|
static int Get_Alignment(void)
|
|
{
|
|
#if defined(HAVE_NEON_INTRINSICS)
|
|
if (hasNEON()) {
|
|
return 16;
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
if (hasSSE2()) {
|
|
return 16;
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_BLIT_GLYPH_64)
|
|
return 8;
|
|
#elif defined(HAVE_BLIT_GLYPH_32)
|
|
return 4;
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-value"
|
|
#endif
|
|
#define BUILD_RENDER_LINE(NAME, IS_BLENDED, IS_BLENDED_OPAQUE, IS_LCD, WB_WP_WC, WS, BLIT_GLYPH_BLENDED_OPAQUE_OPTIM, BLIT_GLYPH_BLENDED_OPTIM, BLIT_GLYPH_OPTIM) \
|
|
\
|
|
static SDL_INLINE \
|
|
int Render_Line_##NAME(TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color *fg) \
|
|
{ \
|
|
const int alignment = Get_Alignment() - 1; \
|
|
const int bpp = ((IS_BLENDED || IS_LCD) ? 4 : 1); \
|
|
unsigned int i; \
|
|
Uint8 fg_alpha = (fg ? fg->a : 0); \
|
|
for (i = 0; i < font->pos_len; i++) { \
|
|
FT_UInt idx = font->pos_buf[i].index; \
|
|
int x = font->pos_buf[i].x; \
|
|
int y = font->pos_buf[i].y; \
|
|
TTF_Image *image; \
|
|
\
|
|
if (Find_GlyphByIndex(font, idx, WB_WP_WC, WS, x & 63, NULL, &image) == 0) { \
|
|
int above_w, above_h; \
|
|
Uint32 dstskip; \
|
|
Sint32 srcskip; /* Can be negative */ \
|
|
Uint8 *dst; \
|
|
int remainder; \
|
|
Uint8 *saved_buffer = image->buffer; \
|
|
int saved_width = image->width; \
|
|
image->buffer += alignment; \
|
|
/* Position updated after glyph rendering */ \
|
|
x = xstart + FT_FLOOR(x) + image->left; \
|
|
y = ystart + FT_FLOOR(y) - image->top; \
|
|
\
|
|
/* Make sure glyph is inside textbuf */ \
|
|
above_w = x + image->width - textbuf->w; \
|
|
above_h = y + image->rows - textbuf->h; \
|
|
\
|
|
if (x >= 0 && y >= 0 && above_w <= 0 && above_h <= 0) { \
|
|
/* Most often, glyph is inside textbuf */ \
|
|
/* Compute dst */ \
|
|
dst = (Uint8 *)textbuf->pixels + y * textbuf->pitch + x * bpp; \
|
|
/* Align dst, get remainder, shift & align glyph width */ \
|
|
remainder = ((uintptr_t)dst & alignment) / bpp; \
|
|
dst = (Uint8 *)((uintptr_t)dst & ~alignment); \
|
|
image->buffer -= remainder; \
|
|
image->width = (image->width + remainder + alignment) & ~alignment; \
|
|
/* Compute srcskip, dstskip */ \
|
|
srcskip = image->pitch - image->width; \
|
|
dstskip = textbuf->pitch - image->width * bpp; \
|
|
/* Render glyph at (x, y) with optimized copy functions */ \
|
|
if (IS_LCD) { \
|
|
image->buffer = saved_buffer; \
|
|
image->buffer += alignment; \
|
|
image->width = saved_width; \
|
|
dst = (Uint8 *)textbuf->pixels + y * textbuf->pitch + x * bpp; \
|
|
/* Compute srcskip, dstskip */ \
|
|
srcskip = image->pitch - 4 * image->width; \
|
|
dstskip = textbuf->pitch - image->width * bpp; \
|
|
BG_Blended_LCD(image, (Uint32 *)dst, srcskip, dstskip, fg); \
|
|
} else if (!IS_BLENDED || image->is_color == 0) { \
|
|
if (IS_BLENDED_OPAQUE) { \
|
|
BLIT_GLYPH_BLENDED_OPAQUE_OPTIM(image, (Uint32 *)dst, srcskip, dstskip); \
|
|
} else if (IS_BLENDED) { \
|
|
BLIT_GLYPH_BLENDED_OPTIM(image, (Uint32 *)dst, srcskip, dstskip, fg_alpha); \
|
|
} else { \
|
|
BLIT_GLYPH_OPTIM(image, dst, srcskip, dstskip); \
|
|
} \
|
|
} else if (IS_BLENDED && image->is_color) { \
|
|
image->buffer = saved_buffer; \
|
|
image->buffer += alignment; \
|
|
image->width = saved_width; \
|
|
dst = (Uint8 *)textbuf->pixels + y * textbuf->pitch + x * bpp; \
|
|
/* Compute srcskip, dstskip */ \
|
|
srcskip = image->pitch - 4 * image->width; \
|
|
dstskip = textbuf->pitch - image->width * bpp; \
|
|
BG_Blended_Color(image, (Uint32 *)dst, srcskip, dstskip, fg_alpha); \
|
|
} \
|
|
/* restore modification */ \
|
|
image->width = saved_width; \
|
|
} else { \
|
|
/* Modify a copy, and clip it */ \
|
|
TTF_Image image_clipped = *image; \
|
|
/* Intersect image glyph at (x,y) with textbuf */ \
|
|
clip_glyph(&x, &y, &image_clipped, textbuf, IS_LCD); \
|
|
/* Compute dst */ \
|
|
dst = (Uint8 *)textbuf->pixels + y * textbuf->pitch + x * bpp; \
|
|
/* Compute srcskip, dstskip */ \
|
|
srcskip = image_clipped.pitch - image_clipped.width; \
|
|
dstskip = textbuf->pitch - image_clipped.width * bpp; \
|
|
/* Render glyph at (x, y) */ \
|
|
if (IS_LCD) { \
|
|
srcskip -= 3 * image_clipped.width; \
|
|
BG_Blended_LCD(&image_clipped, (Uint32 *)dst, srcskip, dstskip, fg); \
|
|
} else if (!IS_BLENDED || image->is_color == 0) { \
|
|
if (IS_BLENDED_OPAQUE) { \
|
|
BG_Blended_Opaque(&image_clipped, (Uint32 *)dst, srcskip, dstskip); \
|
|
} else if (IS_BLENDED) { \
|
|
BG_Blended(&image_clipped, (Uint32 *)dst, srcskip, dstskip, fg_alpha); \
|
|
} else { \
|
|
BG(&image_clipped, dst, srcskip, dstskip); \
|
|
} \
|
|
} else if (IS_BLENDED && image->is_color) { \
|
|
srcskip -= 3 * image_clipped.width; \
|
|
BG_Blended_Color(&image_clipped, (Uint32 *)dst, srcskip, dstskip, fg_alpha); \
|
|
} \
|
|
} \
|
|
image->buffer = saved_buffer; \
|
|
} else { \
|
|
return -1; \
|
|
} \
|
|
} \
|
|
\
|
|
return 0; \
|
|
} \
|
|
\
|
|
|
|
#define BITMAP CACHED_BITMAP, 0, 0, 0
|
|
#define PIXMAP 0, CACHED_PIXMAP, 0, 0
|
|
#define COLOR 0, 0, CACHED_COLOR, 0
|
|
#define LCD 0, 0, 0, CACHED_LCD
|
|
|
|
#define SUBPIX CACHED_SUBPIX
|
|
|
|
/* BUILD_RENDER_LINE(NAME, IS_BLENDED, IS_BLENDED_OPAQUE, WANT_BITMAP_PIXMAP_COLOR_LCD, WANT_SUBPIXEL, BLIT_GLYPH_BLENDED_OPAQUE_OPTIM, BLIT_GLYPH_BLENDED_OPTIM, BLIT_GLYPH_OPTIM) */
|
|
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
BUILD_RENDER_LINE(SSE_Shaded , 0, 0, 0, PIXMAP, 0 , , , BG_SSE )
|
|
BUILD_RENDER_LINE(SSE_Blended , 1, 0, 0, COLOR, 0 , , BG_Blended_SSE , )
|
|
BUILD_RENDER_LINE(SSE_Blended_Opaque , 1, 1, 0, COLOR, 0 , BG_Blended_Opaque_SSE , , )
|
|
BUILD_RENDER_LINE(SSE_Solid , 0, 0, 0, BITMAP, 0 , , , BG_SSE )
|
|
BUILD_RENDER_LINE(SSE_Shaded_SP , 0, 0, 0, PIXMAP, SUBPIX, , , BG_SSE )
|
|
BUILD_RENDER_LINE(SSE_Blended_SP , 1, 0, 0, COLOR, SUBPIX, , BG_Blended_SSE , )
|
|
BUILD_RENDER_LINE(SSE_Blended_Opaque_SP , 1, 1, 0, COLOR, SUBPIX, BG_Blended_Opaque_SSE , , )
|
|
BUILD_RENDER_LINE(SSE_LCD , 0, 0, 1, LCD, 0, , , )
|
|
BUILD_RENDER_LINE(SSE_LCD_SP , 0, 0, 1, LCD, SUBPIX, , , )
|
|
#endif
|
|
|
|
#if defined(HAVE_NEON_INTRINSICS)
|
|
BUILD_RENDER_LINE(NEON_Shaded , 0, 0, 0, PIXMAP, 0 , , , BG_NEON )
|
|
BUILD_RENDER_LINE(NEON_Blended , 1, 0, 0, COLOR, 0 , , BG_Blended_NEON, )
|
|
BUILD_RENDER_LINE(NEON_Blended_Opaque , 1, 1, 0, COLOR, 0 , BG_Blended_Opaque_NEON, , )
|
|
BUILD_RENDER_LINE(NEON_Solid , 0, 0, 0, BITMAP, 0 , , , BG_NEON )
|
|
BUILD_RENDER_LINE(NEON_Shaded_SP , 0, 0, 0, PIXMAP, SUBPIX, , , BG_NEON )
|
|
BUILD_RENDER_LINE(NEON_Blended_SP , 1, 0, 0, COLOR, SUBPIX, , BG_Blended_NEON, )
|
|
BUILD_RENDER_LINE(NEON_Blended_Opaque_SP, 1, 1, 0, COLOR, SUBPIX, BG_Blended_Opaque_NEON, , )
|
|
BUILD_RENDER_LINE(NEON_LCD , 0, 0, 1, LCD, 0 , , , )
|
|
BUILD_RENDER_LINE(NEON_LCD_SP , 0, 0, 1, LCD, SUBPIX, , , )
|
|
#endif
|
|
|
|
#if defined(HAVE_BLIT_GLYPH_64)
|
|
BUILD_RENDER_LINE(64_Shaded , 0, 0, 0, PIXMAP, 0 , , , BG_64 )
|
|
BUILD_RENDER_LINE(64_Blended , 1, 0, 0, COLOR, 0 , , BG_Blended_32 , )
|
|
BUILD_RENDER_LINE(64_Blended_Opaque , 1, 1, 0, COLOR, 0 , BG_Blended_Opaque_32 , , )
|
|
BUILD_RENDER_LINE(64_Solid , 0, 0, 0, BITMAP, 0 , , , BG_64 )
|
|
BUILD_RENDER_LINE(64_Shaded_SP , 0, 0, 0, PIXMAP, SUBPIX, , , BG_64 )
|
|
BUILD_RENDER_LINE(64_Blended_SP , 1, 0, 0, COLOR, SUBPIX, , BG_Blended_32 , )
|
|
BUILD_RENDER_LINE(64_Blended_Opaque_SP , 1, 1, 0, COLOR, SUBPIX, BG_Blended_Opaque_32 , , )
|
|
BUILD_RENDER_LINE(64_LCD , 0, 0, 1, LCD, 0 , , , )
|
|
BUILD_RENDER_LINE(64_LCD_SP , 0, 0, 1, LCD, SUBPIX, , , )
|
|
#elif defined(HAVE_BLIT_GLYPH_32)
|
|
BUILD_RENDER_LINE(32_Shaded , 0, 0, 0, PIXMAP, 0 , , , BG_32 )
|
|
BUILD_RENDER_LINE(32_Blended , 1, 0, 0, COLOR, 0 , , BG_Blended_32 , )
|
|
BUILD_RENDER_LINE(32_Blended_Opaque , 1, 1, 0, COLOR, 0 , BG_Blended_Opaque_32 , , )
|
|
BUILD_RENDER_LINE(32_Solid , 0, 0, 0, BITMAP, 0 , , , BG_32 )
|
|
BUILD_RENDER_LINE(32_Shaded_SP , 0, 0, 0, PIXMAP, SUBPIX, , , BG_32 )
|
|
BUILD_RENDER_LINE(32_Blended_SP , 1, 0, 0, COLOR, SUBPIX, , BG_Blended_32 , )
|
|
BUILD_RENDER_LINE(32_Blended_Opaque_SP , 1, 1, 0, COLOR, SUBPIX, BG_Blended_Opaque_32 , , )
|
|
BUILD_RENDER_LINE(32_LCD , 0, 0, 1, LCD, 0 , , , )
|
|
BUILD_RENDER_LINE(32_LCD_SP , 0, 0, 1, LCD, SUBPIX, , , )
|
|
#else
|
|
BUILD_RENDER_LINE(8_Shaded , 0, 0, 0, PIXMAP, 0 , , , BG )
|
|
BUILD_RENDER_LINE(8_Blended , 1, 0, 0, COLOR, 0 , , BG_Blended , )
|
|
BUILD_RENDER_LINE(8_Blended_Opaque , 1, 1, 0, COLOR, 0 , BG_Blended_Opaque , , )
|
|
BUILD_RENDER_LINE(8_Solid , 0, 0, 0, BITMAP, 0 , , , BG )
|
|
BUILD_RENDER_LINE(8_Shaded_SP , 0, 0, 0, PIXMAP, SUBPIX, , , BG )
|
|
BUILD_RENDER_LINE(8_Blended_SP , 1, 0, 0, COLOR, SUBPIX, , BG_Blended , )
|
|
BUILD_RENDER_LINE(8_Blended_Opaque_SP , 1, 1, 0, COLOR, SUBPIX, BG_Blended_Opaque , , )
|
|
BUILD_RENDER_LINE(8_LCD , 0, 0, 1, LCD, 0 , , , )
|
|
BUILD_RENDER_LINE(8_LCD_SP , 0, 0, 1, LCD, SUBPIX, , , )
|
|
#endif
|
|
|
|
|
|
#if TTF_USE_SDF
|
|
static int (*Render_Line_SDF_Shaded)(TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color *fg) = NULL;
|
|
BUILD_RENDER_LINE(SDF_Blended , 1, 0, 0, COLOR, 0 , , BG_Blended_SDF , )
|
|
BUILD_RENDER_LINE(SDF_Blended_Opaque , 1, 1, 0, COLOR, 0 , BG_Blended_Opaque_SDF , , )
|
|
static int (*Render_Line_SDF_Solid)(TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color *fg) = NULL;
|
|
static int (*Render_Line_SDF_Shaded_SP)(TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color *fg) = NULL;
|
|
BUILD_RENDER_LINE(SDF_Blended_SP , 1, 0, 0, COLOR, SUBPIX, , BG_Blended_SDF , )
|
|
BUILD_RENDER_LINE(SDF_Blended_Opaque_SP , 1, 1, 0, COLOR, SUBPIX, BG_Blended_Opaque_SDF , , )
|
|
static int (*Render_Line_SDF_LCD)(TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color *fg) = NULL;
|
|
static int (*Render_Line_SDF_LCD_SP)(TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color *fg) = NULL;
|
|
#endif
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
static SDL_INLINE int Render_Line(const render_mode_t render_mode, int subpixel, TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color fg)
|
|
{
|
|
/* Render line (pos_buf) to textbuf at (xstart, ystart) */
|
|
|
|
/* Subpixel with RENDER_SOLID doesn't make sense. */
|
|
/* (and 'cached->subpixel.translation' would need to distinguish bitmap/pixmap). */
|
|
int is_opaque = (fg.a == SDL_ALPHA_OPAQUE);
|
|
|
|
#define Call_Specific_Render_Line(NAME) \
|
|
if (render_mode == RENDER_SHADED) { \
|
|
if (subpixel == 0) { \
|
|
return Render_Line_##NAME##_Shaded(font, textbuf, xstart, ystart, NULL); \
|
|
} else { \
|
|
return Render_Line_##NAME##_Shaded_SP(font, textbuf, xstart, ystart, NULL); \
|
|
} \
|
|
} else if (render_mode == RENDER_BLENDED) { \
|
|
if (is_opaque) { \
|
|
if (subpixel == 0) { \
|
|
return Render_Line_##NAME##_Blended_Opaque(font, textbuf, xstart, ystart, NULL); \
|
|
} else { \
|
|
return Render_Line_##NAME##_Blended_Opaque_SP(font, textbuf, xstart, ystart, NULL); \
|
|
} \
|
|
} else { \
|
|
if (subpixel == 0) { \
|
|
return Render_Line_##NAME##_Blended(font, textbuf, xstart, ystart, &fg); \
|
|
} else { \
|
|
return Render_Line_##NAME##_Blended_SP(font, textbuf, xstart, ystart, &fg); \
|
|
} \
|
|
} \
|
|
} else if (render_mode == RENDER_LCD) { \
|
|
if (subpixel == 0) { \
|
|
return Render_Line_##NAME##_LCD(font, textbuf, xstart, ystart, &fg); \
|
|
} else { \
|
|
return Render_Line_##NAME##_LCD_SP(font, textbuf, xstart, ystart, &fg); \
|
|
} \
|
|
} else { \
|
|
return Render_Line_##NAME##_Solid(font, textbuf, xstart, ystart, NULL); \
|
|
}
|
|
|
|
#if TTF_USE_SDF
|
|
if (font->render_sdf && render_mode == RENDER_BLENDED) {
|
|
Call_Specific_Render_Line(SDF)
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_NEON_INTRINSICS)
|
|
if (hasNEON()) {
|
|
Call_Specific_Render_Line(NEON)
|
|
}
|
|
#endif
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
if (hasSSE2()) {
|
|
Call_Specific_Render_Line(SSE)
|
|
}
|
|
#endif
|
|
#if defined(HAVE_BLIT_GLYPH_64)
|
|
Call_Specific_Render_Line(64)
|
|
#elif defined(HAVE_BLIT_GLYPH_32)
|
|
Call_Specific_Render_Line(32)
|
|
#else
|
|
Call_Specific_Render_Line(8)
|
|
#endif
|
|
}
|
|
|
|
#ifndef SIZE_MAX
|
|
# define SIZE_MAX ((size_t)(-1))
|
|
#endif
|
|
|
|
#if !SDL_VERSION_ATLEAST(2, 23, 1)
|
|
SDL_FORCE_INLINE int
|
|
compat_size_add_overflow (size_t a, size_t b, size_t *ret)
|
|
{
|
|
if (b > SIZE_MAX - a) {
|
|
return -1;
|
|
}
|
|
*ret = a + b;
|
|
return 0;
|
|
}
|
|
|
|
SDL_FORCE_INLINE int
|
|
compat_size_mul_overflow (size_t a, size_t b, size_t *ret)
|
|
{
|
|
if (a != 0 && b > SIZE_MAX / a) {
|
|
return -1;
|
|
}
|
|
*ret = a * b;
|
|
return 0;
|
|
}
|
|
|
|
#define SDL_size_add_overflow(a, b, r) compat_size_add_overflow(a, b, r)
|
|
#define SDL_size_mul_overflow(a, b, r) compat_size_mul_overflow(a, b, r)
|
|
#endif /* SDL < 2.23.1 */
|
|
|
|
/* Create a surface with memory:
|
|
* - pitch is rounded to alignment
|
|
* - address is aligned
|
|
*
|
|
* If format is 4 bytes per pixel, bgcolor is used to initialize each
|
|
* 4-byte word in the image data.
|
|
*
|
|
* Otherwise, the low byte of format is used to initialize each byte
|
|
* in the image data.
|
|
*/
|
|
static SDL_Surface *AllocateAlignedPixels(size_t width, size_t height, SDL_PixelFormatEnum format, Uint32 bgcolor)
|
|
{
|
|
const size_t alignment = Get_Alignment() - 1;
|
|
const size_t bytes_per_pixel = SDL_BYTESPERPIXEL(format);
|
|
SDL_Surface *textbuf = NULL;
|
|
size_t size;
|
|
size_t data_bytes;
|
|
void *pixels, *ptr;
|
|
size_t pitch;
|
|
|
|
/* Worst case at the end of line pulling 'alignment' extra blank pixels */
|
|
if (width > SDL_MAX_SINT32 ||
|
|
height > SDL_MAX_SINT32 ||
|
|
SDL_size_add_overflow(width, alignment, &pitch) ||
|
|
SDL_size_mul_overflow(pitch, bytes_per_pixel, &pitch) ||
|
|
SDL_size_add_overflow(pitch, alignment, &pitch) ||
|
|
pitch > SDL_MAX_SINT32) {
|
|
return NULL;
|
|
}
|
|
pitch &= ~alignment;
|
|
|
|
if (SDL_size_mul_overflow(height, pitch, &data_bytes) ||
|
|
SDL_size_add_overflow(data_bytes, sizeof (void *) + alignment, &size) ||
|
|
size > SDL_MAX_SINT32) {
|
|
/* Overflow... */
|
|
return NULL;
|
|
}
|
|
|
|
ptr = SDL_malloc(size);
|
|
if (ptr == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* address is aligned */
|
|
pixels = (void *)(((uintptr_t)ptr + sizeof(void *) + alignment) & ~alignment);
|
|
((void **)pixels)[-1] = ptr;
|
|
|
|
textbuf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, (int)width, (int)height, 0, (int)pitch, format);
|
|
if (textbuf == NULL) {
|
|
SDL_free(ptr);
|
|
return NULL;
|
|
}
|
|
|
|
/* Let SDL handle the memory allocation */
|
|
textbuf->flags &= ~SDL_PREALLOC;
|
|
textbuf->flags |= SDL_SIMD_ALIGNED;
|
|
|
|
if (bytes_per_pixel == 4) {
|
|
SDL_memset4(pixels, bgcolor, data_bytes / 4);
|
|
}
|
|
else {
|
|
SDL_memset(pixels, (bgcolor & 0xff), data_bytes);
|
|
}
|
|
|
|
return textbuf;
|
|
}
|
|
|
|
static SDL_Surface* Create_Surface_Solid(int width, int height, SDL_Color fg, Uint32 *color)
|
|
{
|
|
SDL_Surface *textbuf = AllocateAlignedPixels(width, height, SDL_PIXELFORMAT_INDEX8, 0);
|
|
if (textbuf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Underline/Strikethrough color style */
|
|
*color = 1;
|
|
|
|
/* Fill the palette: 1 is foreground */
|
|
{
|
|
SDL_Palette *palette = textbuf->format->palette;
|
|
palette->colors[0].r = 255 - fg.r;
|
|
palette->colors[0].g = 255 - fg.g;
|
|
palette->colors[0].b = 255 - fg.b;
|
|
palette->colors[1].r = fg.r;
|
|
palette->colors[1].g = fg.g;
|
|
palette->colors[1].b = fg.b;
|
|
palette->colors[1].a = fg.a;
|
|
}
|
|
|
|
SDL_SetColorKey(textbuf, SDL_TRUE, 0);
|
|
|
|
return textbuf;
|
|
}
|
|
|
|
static SDL_Surface* Create_Surface_Shaded(int width, int height, SDL_Color fg, SDL_Color bg, Uint32 *color)
|
|
{
|
|
SDL_Surface *textbuf = AllocateAlignedPixels(width, height, SDL_PIXELFORMAT_INDEX8, 0);
|
|
Uint8 bg_alpha = bg.a;
|
|
if (textbuf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Underline/Strikethrough color style */
|
|
*color = NUM_GRAYS - 1;
|
|
|
|
/* Support alpha blending */
|
|
if (fg.a != SDL_ALPHA_OPAQUE || bg.a != SDL_ALPHA_OPAQUE) {
|
|
SDL_SetSurfaceBlendMode(textbuf, SDL_BLENDMODE_BLEND);
|
|
|
|
/* Would disturb alpha palette */
|
|
if (bg.a == SDL_ALPHA_OPAQUE) {
|
|
bg.a = 0;
|
|
}
|
|
}
|
|
|
|
/* Fill the palette with NUM_GRAYS levels of shading from bg to fg */
|
|
{
|
|
SDL_Palette *palette = textbuf->format->palette;
|
|
int rdiff = fg.r - bg.r;
|
|
int gdiff = fg.g - bg.g;
|
|
int bdiff = fg.b - bg.b;
|
|
int adiff = fg.a - bg.a;
|
|
int sign_r = (rdiff >= 0) ? 1 : 255;
|
|
int sign_g = (gdiff >= 0) ? 1 : 255;
|
|
int sign_b = (bdiff >= 0) ? 1 : 255;
|
|
int sign_a = (adiff >= 0) ? 1 : 255;
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_GRAYS; ++i) {
|
|
/* Compute color[i] = (i * color_diff / 255) */
|
|
int tmp_r = i * rdiff;
|
|
int tmp_g = i * gdiff;
|
|
int tmp_b = i * bdiff;
|
|
int tmp_a = i * adiff;
|
|
palette->colors[i].r = (Uint8)(bg.r + DIVIDE_BY_255_SIGNED(tmp_r, sign_r));
|
|
palette->colors[i].g = (Uint8)(bg.g + DIVIDE_BY_255_SIGNED(tmp_g, sign_g));
|
|
palette->colors[i].b = (Uint8)(bg.b + DIVIDE_BY_255_SIGNED(tmp_b, sign_b));
|
|
palette->colors[i].a = (Uint8)(bg.a + DIVIDE_BY_255_SIGNED(tmp_a, sign_a));
|
|
}
|
|
|
|
/* Make sure background has the correct alpha value */
|
|
palette->colors[0].a = bg_alpha;
|
|
}
|
|
|
|
return textbuf;
|
|
}
|
|
|
|
static SDL_Surface *Create_Surface_Blended(int width, int height, SDL_Color fg, Uint32 *color)
|
|
{
|
|
SDL_Surface *textbuf = NULL;
|
|
Uint32 bgcolor;
|
|
|
|
/* Background color: initialize with fg and 0 alpha */
|
|
bgcolor = (fg.r << 16) | (fg.g << 8) | fg.b;
|
|
|
|
/* Underline/Strikethrough color style */
|
|
*color = bgcolor | (fg.a << 24);
|
|
|
|
/* Create the target surface if required */
|
|
if (width != 0) {
|
|
textbuf = AllocateAlignedPixels(width, height, SDL_PIXELFORMAT_ARGB8888, bgcolor);
|
|
if (textbuf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Support alpha blending */
|
|
if (fg.a != SDL_ALPHA_OPAQUE) {
|
|
SDL_SetSurfaceBlendMode(textbuf, SDL_BLENDMODE_BLEND);
|
|
}
|
|
}
|
|
|
|
return textbuf;
|
|
}
|
|
|
|
static SDL_Surface* Create_Surface_LCD(int width, int height, SDL_Color fg, SDL_Color bg, Uint32 *color)
|
|
{
|
|
SDL_Surface *textbuf = NULL;
|
|
Uint32 bgcolor;
|
|
|
|
/* Background color */
|
|
bgcolor = (bg.a << 24) | (bg.r << 16) | (bg.g << 8) | bg.b;
|
|
|
|
/* Underline/Strikethrough color style */
|
|
*color = (bg.a << 24) | (fg.r << 16) | (fg.g << 8) | fg.b;
|
|
|
|
/* Create the target surface if required */
|
|
if (width != 0) {
|
|
textbuf = AllocateAlignedPixels(width, height, SDL_PIXELFORMAT_ARGB8888, bgcolor);
|
|
if (textbuf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Support alpha blending */
|
|
if (bg.a != SDL_ALPHA_OPAQUE) {
|
|
SDL_SetSurfaceBlendMode(textbuf, SDL_BLENDMODE_BLEND);
|
|
}
|
|
}
|
|
|
|
return textbuf;
|
|
}
|
|
|
|
|
|
/* rcg06192001 get linked library's version. */
|
|
const SDL_version* TTF_Linked_Version(void)
|
|
{
|
|
static SDL_version linked_version;
|
|
SDL_TTF_VERSION(&linked_version);
|
|
return &linked_version;
|
|
}
|
|
|
|
/* This function tells the library whether UNICODE text is generally
|
|
byteswapped. A UNICODE BOM character at the beginning of a string
|
|
will override this setting for that string. */
|
|
void TTF_ByteSwappedUNICODE(SDL_bool swapped)
|
|
{
|
|
TTF_byteswapped = swapped;
|
|
}
|
|
|
|
#if defined(USE_FREETYPE_ERRORS)
|
|
static void TTF_SetFTError(const char *msg, FT_Error error)
|
|
{
|
|
#undef FTERRORS_H_
|
|
#define FT_ERRORDEF(e, v, s) { e, s },
|
|
#define FT_ERROR_START_LIST {
|
|
#define FT_ERROR_END_LIST { 0, NULL } };
|
|
const struct
|
|
{
|
|
int err_code;
|
|
const char *err_msg;
|
|
} ft_errors[] =
|
|
#include FT_ERRORS_H
|
|
|
|
unsigned int i;
|
|
const char *err_msg = NULL;
|
|
|
|
for (i = 0; i < sizeof (ft_errors) / sizeof (ft_errors[0]); ++i) {
|
|
if (error == ft_errors[i].err_code) {
|
|
err_msg = ft_errors[i].err_msg;
|
|
break;
|
|
}
|
|
}
|
|
if (!err_msg) {
|
|
err_msg = "unknown FreeType error";
|
|
}
|
|
TTF_SetError("%s: %s", msg, err_msg);
|
|
}
|
|
#else
|
|
#define TTF_SetFTError(msg, error) TTF_SetError(msg)
|
|
#endif /* USE_FREETYPE_ERRORS */
|
|
|
|
int TTF_Init(void)
|
|
{
|
|
int status = 0;
|
|
|
|
/* Some debug to know how it gets compiled */
|
|
#if 0
|
|
int duffs = 0, sse2 = 0, neon = 0, compil_sse2 = 0, compil_neon = 0;
|
|
# if defined(USE_DUFFS_LOOP)
|
|
duffs = 1;
|
|
# endif
|
|
# if defined(HAVE_SSE2_INTRINSICS)
|
|
sse2 = hasSSE2();
|
|
compil_sse2 = 1;
|
|
# endif
|
|
# if defined(HAVE_NEON_INTRINSICS)
|
|
neon = hasNEON();
|
|
compil_neon = 1;
|
|
# endif
|
|
SDL_Log("SDL_ttf: hasSSE2=%d hasNEON=%d alignment=%d duffs_loop=%d compil_sse2=%d compil_neon=%d",
|
|
sse2, neon, Get_Alignment(), duffs, compil_sse2, compil_neon);
|
|
|
|
SDL_Log("Sizeof TTF_Image: %d c_glyph: %d TTF_Font: %d", sizeof (TTF_Image), sizeof (c_glyph), sizeof (TTF_Font));
|
|
#endif
|
|
|
|
if (!TTF_initialized) {
|
|
FT_Error error = FT_Init_FreeType(&library);
|
|
if (error) {
|
|
TTF_SetFTError("Couldn't init FreeType engine", error);
|
|
status = -1;
|
|
}
|
|
}
|
|
if (status == 0) {
|
|
++TTF_initialized;
|
|
#if TTF_USE_SDF
|
|
# if 0
|
|
/* Set various properties of the renderers. */
|
|
int spread = 4;
|
|
int overlaps = 0;
|
|
FT_Property_Set( library, "bsdf", "spread", &spread);
|
|
FT_Property_Set( library, "sdf", "spread", &spread);
|
|
FT_Property_Set( library, "sdf", "overlaps", &overlaps);
|
|
# endif
|
|
#endif
|
|
}
|
|
return status;
|
|
}
|
|
|
|
SDL_COMPILE_TIME_ASSERT(FT_Int, sizeof(int) == sizeof(FT_Int)); /* just in case. */
|
|
void TTF_GetFreeTypeVersion(int *major, int *minor, int *patch)
|
|
{
|
|
FT_Library_Version(library, major, minor, patch);
|
|
}
|
|
|
|
void TTF_GetHarfBuzzVersion(int *major, int *minor, int *patch)
|
|
{
|
|
unsigned int hb_major = 0;
|
|
unsigned int hb_minor = 0;
|
|
unsigned int hb_micro = 0;
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_version(&hb_major, &hb_minor, &hb_micro);
|
|
#endif
|
|
if (major) {
|
|
*major = (int)hb_major;
|
|
}
|
|
if (minor) {
|
|
*minor = (int)hb_minor;
|
|
}
|
|
if (patch) {
|
|
*patch = (int)hb_micro;
|
|
}
|
|
}
|
|
|
|
static unsigned long RWread(
|
|
FT_Stream stream,
|
|
unsigned long offset,
|
|
unsigned char *buffer,
|
|
unsigned long count
|
|
)
|
|
{
|
|
SDL_RWops *src;
|
|
|
|
src = (SDL_RWops *)stream->descriptor.pointer;
|
|
SDL_RWseek(src, (int)offset, RW_SEEK_SET);
|
|
if (count == 0) {
|
|
return 0;
|
|
}
|
|
return (unsigned long)SDL_RWread(src, buffer, 1, (int)count);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontIndexDPIRW(SDL_RWops *src, int freesrc, int ptsize, long index, unsigned int hdpi, unsigned int vdpi)
|
|
{
|
|
TTF_Font *font;
|
|
FT_Error error;
|
|
FT_Face face;
|
|
FT_Stream stream;
|
|
FT_CharMap found;
|
|
Sint64 position;
|
|
int i;
|
|
|
|
if (!TTF_initialized) {
|
|
TTF_SetError("Library not initialized");
|
|
if (src && freesrc) {
|
|
SDL_RWclose(src);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
if (!src) {
|
|
TTF_SetError("Passed a NULL font source");
|
|
return NULL;
|
|
}
|
|
|
|
/* Check to make sure we can seek in this stream */
|
|
position = SDL_RWtell(src);
|
|
if (position < 0) {
|
|
TTF_SetError("Can't seek in stream");
|
|
if (freesrc) {
|
|
SDL_RWclose(src);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
font = (TTF_Font *)SDL_malloc(sizeof (*font));
|
|
if (font == NULL) {
|
|
TTF_SetError("Out of memory");
|
|
if (freesrc) {
|
|
SDL_RWclose(src);
|
|
}
|
|
return NULL;
|
|
}
|
|
SDL_memset(font, 0, sizeof (*font));
|
|
|
|
font->src = src;
|
|
font->freesrc = freesrc;
|
|
|
|
stream = (FT_Stream)SDL_malloc(sizeof (*stream));
|
|
if (stream == NULL) {
|
|
TTF_SetError("Out of memory");
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
SDL_memset(stream, 0, sizeof (*stream));
|
|
|
|
stream->read = RWread;
|
|
stream->descriptor.pointer = src;
|
|
stream->pos = (unsigned long)position;
|
|
stream->size = (unsigned long)(SDL_RWsize(src) - position);
|
|
|
|
font->args.flags = FT_OPEN_STREAM;
|
|
font->args.stream = stream;
|
|
|
|
error = FT_Open_Face(library, &font->args, index, &font->face);
|
|
if (error || font->face == NULL) {
|
|
TTF_SetFTError("Couldn't load font file", error);
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
face = font->face;
|
|
|
|
/* Set charmap for loaded font */
|
|
found = 0;
|
|
#if 0 /* Font debug code */
|
|
for (i = 0; i < face->num_charmaps; i++) {
|
|
FT_CharMap charmap = face->charmaps[i];
|
|
SDL_Log("Found charmap: platform id %d, encoding id %d", charmap->platform_id, charmap->encoding_id);
|
|
}
|
|
#endif
|
|
if (!found) {
|
|
for (i = 0; i < face->num_charmaps; i++) {
|
|
FT_CharMap charmap = face->charmaps[i];
|
|
if (charmap->platform_id == 3 && charmap->encoding_id == 10) { /* UCS-4 Unicode */
|
|
found = charmap;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
for (i = 0; i < face->num_charmaps; i++) {
|
|
FT_CharMap charmap = face->charmaps[i];
|
|
if ((charmap->platform_id == 3 && charmap->encoding_id == 1) /* Windows Unicode */
|
|
|| (charmap->platform_id == 3 && charmap->encoding_id == 0) /* Windows Symbol */
|
|
|| (charmap->platform_id == 2 && charmap->encoding_id == 1) /* ISO Unicode */
|
|
|| (charmap->platform_id == 0)) { /* Apple Unicode */
|
|
found = charmap;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
/* If this fails, continue using the default charmap */
|
|
FT_Set_Charmap(face, found);
|
|
}
|
|
|
|
/* Set the default font style */
|
|
font->style = TTF_STYLE_NORMAL;
|
|
font->outline_val = 0;
|
|
font->ft_load_target = FT_LOAD_TARGET_NORMAL;
|
|
TTF_SetFontKerning(font, 1);
|
|
|
|
font->pos_len = 0;
|
|
font->pos_max = 16;
|
|
font->pos_buf = (PosBuf_t *)SDL_malloc(font->pos_max * sizeof (font->pos_buf[0]));
|
|
if (! font->pos_buf) {
|
|
TTF_SetError("Out of memory");
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
font->hb_font = hb_ft_font_create(face, NULL);
|
|
if (font->hb_font == NULL) {
|
|
TTF_SetError("Cannot create harfbuzz font");
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
|
|
/* Default load-flags of hb_ft_font_create is no-hinting.
|
|
* So unless you call hb_ft_font_set_load_flags to match what flags you use for rendering,
|
|
* you will get mismatching advances and raster. */
|
|
hb_ft_font_set_load_flags(font->hb_font, FT_LOAD_DEFAULT | font->ft_load_target);
|
|
|
|
/* By default the script / direction are inherited from global variables */
|
|
font->hb_script = HB_SCRIPT_INVALID;
|
|
font->hb_direction = HB_DIRECTION_INVALID;
|
|
#endif
|
|
|
|
if (TTF_SetFontSizeDPI(font, ptsize, hdpi, vdpi) < 0) {
|
|
TTF_SetFTError("Couldn't set font size", error);
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
return font;
|
|
}
|
|
|
|
int TTF_SetFontSizeDPI(TTF_Font *font, int ptsize, unsigned int hdpi, unsigned int vdpi)
|
|
{
|
|
FT_Face face = font->face;
|
|
FT_Error error;
|
|
|
|
/* Make sure that our font face is scalable (global metrics) */
|
|
if (FT_IS_SCALABLE(face)) {
|
|
/* Set the character size using the provided DPI. If a zero DPI
|
|
* is provided, then the other DPI setting will be used. If both
|
|
* are zero, then Freetype's default 72 DPI will be used. */
|
|
error = FT_Set_Char_Size(face, 0, ptsize * 64, hdpi, vdpi);
|
|
if (error) {
|
|
TTF_SetFTError("Couldn't set font size", error);
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* Non-scalable font case. ptsize determines which family
|
|
* or series of fonts to grab from the non-scalable format.
|
|
* It is not the point size of the font. */
|
|
if (face->num_fixed_sizes <= 0) {
|
|
TTF_SetError("Couldn't select size : no num_fixed_sizes");
|
|
return -1;
|
|
}
|
|
|
|
/* within [0; num_fixed_sizes - 1] */
|
|
ptsize = SDL_max(ptsize, 0);
|
|
ptsize = SDL_min(ptsize, face->num_fixed_sizes - 1);
|
|
|
|
error = FT_Select_Size(face, ptsize);
|
|
if (error) {
|
|
TTF_SetFTError("Couldn't select size", error);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (TTF_initFontMetrics(font) < 0) {
|
|
TTF_SetError("Cannot initialize metrics");
|
|
return -1;
|
|
}
|
|
|
|
Flush_Cache(font);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
/* Call when size or variations settings on underlying FT_Face change. */
|
|
hb_ft_font_changed(font->hb_font);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TTF_SetFontSize(TTF_Font *font, int ptsize)
|
|
{
|
|
return TTF_SetFontSizeDPI(font, ptsize, 0, 0);
|
|
}
|
|
|
|
/* Update font parameter depending on a style change */
|
|
static int TTF_initFontMetrics(TTF_Font *font)
|
|
{
|
|
FT_Face face = font->face;
|
|
int underline_offset;
|
|
|
|
/* Make sure that our font face is scalable (global metrics) */
|
|
if (FT_IS_SCALABLE(face)) {
|
|
/* Get the scalable font metrics for this font */
|
|
FT_Fixed scale = face->size->metrics.y_scale;
|
|
font->ascent = FT_CEIL(FT_MulFix(face->ascender, scale));
|
|
font->descent = FT_CEIL(FT_MulFix(face->descender, scale));
|
|
font->height = FT_CEIL(FT_MulFix(face->ascender - face->descender, scale));
|
|
font->lineskip = FT_CEIL(FT_MulFix(face->height, scale));
|
|
underline_offset = FT_FLOOR(FT_MulFix(face->underline_position, scale));
|
|
font->line_thickness = FT_FLOOR(FT_MulFix(face->underline_thickness, scale));
|
|
} else {
|
|
/* Get the font metrics for this font, for the selected size */
|
|
font->ascent = FT_CEIL(face->size->metrics.ascender);
|
|
font->descent = FT_CEIL(face->size->metrics.descender);
|
|
font->height = FT_CEIL(face->size->metrics.height);
|
|
font->lineskip = FT_CEIL(face->size->metrics.height);
|
|
/* face->underline_position and face->underline_height are only
|
|
* relevant for scalable formats (see freetype.h FT_FaceRec) */
|
|
underline_offset = font->descent / 2;
|
|
font->line_thickness = 1;
|
|
}
|
|
|
|
if (font->line_thickness < 1) {
|
|
font->line_thickness = 1;
|
|
}
|
|
|
|
font->underline_top_row = font->ascent - underline_offset - 1;
|
|
font->strikethrough_top_row = font->height / 2;
|
|
|
|
/* Adjust OutlineStyle, only for scalable fonts */
|
|
/* TTF_Size(): increase w and h by 2 * outline_val, translate positionning by 1 * outline_val */
|
|
if (font->outline_val > 0) {
|
|
int fo = font->outline_val;
|
|
font->line_thickness += 2 * fo;
|
|
font->underline_top_row -= fo;
|
|
font->strikethrough_top_row -= fo;
|
|
}
|
|
|
|
/* Robustness: no negative values allowed */
|
|
font->underline_top_row = SDL_max(0, font->underline_top_row);
|
|
font->strikethrough_top_row = SDL_max(0, font->strikethrough_top_row);
|
|
|
|
/* Update height according to the needs of the underline style */
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
int bottom_row = font->underline_top_row + font->line_thickness;
|
|
font->height = SDL_max(font->height, bottom_row);
|
|
}
|
|
/* Update height according to the needs of the strikethrough style */
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
int bottom_row = font->strikethrough_top_row + font->line_thickness;
|
|
font->height = SDL_max(font->height, bottom_row);
|
|
}
|
|
|
|
#if defined(DEBUG_FONTS)
|
|
SDL_Log("Font metrics:");
|
|
SDL_Log("ascent = %d, descent = %d", font->ascent, font->descent);
|
|
SDL_Log("height = %d, lineskip = %d", font->height, font->lineskip);
|
|
SDL_Log("underline_offset = %d, line_thickness = %d", underline_offset, font->line_thickness);
|
|
SDL_Log("underline_top_row = %d, strikethrough_top_row = %d", font->underline_top_row, font->strikethrough_top_row);
|
|
SDL_Log("scalable=%d fixed_sizes=%d", FT_IS_SCALABLE(face), FT_HAS_FIXED_SIZES(face));
|
|
#endif
|
|
|
|
font->glyph_overhang = face->size->metrics.y_ppem / 10;
|
|
|
|
return 0;
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontDPIRW( SDL_RWops *src, int freesrc, int ptsize, unsigned int hdpi, unsigned int vdpi )
|
|
{
|
|
return TTF_OpenFontIndexDPIRW(src, freesrc, ptsize, 0, hdpi, vdpi);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontIndexRW( SDL_RWops *src, int freesrc, int ptsize, long index )
|
|
{
|
|
return TTF_OpenFontIndexDPIRW(src, freesrc, ptsize, index, 0, 0);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontIndexDPI( const char *file, int ptsize, long index, unsigned int hdpi, unsigned int vdpi )
|
|
{
|
|
SDL_RWops *rw = SDL_RWFromFile(file, "rb");
|
|
if ( rw == NULL ) {
|
|
return NULL;
|
|
}
|
|
return TTF_OpenFontIndexDPIRW(rw, 1, ptsize, index, hdpi, vdpi);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontRW(SDL_RWops *src, int freesrc, int ptsize)
|
|
{
|
|
return TTF_OpenFontIndexRW(src, freesrc, ptsize, 0);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontDPI(const char *file, int ptsize, unsigned int hdpi, unsigned int vdpi)
|
|
{
|
|
return TTF_OpenFontIndexDPI(file, ptsize, 0, hdpi, vdpi);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFontIndex(const char *file, int ptsize, long index)
|
|
{
|
|
return TTF_OpenFontIndexDPI(file, ptsize, index, 0, 0);
|
|
}
|
|
|
|
TTF_Font* TTF_OpenFont(const char *file, int ptsize)
|
|
{
|
|
return TTF_OpenFontIndex(file, ptsize, 0);
|
|
}
|
|
|
|
static void Flush_Glyph_Image(TTF_Image *image) {
|
|
if (image->buffer) {
|
|
SDL_free(image->buffer);
|
|
image->buffer = NULL;
|
|
}
|
|
}
|
|
|
|
static void Flush_Glyph(c_glyph *glyph)
|
|
{
|
|
glyph->stored = 0;
|
|
glyph->index = 0;
|
|
Flush_Glyph_Image(&glyph->pixmap);
|
|
Flush_Glyph_Image(&glyph->bitmap);
|
|
}
|
|
|
|
static void Flush_Cache(TTF_Font *font)
|
|
{
|
|
int i;
|
|
int size = sizeof (font->cache) / sizeof (font->cache[0]);
|
|
|
|
for (i = 0; i < size; ++i) {
|
|
if (font->cache[i].stored) {
|
|
Flush_Glyph(&font->cache[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static FT_Error Load_Glyph(TTF_Font *font, c_glyph *cached, int want, int translation)
|
|
{
|
|
const int alignment = Get_Alignment() - 1;
|
|
FT_GlyphSlot slot;
|
|
FT_Error error;
|
|
|
|
int ft_load = FT_LOAD_DEFAULT | font->ft_load_target;
|
|
|
|
#if TTF_USE_COLOR
|
|
if (want & CACHED_COLOR) {
|
|
ft_load |= FT_LOAD_COLOR;
|
|
}
|
|
#endif
|
|
|
|
error = FT_Load_Glyph(font->face, cached->index, ft_load);
|
|
if (error) {
|
|
goto ft_failure;
|
|
}
|
|
|
|
/* Get our glyph shortcut */
|
|
slot = font->face->glyph;
|
|
|
|
if (want & CACHED_LCD) {
|
|
if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
|
|
TTF_SetError("LCD mode not possible with bitmap font");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Get the glyph metrics, always needed */
|
|
if (cached->stored == 0) {
|
|
cached->sz_left = slot->bitmap_left;
|
|
cached->sz_top = slot->bitmap_top;
|
|
cached->sz_rows = slot->bitmap.rows;
|
|
cached->sz_width = slot->bitmap.width;
|
|
|
|
/* Current version of freetype is 2.9.1, but on older freetype (2.8.1) this can be 0.
|
|
* Try to get them from 'FT_Glyph_Metrics' */
|
|
if (cached->sz_left == 0 && cached->sz_top == 0 && cached->sz_rows == 0 && cached->sz_width == 0) {
|
|
FT_Glyph_Metrics *metrics = &slot->metrics;
|
|
if (metrics) {
|
|
int minx = FT_FLOOR(metrics->horiBearingX);
|
|
int maxx = FT_CEIL(metrics->horiBearingX + metrics->width);
|
|
int maxy = FT_FLOOR(metrics->horiBearingY);
|
|
int miny = maxy - FT_CEIL(metrics->height);
|
|
|
|
cached->sz_left = minx;
|
|
cached->sz_top = maxy;
|
|
cached->sz_rows = maxy - miny;
|
|
cached->sz_width = maxx - minx;
|
|
}
|
|
}
|
|
|
|
/* All FP 26.6 are 'long' but 'int' should be engouh */
|
|
cached->advance = (int)slot->metrics.horiAdvance; /* FP 26.6 */
|
|
|
|
if (font->render_subpixel == 0) {
|
|
/* FT KERNING_MODE_SMART */
|
|
cached->kerning_smart.rsb_delta = (int)slot->rsb_delta; /* FP 26.6 */
|
|
cached->kerning_smart.lsb_delta = (int)slot->lsb_delta; /* FP 26.6 */
|
|
} else {
|
|
/* FT LCD_MODE_LIGHT_SUBPIXEL */
|
|
cached->subpixel.lsb_minus_rsb = (int)(slot->lsb_delta - slot->rsb_delta); /* FP 26.6 */
|
|
cached->subpixel.translation = 0; /* FP 26.6 */
|
|
}
|
|
|
|
#if defined(DEBUG_FONTS)
|
|
SDL_Log("Index=%d sz_left=%d sz_top=%d sz_width=%d sz_rows=%d advance=%d is_outline=%d is_bitmap=%d",
|
|
cached->index, cached->sz_left, cached->sz_top, cached->sz_width, cached->sz_rows, cached->advance,
|
|
slot->format == FT_GLYPH_FORMAT_OUTLINE, slot->format == FT_GLYPH_FORMAT_BITMAP);
|
|
#endif
|
|
|
|
/* Adjust for bold text */
|
|
if (TTF_HANDLE_STYLE_BOLD(font)) {
|
|
cached->sz_width += font->glyph_overhang;
|
|
cached->advance += F26Dot6(font->glyph_overhang);
|
|
}
|
|
|
|
/* Adjust for italic text */
|
|
if (TTF_HANDLE_STYLE_ITALIC(font) && slot->format == FT_GLYPH_FORMAT_OUTLINE) {
|
|
cached->sz_width += (GLYPH_ITALICS * font->height) >> 16;
|
|
}
|
|
|
|
/* Adjust for subpixel */
|
|
if (font->render_subpixel) {
|
|
cached->sz_width += 1;
|
|
}
|
|
|
|
/* Adjust for SDF */
|
|
if (font->render_sdf) {
|
|
/* Default 'spread' property */
|
|
cached->sz_width += 2 * 8;
|
|
cached->sz_rows += 2 * 8;
|
|
}
|
|
|
|
cached->stored |= CACHED_METRICS;
|
|
}
|
|
|
|
if (((want & CACHED_BITMAP) && !(cached->stored & CACHED_BITMAP)) ||
|
|
((want & CACHED_PIXMAP) && !(cached->stored & CACHED_PIXMAP)) ||
|
|
((want & CACHED_COLOR) && !(cached->stored & CACHED_COLOR)) ||
|
|
((want & CACHED_LCD) && !(cached->stored & CACHED_LCD)) ||
|
|
(want & CACHED_SUBPIX)
|
|
) {
|
|
const int mono = (want & CACHED_BITMAP);
|
|
TTF_Image *dst = (mono ? &cached->bitmap : &cached->pixmap);
|
|
FT_Glyph glyph = NULL;
|
|
FT_Bitmap *src;
|
|
FT_Render_Mode ft_render_mode;
|
|
|
|
if (mono) {
|
|
ft_render_mode = FT_RENDER_MODE_MONO;
|
|
} else {
|
|
ft_render_mode = FT_RENDER_MODE_NORMAL;
|
|
#if TTF_USE_SDF
|
|
if ((want & CACHED_COLOR) && font->render_sdf) {
|
|
ft_render_mode = FT_RENDER_MODE_SDF;
|
|
}
|
|
#endif
|
|
if ((want & CACHED_LCD)) {
|
|
ft_render_mode = FT_RENDER_MODE_LCD;
|
|
}
|
|
}
|
|
|
|
/* Subpixel translation, flush previous datas */
|
|
if (want & CACHED_SUBPIX) {
|
|
Flush_Glyph_Image(&cached->pixmap);
|
|
FT_Outline_Translate(&slot->outline, translation, 0 );
|
|
cached->subpixel.translation = translation;
|
|
}
|
|
|
|
/* Handle the italic style, only for scalable fonts */
|
|
if (TTF_HANDLE_STYLE_ITALIC(font) && slot->format == FT_GLYPH_FORMAT_OUTLINE) {
|
|
FT_Matrix shear;
|
|
shear.xx = 1 << 16;
|
|
shear.xy = GLYPH_ITALICS;
|
|
shear.yx = 0;
|
|
shear.yy = 1 << 16;
|
|
FT_Outline_Transform(&slot->outline, &shear);
|
|
}
|
|
|
|
/* Render as outline */
|
|
if ((font->outline_val > 0 && slot->format == FT_GLYPH_FORMAT_OUTLINE)
|
|
|| slot->format == FT_GLYPH_FORMAT_BITMAP) {
|
|
|
|
FT_BitmapGlyph bitmap_glyph;
|
|
|
|
error = FT_Get_Glyph(slot, &glyph);
|
|
if (error) {
|
|
goto ft_failure;
|
|
}
|
|
|
|
if (font->outline_val > 0) {
|
|
FT_Stroker stroker;
|
|
error = FT_Stroker_New(library, &stroker);
|
|
if (error) {
|
|
goto ft_failure;
|
|
}
|
|
FT_Stroker_Set(stroker, font->outline_val * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
|
FT_Glyph_Stroke(&glyph, stroker, 1 /* delete the original glyph */);
|
|
FT_Stroker_Done(stroker);
|
|
}
|
|
|
|
/* Render the glyph */
|
|
error = FT_Glyph_To_Bitmap(&glyph, ft_render_mode, 0, 1);
|
|
if (error) {
|
|
FT_Done_Glyph(glyph);
|
|
goto ft_failure;
|
|
}
|
|
|
|
/* Access bitmap content by typecasting */
|
|
bitmap_glyph = (FT_BitmapGlyph) glyph;
|
|
src = &bitmap_glyph->bitmap;
|
|
|
|
/* Get new metrics, from bitmap */
|
|
dst->left = bitmap_glyph->left;
|
|
dst->top = bitmap_glyph->top;
|
|
} else {
|
|
/* Render the glyph */
|
|
error = FT_Render_Glyph(slot, ft_render_mode);
|
|
if (error) {
|
|
goto ft_failure;
|
|
}
|
|
|
|
/* Access bitmap from slot */
|
|
src = &slot->bitmap;
|
|
|
|
/* Get new metrics, from slot */
|
|
dst->left = slot->bitmap_left;
|
|
dst->top = slot->bitmap_top;
|
|
}
|
|
|
|
/* Common metrics */
|
|
dst->width = src->width;
|
|
dst->rows = src->rows;
|
|
dst->buffer = NULL;
|
|
|
|
/* FT can make small size glyph of 'width == 0', and 'rows != 0'.
|
|
* Make sure 'rows' is also 0, so it doesn't break USE_DUFFS_LOOP */
|
|
if (dst->width == 0) {
|
|
dst->rows = 0;
|
|
}
|
|
|
|
/* Adjust for bold text */
|
|
if (TTF_HANDLE_STYLE_BOLD(font)) {
|
|
dst->width += font->glyph_overhang;
|
|
}
|
|
|
|
/* Compute pitch: glyph is padded right to be able to read an 'aligned' size expanding on the right */
|
|
dst->pitch = dst->width + alignment;
|
|
#if TTF_USE_COLOR
|
|
if (src->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
|
dst->pitch += 3 * dst->width;
|
|
}
|
|
#endif
|
|
if (src->pixel_mode == FT_PIXEL_MODE_LCD) {
|
|
dst->pitch += 3 * dst->width;
|
|
}
|
|
|
|
if (dst->rows != 0) {
|
|
unsigned int i;
|
|
|
|
/* Glyph buffer is NOT aligned,
|
|
* Extra width so it can read an 'aligned' size expanding on the left */
|
|
dst->buffer = (unsigned char *)SDL_malloc(alignment + dst->pitch * dst->rows);
|
|
|
|
if (!dst->buffer) {
|
|
error = FT_Err_Out_Of_Memory;
|
|
goto ft_failure;
|
|
}
|
|
|
|
/* Memset */
|
|
SDL_memset(dst->buffer, 0, alignment + dst->pitch * dst->rows);
|
|
|
|
/* Shift, so that the glyph is decoded centered */
|
|
dst->buffer += alignment;
|
|
|
|
/* FT_Render_Glyph() and .fon fonts always generate a two-color (black and white)
|
|
* glyphslot surface, even when rendered in FT_RENDER_MODE_NORMAL. */
|
|
/* FT_IS_SCALABLE() means that the face contains outline glyphs, but does not imply
|
|
* that outline is rendered as 8-bit grayscale, because embedded bitmap/graymap is
|
|
* preferred (see FT_LOAD_DEFAULT section of FreeType2 API Reference).
|
|
* FT_Render_Glyph() canreturn two-color bitmap or 4/16/256 color graymap
|
|
* according to the format of embedded bitmap/graymap. */
|
|
for (i = 0; i < (unsigned int)src->rows; i++) {
|
|
unsigned char *srcp = src->buffer + i * src->pitch;
|
|
unsigned char *dstp = dst->buffer + i * dst->pitch;
|
|
unsigned int k, quotient, remainder;
|
|
|
|
/* Decode exactly the needed size from src->width */
|
|
if (src->pixel_mode == FT_PIXEL_MODE_MONO) {
|
|
quotient = src->width / 8;
|
|
remainder = src->width & 0x7;
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_GRAY2) {
|
|
quotient = src->width / 4;
|
|
remainder = src->width & 0x3;
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_GRAY4) {
|
|
quotient = src->width / 2;
|
|
remainder = src->width & 0x1;
|
|
#if TTF_USE_COLOR
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
|
quotient = src->width;
|
|
remainder = 0;
|
|
#endif
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_LCD) {
|
|
quotient = src->width / 3;
|
|
remainder = 0;
|
|
} else {
|
|
quotient = src->width;
|
|
remainder = 0;
|
|
}
|
|
|
|
/* FT_RENDER_MODE_MONO and src->pixel_mode MONO */
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push, 1)
|
|
#pragma warning(disable:4127)
|
|
#endif
|
|
#define MONO_MONO(K_MAX) \
|
|
if ((K_MAX)) { \
|
|
unsigned char c = *srcp++; \
|
|
for (k = 0; k < (K_MAX); ++k) { \
|
|
*dstp++ = (c & 0x80) >> 7; \
|
|
c <<= 1; \
|
|
} \
|
|
}
|
|
|
|
/* FT_RENDER_MODE_MONO and src->pixel_mode GRAY2 */
|
|
#define MONO_GRAY2(K_MAX) \
|
|
if ((K_MAX)) { \
|
|
unsigned char c = *srcp++; \
|
|
for (k = 0; k < (K_MAX); ++k) { \
|
|
*dstp++ = (((c&0xA0) >> 6) >= 0x2) ? 1 : 0; \
|
|
c <<= 2; \
|
|
} \
|
|
}
|
|
|
|
/* FT_RENDER_MODE_MONO and src->pixel_mode GRAY4 */
|
|
#define MONO_GRAY4(K_MAX) \
|
|
if ((K_MAX)) { \
|
|
unsigned char c = *srcp++; \
|
|
for (k = 0; k < (K_MAX); ++k) { \
|
|
*dstp++ = (((c&0xF0) >> 4) >= 0x8) ? 1 : 0; \
|
|
c <<= 4; \
|
|
} \
|
|
}
|
|
|
|
/* FT_RENDER_MODE_NORMAL and src->pixel_mode MONO */
|
|
#define NORMAL_MONO(K_MAX) \
|
|
if ((K_MAX)) { \
|
|
unsigned char c = *srcp++; \
|
|
for (k = 0; k < (K_MAX); ++k) { \
|
|
if ((c&0x80) >> 7) { \
|
|
*dstp++ = NUM_GRAYS - 1; \
|
|
} else { \
|
|
*dstp++ = 0x00; \
|
|
} \
|
|
c <<= 1; \
|
|
} \
|
|
}
|
|
|
|
/* FT_RENDER_MODE_NORMAL and src->pixel_mode GRAY2 */
|
|
#define NORMAL_GRAY2(K_MAX) \
|
|
if ((K_MAX)) { \
|
|
unsigned char c = *srcp++; \
|
|
for (k = 0; k < (K_MAX); ++k) { \
|
|
if ((c&0xA0) >> 6) { \
|
|
*dstp++ = NUM_GRAYS * ((c&0xA0) >> 6) / 3 - 1; \
|
|
} else { \
|
|
*dstp++ = 0x00; \
|
|
} \
|
|
c <<= 2; \
|
|
} \
|
|
}
|
|
|
|
/* FT_RENDER_MODE_NORMAL and src->pixel_mode GRAY4 */
|
|
#define NORMAL_GRAY4(K_MAX) \
|
|
if ((K_MAX)) { \
|
|
unsigned char c = *srcp++; \
|
|
for (k = 0; k < (K_MAX); ++k) { \
|
|
if ((c&0xF0) >> 4) { \
|
|
*dstp++ = NUM_GRAYS * ((c&0xF0) >> 4) / 15 - 1; \
|
|
} else { \
|
|
*dstp++ = 0x00; \
|
|
} \
|
|
c <<= 4; \
|
|
} \
|
|
}
|
|
|
|
if (mono) {
|
|
if (src->pixel_mode == FT_PIXEL_MODE_MONO) {
|
|
while (quotient--) {
|
|
MONO_MONO(8);
|
|
}
|
|
MONO_MONO(remainder);
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_GRAY2) {
|
|
while (quotient--) {
|
|
MONO_GRAY2(4);
|
|
}
|
|
MONO_GRAY2(remainder);
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_GRAY4) {
|
|
while (quotient--) {
|
|
MONO_GRAY4(2);
|
|
}
|
|
MONO_GRAY4(remainder);
|
|
} else {
|
|
while (quotient--) {
|
|
unsigned char c = *srcp++;
|
|
*dstp++ = (c >= 0x80) ? 1 : 0;
|
|
}
|
|
}
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_MONO) {
|
|
/* This special case wouldn't be here if the FT_Render_Glyph()
|
|
* function wasn't buggy when it tried to render a .fon font with 256
|
|
* shades of gray. Instead, it returns a black and white surface
|
|
* and we have to translate it back to a 256 gray shaded surface. */
|
|
while (quotient--) {
|
|
NORMAL_MONO(8);
|
|
}
|
|
NORMAL_MONO(remainder);
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_GRAY2) {
|
|
while (quotient--) {
|
|
NORMAL_GRAY2(4);
|
|
}
|
|
NORMAL_GRAY2(remainder);
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_GRAY4) {
|
|
while (quotient--) {
|
|
NORMAL_GRAY4(2);
|
|
}
|
|
NORMAL_GRAY4(remainder);
|
|
#if TTF_USE_COLOR
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
|
SDL_memcpy(dstp, srcp, 4 * src->width);
|
|
#endif
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_LCD) {
|
|
while (quotient--) {
|
|
Uint8 alpha = 0;
|
|
Uint8 r, g, b;
|
|
r = *srcp++;
|
|
g = *srcp++;
|
|
b = *srcp++;
|
|
*dstp++ = b;
|
|
*dstp++ = g;
|
|
*dstp++ = r;
|
|
*dstp++ = alpha;
|
|
}
|
|
} else {
|
|
#if TTF_USE_SDF
|
|
if (ft_render_mode != FT_RENDER_MODE_SDF) {
|
|
SDL_memcpy(dstp, srcp, src->width);
|
|
} else {
|
|
int x;
|
|
for (x = 0; x < src->width; x++) {
|
|
Uint8 s = srcp[x];
|
|
Uint8 d;
|
|
if (s < 128) {
|
|
d = 256 - (128 - s) * 2;
|
|
} else {
|
|
d = 255;
|
|
/* some glitch ?
|
|
if (s == 255) {
|
|
d = 0;
|
|
}*/
|
|
}
|
|
dstp[x] = d;
|
|
}
|
|
}
|
|
#else
|
|
SDL_memcpy(dstp, srcp, src->width);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
/* Handle the bold style */
|
|
if (TTF_HANDLE_STYLE_BOLD(font)) {
|
|
int row;
|
|
/* The pixmap is a little hard, we have to add and clamp */
|
|
for (row = dst->rows - 1; row >= 0; --row) {
|
|
Uint8 *pixmap = dst->buffer + row * dst->pitch;
|
|
int col, offset;
|
|
/* Minimal memset */
|
|
/* SDL_memset(pixmap + dst->width - font->glyph_overhang, 0, font->glyph_overhang); */
|
|
for (offset = 1; offset <= font->glyph_overhang; ++offset) {
|
|
for (col = dst->width - 1; col > 0; --col) {
|
|
if (mono) {
|
|
pixmap[col] |= pixmap[col-1];
|
|
} else {
|
|
int pixel = (pixmap[col] + pixmap[col-1]);
|
|
if (pixel > NUM_GRAYS - 1) {
|
|
pixel = NUM_GRAYS - 1;
|
|
}
|
|
pixmap[col] = (Uint8) pixel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Shift back */
|
|
if (dst->buffer) {
|
|
dst->buffer -= alignment;
|
|
}
|
|
|
|
#if TTF_USE_COLOR
|
|
if (src->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
|
dst->is_color = 1;
|
|
} else {
|
|
dst->is_color = 0;
|
|
}
|
|
#else
|
|
dst->is_color = 0;
|
|
#endif
|
|
|
|
/* Mark that we rendered this format */
|
|
if (mono) {
|
|
cached->stored |= CACHED_BITMAP;
|
|
} else if (src->pixel_mode == FT_PIXEL_MODE_LCD) {
|
|
cached->stored |= CACHED_LCD;
|
|
} else {
|
|
#if TTF_USE_COLOR
|
|
if (want & CACHED_COLOR) {
|
|
cached->stored |= CACHED_COLOR;
|
|
/* Most of the time, glyphs loaded with FT_LOAD_COLOR are non colored, so the cache is
|
|
also suitable for Shaded rendering (eg, loaded without FT_LOAD_COLOR) */
|
|
if (dst->is_color == 0) {
|
|
cached->stored |= CACHED_PIXMAP;
|
|
}
|
|
} else {
|
|
cached->stored |= CACHED_PIXMAP;
|
|
/* If font has no color information, Shaded/Pixmap cache is also suitable for Blend/Color */
|
|
if (!FT_HAS_COLOR(font->face)) {
|
|
cached->stored |= CACHED_COLOR;
|
|
}
|
|
}
|
|
#else
|
|
cached->stored |= CACHED_COLOR | CACHED_PIXMAP;
|
|
#endif
|
|
}
|
|
|
|
/* Free outlined glyph */
|
|
if (glyph) {
|
|
FT_Done_Glyph(glyph);
|
|
}
|
|
}
|
|
|
|
/* We're done, this glyph is cached since 'stored' is not 0 */
|
|
return 0;
|
|
|
|
ft_failure:
|
|
TTF_SetFTError("Couldn't find glyph", error);
|
|
return -1;
|
|
}
|
|
|
|
static SDL_INLINE int Find_GlyphByIndex(TTF_Font *font, FT_UInt idx,
|
|
int want_bitmap, int want_pixmap, int want_color, int want_lcd, int want_subpixel,
|
|
int translation, c_glyph **out_glyph, TTF_Image **out_image)
|
|
{
|
|
/* cache size is 256, get key by masking */
|
|
c_glyph *glyph = &font->cache[idx & 0xff];
|
|
|
|
if (out_glyph) {
|
|
*out_glyph = glyph;
|
|
}
|
|
|
|
if (want_pixmap || want_color || want_lcd) {
|
|
*out_image = &glyph->pixmap;
|
|
}
|
|
|
|
if (want_bitmap) {
|
|
*out_image = &glyph->bitmap;
|
|
}
|
|
|
|
if (want_subpixel)
|
|
{
|
|
/* No a real cache, but if it always advances by integer pixels (eg translation 0 or same as previous),
|
|
* this allows to render as fast as normal mode. */
|
|
int retval;
|
|
int want = CACHED_METRICS | want_bitmap | want_pixmap | want_color | want_lcd | want_subpixel;
|
|
|
|
if (glyph->stored && glyph->index != idx) {
|
|
Flush_Glyph(glyph);
|
|
}
|
|
|
|
if (glyph->subpixel.translation == translation) {
|
|
want &= ~CACHED_SUBPIX;
|
|
}
|
|
|
|
if ((glyph->stored & want) == want) {
|
|
return 0;
|
|
}
|
|
|
|
if (want_color || want_pixmap || want_lcd) {
|
|
if (glyph->stored & (CACHED_COLOR|CACHED_PIXMAP|CACHED_LCD)) {
|
|
Flush_Glyph(glyph);
|
|
}
|
|
}
|
|
|
|
glyph->index = idx;
|
|
retval = Load_Glyph(font, glyph, want, translation);
|
|
if (retval == 0) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int retval;
|
|
const int want = CACHED_METRICS | want_bitmap | want_pixmap | want_color | want_lcd;
|
|
|
|
/* Faster check as it gets inlined */
|
|
if (want_pixmap) {
|
|
if ((glyph->stored & CACHED_PIXMAP) && glyph->index == idx) {
|
|
return 0;
|
|
}
|
|
} else if (want_bitmap) {
|
|
if ((glyph->stored & CACHED_BITMAP) && glyph->index == idx) {
|
|
return 0;
|
|
}
|
|
} else if (want_color) {
|
|
if ((glyph->stored & CACHED_COLOR) && glyph->index == idx) {
|
|
return 0;
|
|
}
|
|
} else if (want_lcd) {
|
|
if ((glyph->stored & CACHED_LCD) && glyph->index == idx) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
/* Get metrics */
|
|
if (glyph->stored && glyph->index == idx) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Cache cannot contain both PIXMAP and COLOR (unless COLOR is actually not colored) and LCD
|
|
So, if it's already used, clear it */
|
|
if (want_color || want_pixmap || want_lcd) {
|
|
if (glyph->stored & (CACHED_COLOR|CACHED_PIXMAP|CACHED_LCD)) {
|
|
Flush_Glyph(glyph);
|
|
}
|
|
}
|
|
|
|
if (glyph->stored && glyph->index != idx) {
|
|
Flush_Glyph(glyph);
|
|
}
|
|
|
|
glyph->index = idx;
|
|
retval = Load_Glyph(font, glyph, want, 0);
|
|
if (retval == 0) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static SDL_INLINE FT_UInt get_char_index(TTF_Font *font, Uint32 ch)
|
|
{
|
|
Uint32 cache_index_size = sizeof (font->cache_index) / sizeof (font->cache_index[0]);
|
|
|
|
if (ch < cache_index_size) {
|
|
FT_UInt idx = font->cache_index[ch];
|
|
if (idx) {
|
|
return idx;
|
|
}
|
|
idx = FT_Get_Char_Index(font->face, ch);
|
|
font->cache_index[ch] = idx;
|
|
return idx;
|
|
}
|
|
|
|
return FT_Get_Char_Index(font->face, ch);
|
|
}
|
|
|
|
|
|
static SDL_INLINE int Find_GlyphMetrics(TTF_Font *font, Uint32 ch, c_glyph **out_glyph)
|
|
{
|
|
FT_UInt idx = get_char_index(font, ch);
|
|
return Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, out_glyph, NULL);
|
|
}
|
|
|
|
void TTF_CloseFont(TTF_Font *font)
|
|
{
|
|
if (font) {
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_font_destroy(font->hb_font);
|
|
#endif
|
|
Flush_Cache(font);
|
|
if (font->face) {
|
|
FT_Done_Face(font->face);
|
|
}
|
|
if (font->args.stream) {
|
|
SDL_free(font->args.stream);
|
|
}
|
|
if (font->freesrc) {
|
|
SDL_RWclose(font->src);
|
|
}
|
|
if (font->pos_buf) {
|
|
SDL_free(font->pos_buf);
|
|
}
|
|
SDL_free(font);
|
|
}
|
|
}
|
|
|
|
/* Gets the number of bytes needed to convert a Latin-1 string to UTF-8 */
|
|
static size_t LATIN1_to_UTF8_len(const char *text)
|
|
{
|
|
size_t bytes = 1;
|
|
while (*text) {
|
|
Uint8 ch = *(const Uint8 *)text++;
|
|
if (ch <= 0x7F) {
|
|
bytes += 1;
|
|
} else {
|
|
bytes += 2;
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
/* Gets the number of bytes needed to convert a UCS2 string to UTF-8 */
|
|
static size_t UCS2_to_UTF8_len(const Uint16 *text)
|
|
{
|
|
SDL_bool swapped = TTF_byteswapped;
|
|
size_t bytes = 1;
|
|
while (*text) {
|
|
Uint16 ch = *text++;
|
|
if (ch == UNICODE_BOM_NATIVE) {
|
|
swapped = SDL_FALSE;
|
|
continue;
|
|
}
|
|
if (ch == UNICODE_BOM_SWAPPED) {
|
|
swapped = SDL_TRUE;
|
|
continue;
|
|
}
|
|
if (swapped) {
|
|
ch = SDL_Swap16(ch);
|
|
}
|
|
if (ch <= 0x7F) {
|
|
bytes += 1;
|
|
} else if (ch <= 0x7FF) {
|
|
bytes += 2;
|
|
} else {
|
|
bytes += 3;
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
/* Convert a Latin-1 string to a UTF-8 string */
|
|
static void LATIN1_to_UTF8(const char *src, Uint8 *dst)
|
|
{
|
|
while (*src) {
|
|
Uint8 ch = *(const Uint8 *)src++;
|
|
if (ch <= 0x7F) {
|
|
*dst++ = ch;
|
|
} else {
|
|
*dst++ = 0xC0 | ((ch >> 6) & 0x1F);
|
|
*dst++ = 0x80 | (ch & 0x3F);
|
|
}
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
|
|
/* Convert a UCS-2 string to a UTF-8 string */
|
|
static void UCS2_to_UTF8(const Uint16 *src, Uint8 *dst)
|
|
{
|
|
SDL_bool swapped = TTF_byteswapped;
|
|
|
|
while (*src) {
|
|
Uint16 ch = *src++;
|
|
if (ch == UNICODE_BOM_NATIVE) {
|
|
swapped = SDL_FALSE;
|
|
continue;
|
|
}
|
|
if (ch == UNICODE_BOM_SWAPPED) {
|
|
swapped = SDL_TRUE;
|
|
continue;
|
|
}
|
|
if (swapped) {
|
|
ch = SDL_Swap16(ch);
|
|
}
|
|
if (ch <= 0x7F) {
|
|
*dst++ = (Uint8) ch;
|
|
} else if (ch <= 0x7FF) {
|
|
*dst++ = 0xC0 | (Uint8) ((ch >> 6) & 0x1F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
} else {
|
|
*dst++ = 0xE0 | (Uint8) ((ch >> 12) & 0x0F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 6) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
}
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
|
|
/* Convert a unicode char to a UTF-8 string */
|
|
static SDL_bool Char_to_UTF8(Uint32 ch, Uint8 *dst)
|
|
{
|
|
if (ch <= 0x7F) {
|
|
*dst++ = (Uint8) ch;
|
|
} else if (ch <= 0x7FF) {
|
|
*dst++ = 0xC0 | (Uint8) ((ch >> 6) & 0x1F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
} else if (ch <= 0xFFFF) {
|
|
*dst++ = 0xE0 | (Uint8) ((ch >> 12) & 0x0F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 6) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
} else if (ch <= 0x1FFFFF) {
|
|
*dst++ = 0xF0 | (Uint8) ((ch >> 18) & 0x07);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 12) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 6) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
} else if (ch <= 0x3FFFFFF) {
|
|
*dst++ = 0xF8 | (Uint8) ((ch >> 24) & 0x03);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 18) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 12) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 6) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
} else if (ch < 0x7FFFFFFF) {
|
|
*dst++ = 0xFC | (Uint8) ((ch >> 30) & 0x01);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 24) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 18) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 12) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) ((ch >> 6) & 0x3F);
|
|
*dst++ = 0x80 | (Uint8) (ch & 0x3F);
|
|
} else {
|
|
TTF_SetError("Invalid character");
|
|
return SDL_FALSE;
|
|
}
|
|
*dst = '\0';
|
|
return SDL_TRUE;
|
|
}
|
|
|
|
/* Gets a unicode value from a UTF-8 encoded string
|
|
* Ouputs increment to advance the string */
|
|
#define UNKNOWN_UNICODE 0xFFFD
|
|
static Uint32 UTF8_getch(const char *src, size_t srclen, int *inc)
|
|
{
|
|
const Uint8 *p = (const Uint8 *)src;
|
|
size_t left = 0;
|
|
size_t save_srclen = srclen;
|
|
SDL_bool overlong = SDL_FALSE;
|
|
SDL_bool underflow = SDL_FALSE;
|
|
Uint32 ch = UNKNOWN_UNICODE;
|
|
|
|
if (srclen == 0) {
|
|
return UNKNOWN_UNICODE;
|
|
}
|
|
if (p[0] >= 0xFC) {
|
|
if ((p[0] & 0xFE) == 0xFC) {
|
|
if (p[0] == 0xFC && (p[1] & 0xFC) == 0x80) {
|
|
overlong = SDL_TRUE;
|
|
}
|
|
ch = (Uint32) (p[0] & 0x01);
|
|
left = 5;
|
|
}
|
|
} else if (p[0] >= 0xF8) {
|
|
if ((p[0] & 0xFC) == 0xF8) {
|
|
if (p[0] == 0xF8 && (p[1] & 0xF8) == 0x80) {
|
|
overlong = SDL_TRUE;
|
|
}
|
|
ch = (Uint32) (p[0] & 0x03);
|
|
left = 4;
|
|
}
|
|
} else if (p[0] >= 0xF0) {
|
|
if ((p[0] & 0xF8) == 0xF0) {
|
|
if (p[0] == 0xF0 && (p[1] & 0xF0) == 0x80) {
|
|
overlong = SDL_TRUE;
|
|
}
|
|
ch = (Uint32) (p[0] & 0x07);
|
|
left = 3;
|
|
}
|
|
} else if (p[0] >= 0xE0) {
|
|
if ((p[0] & 0xF0) == 0xE0) {
|
|
if (p[0] == 0xE0 && (p[1] & 0xE0) == 0x80) {
|
|
overlong = SDL_TRUE;
|
|
}
|
|
ch = (Uint32) (p[0] & 0x0F);
|
|
left = 2;
|
|
}
|
|
} else if (p[0] >= 0xC0) {
|
|
if ((p[0] & 0xE0) == 0xC0) {
|
|
if ((p[0] & 0xDE) == 0xC0) {
|
|
overlong = SDL_TRUE;
|
|
}
|
|
ch = (Uint32) (p[0] & 0x1F);
|
|
left = 1;
|
|
}
|
|
} else {
|
|
if ((p[0] & 0x80) == 0x00) {
|
|
ch = (Uint32) p[0];
|
|
}
|
|
}
|
|
--srclen;
|
|
while (left > 0 && srclen > 0) {
|
|
++p;
|
|
if ((p[0] & 0xC0) != 0x80) {
|
|
ch = UNKNOWN_UNICODE;
|
|
break;
|
|
}
|
|
ch <<= 6;
|
|
ch |= (p[0] & 0x3F);
|
|
--srclen;
|
|
--left;
|
|
}
|
|
if (left > 0) {
|
|
underflow = SDL_TRUE;
|
|
}
|
|
/* Technically overlong sequences are invalid and should not be interpreted.
|
|
However, it doesn't cause a security risk here and I don't see any harm in
|
|
displaying them. The application is responsible for any other side effects
|
|
of allowing overlong sequences (e.g. string compares failing, etc.)
|
|
See bug 1931 for sample input that triggers this.
|
|
*/
|
|
/* if (overlong) return UNKNOWN_UNICODE; */
|
|
|
|
(void) overlong;
|
|
|
|
if (underflow ||
|
|
(ch >= 0xD800 && ch <= 0xDFFF) ||
|
|
(ch == 0xFFFE || ch == 0xFFFF) || ch > 0x10FFFF) {
|
|
ch = UNKNOWN_UNICODE;
|
|
}
|
|
|
|
*inc = (int)(save_srclen - srclen);
|
|
|
|
return ch;
|
|
}
|
|
|
|
int TTF_FontHeight(const TTF_Font *font)
|
|
{
|
|
return font->height;
|
|
}
|
|
|
|
int TTF_FontAscent(const TTF_Font *font)
|
|
{
|
|
return font->ascent + 2 * font->outline_val;
|
|
}
|
|
|
|
int TTF_FontDescent(const TTF_Font *font)
|
|
{
|
|
return font->descent;
|
|
}
|
|
|
|
int TTF_FontLineSkip(const TTF_Font *font)
|
|
{
|
|
return font->lineskip;
|
|
}
|
|
|
|
int TTF_GetFontKerning(const TTF_Font *font)
|
|
{
|
|
return font->allow_kerning;
|
|
}
|
|
|
|
void TTF_SetFontKerning(TTF_Font *font, int allowed)
|
|
{
|
|
font->allow_kerning = allowed;
|
|
font->use_kerning = allowed && FT_HAS_KERNING(font->face);
|
|
}
|
|
|
|
long TTF_FontFaces(const TTF_Font *font)
|
|
{
|
|
return font->face->num_faces;
|
|
}
|
|
|
|
int TTF_FontFaceIsFixedWidth(const TTF_Font *font)
|
|
{
|
|
return FT_IS_FIXED_WIDTH(font->face);
|
|
}
|
|
|
|
const char *
|
|
TTF_FontFaceFamilyName(const TTF_Font *font)
|
|
{
|
|
return font->face->family_name;
|
|
}
|
|
|
|
const char *
|
|
TTF_FontFaceStyleName(const TTF_Font *font)
|
|
{
|
|
return font->face->style_name;
|
|
}
|
|
|
|
int TTF_GlyphIsProvided(TTF_Font *font, Uint16 ch)
|
|
{
|
|
return (int)get_char_index(font, ch);
|
|
}
|
|
|
|
int TTF_GlyphIsProvided32(TTF_Font *font, Uint32 ch)
|
|
{
|
|
return (int)get_char_index(font, ch);
|
|
}
|
|
|
|
int TTF_GlyphMetrics(TTF_Font *font, Uint16 ch,
|
|
int *minx, int *maxx, int *miny, int *maxy, int *advance)
|
|
{
|
|
return TTF_GlyphMetrics32(font, ch, minx, maxx, miny, maxy, advance);
|
|
}
|
|
|
|
int TTF_GlyphMetrics32(TTF_Font *font, Uint32 ch,
|
|
int *minx, int *maxx, int *miny, int *maxy, int *advance)
|
|
{
|
|
c_glyph *glyph;
|
|
|
|
TTF_CHECK_POINTER(font, -1);
|
|
|
|
if (Find_GlyphMetrics(font, ch, &glyph) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (minx) {
|
|
*minx = glyph->sz_left;
|
|
}
|
|
if (maxx) {
|
|
*maxx = glyph->sz_left + glyph->sz_width;
|
|
*maxx += 2 * font->outline_val;
|
|
}
|
|
if (miny) {
|
|
*miny = glyph->sz_top - glyph->sz_rows;
|
|
}
|
|
if (maxy) {
|
|
*maxy = glyph->sz_top;
|
|
*maxy += 2 * font->outline_val;
|
|
}
|
|
if (advance) {
|
|
*advance = FT_CEIL(glyph->advance);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int TTF_SetFontDirection(TTF_Font *font, TTF_Direction direction)
|
|
{
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_direction_t dir;
|
|
if (direction == TTF_DIRECTION_LTR) {
|
|
dir = HB_DIRECTION_LTR;
|
|
} else if (direction == TTF_DIRECTION_RTL) {
|
|
dir = HB_DIRECTION_RTL;
|
|
} else if (direction == TTF_DIRECTION_TTB) {
|
|
dir = HB_DIRECTION_BTT;
|
|
} else if (direction == TTF_DIRECTION_BTT) {
|
|
dir = HB_DIRECTION_TTB;
|
|
} else {
|
|
return -1;
|
|
}
|
|
font->hb_direction = dir;
|
|
return 0;
|
|
#else
|
|
(void) font;
|
|
(void) direction;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int TTF_SetFontScriptName(TTF_Font *font, const char *script)
|
|
{
|
|
#if TTF_USE_HARFBUZZ
|
|
Uint8 a, b, c, d;
|
|
hb_script_t scr;
|
|
|
|
if (script == NULL || SDL_strlen(script) != 4) {
|
|
return -1;
|
|
}
|
|
|
|
a = script[0];
|
|
b = script[1];
|
|
c = script[2];
|
|
d = script[3];
|
|
|
|
scr = HB_TAG(a, b, c, d);
|
|
font->hb_script = scr;
|
|
return 0;
|
|
#else
|
|
(void) font;
|
|
(void) script;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
static int TTF_Size_Internal(TTF_Font *font,
|
|
const char *text, const str_type_t str_type,
|
|
int *w, int *h, int *xstart, int *ystart,
|
|
int measure_width, int *extent, int *count)
|
|
{
|
|
int x = 0;
|
|
int pos_x, pos_y;
|
|
int minx = 0, maxx = 0;
|
|
int miny = 0, maxy = 0;
|
|
Uint8 *utf8_alloc = NULL;
|
|
c_glyph *glyph;
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_direction_t hb_direction;
|
|
hb_script_t hb_script;
|
|
hb_buffer_t *hb_buffer = NULL;
|
|
unsigned int g;
|
|
unsigned int glyph_count;
|
|
hb_glyph_info_t *hb_glyph_info;
|
|
hb_glyph_position_t *hb_glyph_position;
|
|
int y = 0;
|
|
int advance_if_bold = 0;
|
|
#else
|
|
size_t textlen;
|
|
int skip_first = 1;
|
|
FT_UInt prev_index = 0;
|
|
FT_Pos prev_delta = 0;
|
|
#endif
|
|
int prev_advance = 0;
|
|
|
|
/* Measurement mode */
|
|
int char_count = 0;
|
|
int current_width = 0;
|
|
|
|
TTF_CHECK_INITIALIZED(-1);
|
|
TTF_CHECK_POINTER(font, -1);
|
|
TTF_CHECK_POINTER(text, -1);
|
|
|
|
/* Convert input string to default encoding UTF-8 */
|
|
if (str_type == STR_TEXT) {
|
|
utf8_alloc = SDL_stack_alloc(Uint8, LATIN1_to_UTF8_len(text));
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
LATIN1_to_UTF8(text, utf8_alloc);
|
|
text = (const char *)utf8_alloc;
|
|
} else if (str_type == STR_UNICODE) {
|
|
const Uint16 *text16 = (const Uint16 *) text;
|
|
utf8_alloc = SDL_stack_alloc(Uint8, UCS2_to_UTF8_len(text16));
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
UCS2_to_UTF8(text16, utf8_alloc);
|
|
text = (const char *)utf8_alloc;
|
|
}
|
|
|
|
maxy = font->height;
|
|
|
|
/* Reset buffer */
|
|
font->pos_len = 0;
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
|
|
/* Adjust for bold text */
|
|
if (TTF_HANDLE_STYLE_BOLD(font)) {
|
|
advance_if_bold = F26Dot6(font->glyph_overhang);
|
|
}
|
|
|
|
/* Create a buffer for harfbuzz to use */
|
|
hb_buffer = hb_buffer_create();
|
|
if (hb_buffer == NULL) {
|
|
TTF_SetError("Cannot create harfbuzz buffer");
|
|
goto failure;
|
|
}
|
|
|
|
|
|
hb_direction = font->hb_direction;
|
|
hb_script = font->hb_script;
|
|
|
|
if (hb_script == HB_SCRIPT_INVALID) {
|
|
hb_script = g_hb_script;
|
|
}
|
|
|
|
if (hb_direction == HB_DIRECTION_INVALID) {
|
|
hb_direction = g_hb_direction;
|
|
}
|
|
|
|
/* Set global configuration */
|
|
hb_buffer_set_direction(hb_buffer, hb_direction);
|
|
hb_buffer_set_script(hb_buffer, hb_script);
|
|
|
|
/* Layout the text */
|
|
hb_buffer_add_utf8(hb_buffer, text, -1, 0, -1);
|
|
hb_shape(font->hb_font, hb_buffer, NULL, 0);
|
|
|
|
/* Get the result */
|
|
hb_glyph_info = hb_buffer_get_glyph_infos(hb_buffer, &glyph_count);
|
|
hb_glyph_position = hb_buffer_get_glyph_positions(hb_buffer, &glyph_count);
|
|
|
|
/* Load and render each character */
|
|
for (g = 0; g < glyph_count; g++)
|
|
{
|
|
FT_UInt idx = hb_glyph_info[g].codepoint;
|
|
int x_advance = hb_glyph_position[g].x_advance;
|
|
int y_advance = hb_glyph_position[g].y_advance;
|
|
int x_offset = hb_glyph_position[g].x_offset;
|
|
int y_offset = hb_glyph_position[g].y_offset;
|
|
#else
|
|
/* Load each character and sum it's bounding box */
|
|
textlen = SDL_strlen(text);
|
|
while (textlen > 0) {
|
|
int inc = 0;
|
|
Uint32 c = UTF8_getch(text, textlen, &inc);
|
|
FT_UInt idx = get_char_index(font, c);
|
|
text += inc;
|
|
textlen -= inc;
|
|
|
|
if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
|
|
continue;
|
|
}
|
|
#endif
|
|
if (Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL) < 0) {
|
|
goto failure;
|
|
}
|
|
|
|
/* Realloc, if needed */
|
|
if (font->pos_len >= font->pos_max) {
|
|
PosBuf_t *saved = font->pos_buf;
|
|
font->pos_max *= 2;
|
|
font->pos_buf = (PosBuf_t *)SDL_realloc(font->pos_buf, font->pos_max * sizeof (font->pos_buf[0]));
|
|
if (font->pos_buf == NULL) {
|
|
font->pos_max /= 2;
|
|
font->pos_buf = saved;
|
|
TTF_SetError("Out of memory");
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
/* Compute positions */
|
|
pos_x = x + x_offset;
|
|
pos_y = y + F26Dot6(font->ascent) - y_offset;
|
|
x += x_advance + advance_if_bold;
|
|
y += y_advance;
|
|
#else
|
|
/* Compute positions */
|
|
x += prev_advance;
|
|
prev_advance = glyph->advance;
|
|
if (font->use_kerning) {
|
|
if (prev_index && glyph->index) {
|
|
FT_Vector delta;
|
|
FT_Get_Kerning(font->face, prev_index, glyph->index, FT_KERNING_UNFITTED, &delta);
|
|
x += delta.x;
|
|
}
|
|
prev_index = glyph->index;
|
|
}
|
|
/* FT SUBPIXEL : LCD_MODE_LIGHT_SUBPIXEL */
|
|
if (font->render_subpixel) {
|
|
x += prev_delta;
|
|
/* Increment by prev_glyph->lsb_delta - prev_glyph->rsb_delta; */
|
|
prev_delta = glyph->subpixel.lsb_minus_rsb;
|
|
} else {
|
|
/* FT KERNING_MODE_SMART: Use `lsb_delta' and `rsb_delta' to improve integer positioning of glyphs */
|
|
if (skip_first) {
|
|
skip_first = 0;
|
|
} else {
|
|
if (prev_delta - glyph->kerning_smart.lsb_delta > 32 ) {
|
|
x -= 64;
|
|
} else if (prev_delta - glyph->kerning_smart.lsb_delta < -31 ) {
|
|
x += 64;
|
|
}
|
|
}
|
|
prev_delta = glyph->kerning_smart.rsb_delta;
|
|
x = ((x + 32) & -64); /* ROUND() */
|
|
}
|
|
|
|
/* Compute positions where to copy the glyph bitmap */
|
|
pos_x = x;
|
|
pos_y = F26Dot6(font->ascent);
|
|
#endif
|
|
/* Store things for Render_Line() */
|
|
font->pos_buf[font->pos_len].x = pos_x;
|
|
font->pos_buf[font->pos_len].y = pos_y;
|
|
font->pos_buf[font->pos_len].index = idx;
|
|
font->pos_len += 1;
|
|
|
|
/* Compute previsionnal global bounding box */
|
|
pos_x = FT_FLOOR(pos_x) + glyph->sz_left;
|
|
pos_y = FT_FLOOR(pos_y) - glyph->sz_top;
|
|
|
|
minx = SDL_min(minx, pos_x);
|
|
maxx = SDL_max(maxx, pos_x + glyph->sz_width);
|
|
miny = SDL_min(miny, pos_y);
|
|
maxy = SDL_max(maxy, pos_y + glyph->sz_rows);
|
|
|
|
/* Measurement mode */
|
|
if (measure_width) {
|
|
int cw = SDL_max(maxx, FT_FLOOR(x + prev_advance)) - minx;
|
|
cw += 2 * font->outline_val;
|
|
if (cw <= measure_width) {
|
|
current_width = cw;
|
|
char_count += 1;
|
|
}
|
|
if (cw >= measure_width) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Allows to render a string with only one space (bug 4344). */
|
|
maxx = SDL_max(maxx, FT_FLOOR(x + prev_advance));
|
|
|
|
/* Initial x start position: often 0, except when a glyph would be written at
|
|
* a negative position. In this case an offset is needed for the whole line. */
|
|
if (xstart) {
|
|
*xstart = (minx < 0)? -minx : 0;
|
|
*xstart += font->outline_val;
|
|
if (font->render_sdf) {
|
|
*xstart += 8; /* Default 'spread' property */
|
|
}
|
|
}
|
|
|
|
/* Initial y start: compensation for a negative y offset */
|
|
if (ystart) {
|
|
*ystart = (miny < 0)? -miny : 0;
|
|
*ystart += font->outline_val;
|
|
if (font->render_sdf) {
|
|
*ystart += 8; /* Default 'spread' property */
|
|
}
|
|
}
|
|
|
|
/* Fill the bounds rectangle */
|
|
if (w) {
|
|
*w = (maxx - minx);
|
|
if (*w != 0) {
|
|
*w += 2 * font->outline_val;
|
|
}
|
|
}
|
|
if (h) {
|
|
*h = (maxy - miny);
|
|
*h += 2 * font->outline_val;
|
|
}
|
|
|
|
/* Measurement mode */
|
|
if (measure_width) {
|
|
if (extent) {
|
|
*extent = current_width;
|
|
}
|
|
if (count) {
|
|
#if TTF_USE_HARFBUZZ
|
|
if (char_count == glyph_count) {
|
|
/* The higher level code doesn't know about ligatures,
|
|
* so if we've covered all the glyphs, report the full
|
|
* string length.
|
|
*
|
|
* If we have to line wrap somewhere in the middle, we
|
|
* might be off by the number of ligatures, but there
|
|
* isn't an easy way around that without using hb_buffer
|
|
* at that level instead.
|
|
*/
|
|
*count = (int)SDL_utf8strlen(text);
|
|
} else
|
|
#endif
|
|
*count = char_count;
|
|
}
|
|
}
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
if (hb_buffer) {
|
|
hb_buffer_destroy(hb_buffer);
|
|
}
|
|
#endif
|
|
if (utf8_alloc) {
|
|
SDL_stack_free(utf8_alloc);
|
|
}
|
|
return 0;
|
|
failure:
|
|
#if TTF_USE_HARFBUZZ
|
|
if (hb_buffer) {
|
|
hb_buffer_destroy(hb_buffer);
|
|
}
|
|
#endif
|
|
if (utf8_alloc) {
|
|
SDL_stack_free(utf8_alloc);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h)
|
|
{
|
|
return TTF_Size_Internal(font, text, STR_TEXT, w, h, NULL, NULL, NO_MEASUREMENT);
|
|
}
|
|
|
|
int TTF_SizeUTF8(TTF_Font *font, const char *text, int *w, int *h)
|
|
{
|
|
return TTF_Size_Internal(font, text, STR_UTF8, w, h, NULL, NULL, NO_MEASUREMENT);
|
|
}
|
|
|
|
int TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h)
|
|
{
|
|
return TTF_Size_Internal(font, (const char *)text, STR_UNICODE, w, h, NULL, NULL, NO_MEASUREMENT);
|
|
}
|
|
|
|
int TTF_MeasureText(TTF_Font *font, const char *text, int width, int *extent, int *count)
|
|
{
|
|
return TTF_Size_Internal(font, text, STR_TEXT, NULL, NULL, NULL, NULL, width, extent, count);
|
|
}
|
|
|
|
int TTF_MeasureUTF8(TTF_Font *font, const char *text, int width, int *extent, int *count)
|
|
{
|
|
return TTF_Size_Internal(font, text, STR_UTF8, NULL, NULL, NULL, NULL, width, extent, count);
|
|
}
|
|
|
|
int TTF_MeasureUNICODE(TTF_Font *font, const Uint16 *text, int width, int *extent, int *count)
|
|
{
|
|
return TTF_Size_Internal(font, (const char *)text, STR_UNICODE, NULL, NULL, NULL, NULL, width, extent, count);
|
|
}
|
|
|
|
static SDL_Surface* TTF_Render_Internal(TTF_Font *font, const char *text, const str_type_t str_type,
|
|
SDL_Color fg, SDL_Color bg, const render_mode_t render_mode)
|
|
{
|
|
Uint32 color;
|
|
int xstart, ystart, width, height;
|
|
SDL_Surface *textbuf = NULL;
|
|
Uint8 *utf8_alloc = NULL;
|
|
|
|
TTF_CHECK_INITIALIZED(NULL);
|
|
TTF_CHECK_POINTER(font, NULL);
|
|
TTF_CHECK_POINTER(text, NULL);
|
|
|
|
if (render_mode == RENDER_LCD && !FT_IS_SCALABLE(font->face)) {
|
|
TTF_SetError("LCD rendering is not available for non-scalable font");
|
|
goto failure;
|
|
}
|
|
|
|
/* Convert input string to default encoding UTF-8 */
|
|
if (str_type == STR_TEXT) {
|
|
utf8_alloc = SDL_stack_alloc(Uint8, LATIN1_to_UTF8_len(text));
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
LATIN1_to_UTF8(text, utf8_alloc);
|
|
text = (const char *)utf8_alloc;
|
|
} else if (str_type == STR_UNICODE) {
|
|
const Uint16 *text16 = (const Uint16 *) text;
|
|
utf8_alloc = SDL_stack_alloc(Uint8, UCS2_to_UTF8_len(text16));
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
UCS2_to_UTF8(text16, utf8_alloc);
|
|
text = (const char *)utf8_alloc;
|
|
}
|
|
|
|
#if TTF_USE_SDF
|
|
/* Invalid cache if we were using SDF */
|
|
if (render_mode != RENDER_BLENDED) {
|
|
if (font->render_sdf) {
|
|
font->render_sdf = 0;
|
|
Flush_Cache(font);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Get the dimensions of the text surface */
|
|
if ((TTF_Size_Internal(font, text, STR_UTF8, &width, &height, &xstart, &ystart, NO_MEASUREMENT) < 0) || !width) {
|
|
TTF_SetError("Text has zero width");
|
|
goto failure;
|
|
}
|
|
|
|
/* Support alpha blending */
|
|
fg.a = fg.a ? fg.a : SDL_ALPHA_OPAQUE;
|
|
bg.a = bg.a ? bg.a : SDL_ALPHA_OPAQUE;
|
|
|
|
/* Create surface for rendering */
|
|
if (render_mode == RENDER_SOLID) {
|
|
textbuf = Create_Surface_Solid(width, height, fg, &color);
|
|
} else if (render_mode == RENDER_SHADED) {
|
|
textbuf = Create_Surface_Shaded(width, height, fg, bg, &color);
|
|
} else if (render_mode == RENDER_BLENDED) {
|
|
textbuf = Create_Surface_Blended(width, height, fg, &color);
|
|
} else { /* render_mode == RENDER_LCD */
|
|
textbuf = Create_Surface_LCD(width, height, fg, bg, &color);
|
|
}
|
|
|
|
if (textbuf == NULL) {
|
|
goto failure;
|
|
}
|
|
|
|
/* Render one text line to textbuf at (xstart, ystart) */
|
|
if (Render_Line(render_mode, font->render_subpixel, font, textbuf, xstart, ystart, fg) < 0) {
|
|
goto failure;
|
|
}
|
|
|
|
/* Apply underline or strikethrough style, if needed */
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
Draw_Line(font, textbuf, 0, ystart + font->underline_top_row, width, font->line_thickness, color, render_mode);
|
|
}
|
|
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
Draw_Line(font, textbuf, 0, ystart + font->strikethrough_top_row, width, font->line_thickness, color, render_mode);
|
|
}
|
|
|
|
if (utf8_alloc) {
|
|
SDL_stack_free(utf8_alloc);
|
|
}
|
|
return textbuf;
|
|
failure:
|
|
if (textbuf) {
|
|
SDL_FreeSurface(textbuf);
|
|
}
|
|
if (utf8_alloc) {
|
|
SDL_stack_free(utf8_alloc);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Solid(TTF_Font *font, const char *text, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_TEXT, fg, fg /* unused */, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_Solid(TTF_Font *font, const char *text, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_UTF8, fg, fg /* unused */, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_Solid(TTF_Font *font, const Uint16 *text, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, (const char *)text, STR_UNICODE, fg, fg /* unused */, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_Solid(TTF_Font *font, Uint16 ch, SDL_Color fg)
|
|
{
|
|
return TTF_RenderGlyph32_Solid(font, ch, fg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph32_Solid(TTF_Font *font, Uint32 ch, SDL_Color fg)
|
|
{
|
|
Uint8 utf8[7];
|
|
|
|
TTF_CHECK_POINTER(font, NULL);
|
|
|
|
if (!Char_to_UTF8(ch, utf8)) {
|
|
return NULL;
|
|
}
|
|
|
|
return TTF_RenderUTF8_Solid(font, (char *)utf8, fg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_TEXT, fg, bg, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_UTF8, fg, bg, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_Shaded(TTF_Font *font, const Uint16 *text, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, (const char *)text, STR_UNICODE, fg, bg, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_Shaded(TTF_Font *font, Uint16 ch, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_RenderGlyph32_Shaded(font, ch, fg, bg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph32_Shaded(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
Uint8 utf8[7];
|
|
|
|
TTF_CHECK_POINTER(font, NULL);
|
|
|
|
if (!Char_to_UTF8(ch, utf8)) {
|
|
return NULL;
|
|
}
|
|
|
|
return TTF_RenderUTF8_Shaded(font, (char *)utf8, fg, bg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Blended(TTF_Font *font, const char *text, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_TEXT, fg, fg /* unused */, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_Blended(TTF_Font *font, const char *text, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_UTF8, fg, fg /* unused */, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_Blended(TTF_Font *font, const Uint16 *text, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, (const char *)text, STR_UNICODE, fg, fg /* unused */, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_LCD(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_TEXT, fg, bg, RENDER_LCD);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_LCD(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, text, STR_UTF8, fg, bg, RENDER_LCD);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_LCD(TTF_Font *font, const Uint16 *text, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, (const char *)text, STR_UNICODE, fg, bg, RENDER_LCD);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_LCD(TTF_Font *font, Uint16 ch, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_RenderGlyph32_LCD(font, ch, fg, bg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph32_LCD(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
Uint8 utf8[7];
|
|
|
|
TTF_CHECK_POINTER(font, NULL);
|
|
|
|
if (!Char_to_UTF8(ch, utf8)) {
|
|
return NULL;
|
|
}
|
|
|
|
return TTF_RenderUTF8_LCD(font, (char *)utf8, fg, bg);
|
|
}
|
|
|
|
static SDL_bool CharacterIsDelimiter(Uint32 c)
|
|
{
|
|
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
|
|
return SDL_TRUE;
|
|
}
|
|
return SDL_FALSE;
|
|
}
|
|
|
|
static SDL_bool CharacterIsNewLine(Uint32 c)
|
|
{
|
|
if (c == '\n') {
|
|
return SDL_TRUE;
|
|
}
|
|
return SDL_FALSE;
|
|
}
|
|
|
|
static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text, const str_type_t str_type,
|
|
SDL_Color fg, SDL_Color bg, Uint32 wrapLength, const render_mode_t render_mode)
|
|
{
|
|
Uint32 color;
|
|
int width, height;
|
|
SDL_Surface *textbuf = NULL;
|
|
Uint8 *utf8_alloc = NULL;
|
|
|
|
int i, numLines, rowHeight, lineskip;
|
|
char **strLines = NULL, *text_cpy;
|
|
|
|
TTF_CHECK_INITIALIZED(NULL);
|
|
TTF_CHECK_POINTER(font, NULL);
|
|
TTF_CHECK_POINTER(text, NULL);
|
|
|
|
if (render_mode == RENDER_LCD && !FT_IS_SCALABLE(font->face)) {
|
|
TTF_SetError("LCD rendering is not available for non-scalable font");
|
|
goto failure;
|
|
}
|
|
|
|
/* Convert input string to default encoding UTF-8 */
|
|
if (str_type == STR_TEXT) {
|
|
utf8_alloc = SDL_stack_alloc(Uint8, LATIN1_to_UTF8_len(text));
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
LATIN1_to_UTF8(text, utf8_alloc);
|
|
text_cpy = (char *)utf8_alloc;
|
|
} else if (str_type == STR_UNICODE) {
|
|
const Uint16 *text16 = (const Uint16 *) text;
|
|
utf8_alloc = SDL_stack_alloc(Uint8, UCS2_to_UTF8_len(text16));
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
UCS2_to_UTF8(text16, utf8_alloc);
|
|
text_cpy = (char *)utf8_alloc;
|
|
} else {
|
|
/* Use a copy anyway */
|
|
size_t str_len = SDL_strlen(text);
|
|
utf8_alloc = SDL_stack_alloc(Uint8, str_len + 1);
|
|
if (utf8_alloc == NULL) {
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
SDL_memcpy(utf8_alloc, text, str_len + 1);
|
|
text_cpy = (char *)utf8_alloc;
|
|
}
|
|
|
|
#if TTF_USE_SDF
|
|
/* Invalid cache if we were using SDF */
|
|
if (render_mode != RENDER_BLENDED) {
|
|
if (font->render_sdf) {
|
|
font->render_sdf = 0;
|
|
Flush_Cache(font);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Get the dimensions of the text surface */
|
|
if ((TTF_SizeUTF8(font, text_cpy, &width, &height) < 0) || !width) {
|
|
TTF_SetError("Text has zero width");
|
|
goto failure;
|
|
}
|
|
|
|
/* wrapLength is unsigned, but don't allow negative values */
|
|
if ((int)wrapLength < 0) {
|
|
TTF_SetError("Invalid parameter 'wrapLength'");
|
|
goto failure;
|
|
}
|
|
|
|
numLines = 1;
|
|
|
|
if (*text_cpy) {
|
|
int maxNumLines = 0;
|
|
size_t textlen = SDL_strlen(text_cpy);
|
|
numLines = 0;
|
|
|
|
do {
|
|
int extent = 0, max_count = 0, char_count = 0;
|
|
size_t save_textlen = (size_t)(-1);
|
|
char *save_text = NULL;
|
|
|
|
if (numLines >= maxNumLines) {
|
|
char **saved = strLines;
|
|
if (wrapLength == 0) {
|
|
maxNumLines += 32;
|
|
} else {
|
|
maxNumLines += (width / wrapLength) + 1;
|
|
}
|
|
strLines = (char **)SDL_realloc(strLines, maxNumLines * sizeof (*strLines));
|
|
if (strLines == NULL) {
|
|
strLines = saved;
|
|
SDL_OutOfMemory();
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
strLines[numLines++] = text_cpy;
|
|
|
|
if (TTF_MeasureUTF8(font, text_cpy, wrapLength, &extent, &max_count) < 0) {
|
|
TTF_SetError("Error measure text");
|
|
goto failure;
|
|
}
|
|
|
|
if (wrapLength != 0) {
|
|
if (max_count == 0) {
|
|
max_count = 1;
|
|
}
|
|
}
|
|
|
|
while (textlen > 0) {
|
|
int inc = 0;
|
|
int is_delim;
|
|
Uint32 c = UTF8_getch(text_cpy, textlen, &inc);
|
|
text_cpy += inc;
|
|
textlen -= inc;
|
|
|
|
if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
|
|
continue;
|
|
}
|
|
|
|
char_count += 1;
|
|
|
|
/* With wrapLength == 0, normal text rendering but newline aware */
|
|
is_delim = (wrapLength > 0) ? CharacterIsDelimiter(c) : CharacterIsNewLine(c);
|
|
|
|
/* Record last delimiter position */
|
|
if (is_delim) {
|
|
save_textlen = textlen;
|
|
save_text = text_cpy;
|
|
/* Break, if new line */
|
|
if (c == '\n' || c == '\r') {
|
|
*(text_cpy - 1) = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Break, if reach the limit */
|
|
if (char_count == max_count) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Cut at last delimiter/new lines, otherwise in the middle of the word */
|
|
if (save_text && textlen) {
|
|
text_cpy = save_text;
|
|
textlen = save_textlen;
|
|
}
|
|
} while (textlen > 0);
|
|
}
|
|
|
|
lineskip = TTF_FontLineSkip(font);
|
|
rowHeight = SDL_max(height, lineskip);
|
|
|
|
if (wrapLength == 0) {
|
|
/* Find the max of all line lengths */
|
|
if (numLines > 1) {
|
|
width = 0;
|
|
for (i = 0; i < numLines; i++) {
|
|
char save_c = 0;
|
|
int w, h;
|
|
|
|
/* Add end-of-line */
|
|
if (strLines) {
|
|
text = strLines[i];
|
|
if (i + 1 < numLines) {
|
|
save_c = strLines[i + 1][0];
|
|
strLines[i + 1][0] = '\0';
|
|
}
|
|
}
|
|
|
|
if (TTF_SizeUTF8(font, text, &w, &h) == 0) {
|
|
width = SDL_max(w, width);
|
|
}
|
|
|
|
/* Remove end-of-line */
|
|
if (strLines) {
|
|
if (i + 1 < numLines) {
|
|
strLines[i + 1][0] = save_c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (numLines <= 1 && font->horizontal_align == TTF_WRAPPED_ALIGN_LEFT) {
|
|
/* Don't go above wrapLength if you have only 1 line which hasn't been cut */
|
|
width = SDL_min((int)wrapLength, width);
|
|
} else {
|
|
width = wrapLength;
|
|
}
|
|
}
|
|
height = rowHeight + lineskip * (numLines - 1);
|
|
|
|
/* Support alpha blending */
|
|
fg.a = fg.a ? fg.a : SDL_ALPHA_OPAQUE;
|
|
bg.a = bg.a ? bg.a : SDL_ALPHA_OPAQUE;
|
|
|
|
/* Create surface for rendering */
|
|
if (render_mode == RENDER_SOLID) {
|
|
textbuf = Create_Surface_Solid(width, height, fg, &color);
|
|
} else if (render_mode == RENDER_SHADED) {
|
|
textbuf = Create_Surface_Shaded(width, height, fg, bg, &color);
|
|
} else if (render_mode == RENDER_BLENDED) {
|
|
textbuf = Create_Surface_Blended(width, height, fg, &color);
|
|
} else { /* render_mode == RENDER_LCD */
|
|
textbuf = Create_Surface_LCD(width, height, fg, bg, &color);
|
|
}
|
|
|
|
if (textbuf == NULL) {
|
|
goto failure;
|
|
}
|
|
|
|
/* Render each line */
|
|
for (i = 0; i < numLines; i++) {
|
|
int xstart, ystart, line_width, xoffset;
|
|
char save_c = 0;
|
|
|
|
/* Add end-of-line */
|
|
if (strLines) {
|
|
text = strLines[i];
|
|
if (i + 1 < numLines) {
|
|
save_c = strLines[i + 1][0];
|
|
strLines[i + 1][0] = '\0';
|
|
}
|
|
}
|
|
|
|
/* Initialize xstart, ystart and compute positions */
|
|
if (TTF_Size_Internal(font, text, STR_UTF8, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT) < 0) {
|
|
goto failure;
|
|
}
|
|
|
|
/* Move to i-th line */
|
|
ystart += i * lineskip;
|
|
|
|
/* Control left/right/center align of each bit of text */
|
|
if (font->horizontal_align == TTF_WRAPPED_ALIGN_RIGHT) {
|
|
xoffset = width - line_width;
|
|
} else if (font->horizontal_align == TTF_WRAPPED_ALIGN_CENTER) {
|
|
xoffset = width / 2 - line_width / 2;
|
|
} else {
|
|
xoffset = 0;
|
|
}
|
|
|
|
/* Render one text line to textbuf at (xstart, ystart) */
|
|
if (Render_Line(render_mode, font->render_subpixel, font, textbuf, xstart + xoffset, ystart, fg) < 0) {
|
|
goto failure;
|
|
}
|
|
|
|
/* Apply underline or strikethrough style, if needed */
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
Draw_Line(font, textbuf, xoffset, ystart + font->underline_top_row, line_width, font->line_thickness, color, render_mode);
|
|
}
|
|
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
Draw_Line(font, textbuf, xoffset, ystart + font->strikethrough_top_row, line_width, font->line_thickness, color, render_mode);
|
|
}
|
|
/* Remove end-of-line */
|
|
if (strLines) {
|
|
if (i + 1 < numLines) {
|
|
strLines[i + 1][0] = save_c;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strLines) {
|
|
SDL_free(strLines);
|
|
}
|
|
if (utf8_alloc) {
|
|
SDL_stack_free(utf8_alloc);
|
|
}
|
|
return textbuf;
|
|
failure:
|
|
if (textbuf) {
|
|
SDL_FreeSurface(textbuf);
|
|
}
|
|
if (strLines) {
|
|
SDL_free(strLines);
|
|
}
|
|
if (utf8_alloc) {
|
|
SDL_stack_free(utf8_alloc);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Solid_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_TEXT, fg, fg /* unused */, wrapLength, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_Solid_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_UTF8, fg, fg /* unused */, wrapLength, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_Solid_Wrapped(TTF_Font *font, const Uint16 *text, SDL_Color fg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, (const char *)text, STR_UNICODE, fg, fg /* unused */, wrapLength, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Shaded_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_TEXT, fg, bg, wrapLength, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_Shaded_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_UTF8, fg, bg, wrapLength, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_Shaded_Wrapped(TTF_Font *font, const Uint16 *text, SDL_Color fg, SDL_Color bg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, (const char *)text, STR_UNICODE, fg, bg, wrapLength, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Blended_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_TEXT, fg, fg /* unused */, wrapLength, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_Blended_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_UTF8, fg, fg /* unused */, wrapLength, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_Blended_Wrapped(TTF_Font *font, const Uint16 *text, SDL_Color fg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, (const char *)text, STR_UNICODE, fg, fg /* unused */, wrapLength, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_LCD_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_TEXT, fg, bg, wrapLength, RENDER_LCD);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUTF8_LCD_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, SDL_Color bg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, STR_UTF8, fg, bg, wrapLength, RENDER_LCD);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderUNICODE_LCD_Wrapped(TTF_Font *font, const Uint16 *text, SDL_Color fg, SDL_Color bg, Uint32 wrapLength)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, (const char *)text, STR_UNICODE, fg, bg, wrapLength, RENDER_LCD);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_Blended(TTF_Font *font, Uint16 ch, SDL_Color fg)
|
|
{
|
|
return TTF_RenderGlyph32_Blended(font, ch, fg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph32_Blended(TTF_Font *font, Uint32 ch, SDL_Color fg)
|
|
{
|
|
Uint8 utf8[7];
|
|
|
|
TTF_CHECK_POINTER(font, NULL);
|
|
|
|
if (!Char_to_UTF8(ch, utf8)) {
|
|
return NULL;
|
|
}
|
|
|
|
return TTF_RenderUTF8_Blended(font, (char *)utf8, fg);
|
|
}
|
|
|
|
void TTF_SetFontStyle(TTF_Font *font, int style)
|
|
{
|
|
int prev_style;
|
|
long face_style;
|
|
|
|
TTF_CHECK_POINTER(font,);
|
|
|
|
prev_style = font->style;
|
|
face_style = font->face->style_flags;
|
|
|
|
/* Don't add a style if already in the font, SDL_ttf doesn't need to handle them */
|
|
if (face_style & FT_STYLE_FLAG_BOLD) {
|
|
style &= ~TTF_STYLE_BOLD;
|
|
}
|
|
if (face_style & FT_STYLE_FLAG_ITALIC) {
|
|
style &= ~TTF_STYLE_ITALIC;
|
|
}
|
|
|
|
font->style = style;
|
|
|
|
TTF_initFontMetrics(font);
|
|
|
|
/* Flush the cache if the style has changed.
|
|
* Ignore styles which do not impact glyph drawning. */
|
|
if ((font->style | TTF_STYLE_NO_GLYPH_CHANGE) != (prev_style | TTF_STYLE_NO_GLYPH_CHANGE)) {
|
|
Flush_Cache(font);
|
|
}
|
|
}
|
|
|
|
int TTF_GetFontStyle(const TTF_Font *font)
|
|
{
|
|
int style;
|
|
long face_style;
|
|
|
|
TTF_CHECK_POINTER(font, -1);
|
|
|
|
style = font->style;
|
|
face_style = font->face->style_flags;
|
|
|
|
/* Add the style already in the font */
|
|
if (face_style & FT_STYLE_FLAG_BOLD) {
|
|
style |= TTF_STYLE_BOLD;
|
|
}
|
|
if (face_style & FT_STYLE_FLAG_ITALIC) {
|
|
style |= TTF_STYLE_ITALIC;
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
void TTF_SetFontOutline(TTF_Font *font, int outline)
|
|
{
|
|
TTF_CHECK_POINTER(font,);
|
|
|
|
font->outline_val = SDL_max(0, outline);
|
|
TTF_initFontMetrics(font);
|
|
Flush_Cache(font);
|
|
}
|
|
|
|
int TTF_GetFontOutline(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_POINTER(font, -1);
|
|
|
|
return font->outline_val;
|
|
}
|
|
|
|
void TTF_SetFontHinting(TTF_Font *font, int hinting)
|
|
{
|
|
TTF_CHECK_POINTER(font,);
|
|
|
|
if (hinting == TTF_HINTING_LIGHT || hinting == TTF_HINTING_LIGHT_SUBPIXEL) {
|
|
font->ft_load_target = FT_LOAD_TARGET_LIGHT;
|
|
} else if (hinting == TTF_HINTING_MONO) {
|
|
font->ft_load_target = FT_LOAD_TARGET_MONO;
|
|
} else if (hinting == TTF_HINTING_NONE) {
|
|
font->ft_load_target = FT_LOAD_NO_HINTING;
|
|
} else {
|
|
font->ft_load_target = FT_LOAD_TARGET_NORMAL;
|
|
}
|
|
|
|
font->render_subpixel = (hinting == TTF_HINTING_LIGHT_SUBPIXEL) ? 1 : 0;
|
|
#if TTF_USE_HARFBUZZ
|
|
/* update flag for HB */
|
|
hb_ft_font_set_load_flags(font->hb_font, FT_LOAD_DEFAULT | font->ft_load_target);
|
|
#endif
|
|
|
|
Flush_Cache(font);
|
|
}
|
|
|
|
int TTF_GetFontHinting(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_POINTER(font, -1);
|
|
|
|
if (font->ft_load_target == FT_LOAD_TARGET_LIGHT) {
|
|
if (font->render_subpixel == 0) {
|
|
return TTF_HINTING_LIGHT;
|
|
} else {
|
|
return TTF_HINTING_LIGHT_SUBPIXEL;
|
|
}
|
|
} else if (font->ft_load_target == FT_LOAD_TARGET_MONO) {
|
|
return TTF_HINTING_MONO;
|
|
} else if (font->ft_load_target == FT_LOAD_NO_HINTING) {
|
|
return TTF_HINTING_NONE;
|
|
}
|
|
return TTF_HINTING_NORMAL;
|
|
}
|
|
|
|
int TTF_SetFontSDF(TTF_Font *font, SDL_bool on_off)
|
|
{
|
|
TTF_CHECK_POINTER(font, -1);
|
|
#if TTF_USE_SDF
|
|
font->render_sdf = on_off;
|
|
Flush_Cache(font);
|
|
return 0;
|
|
#else
|
|
TTF_SetError("SDL_ttf compiled without SDF support");
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
SDL_bool TTF_GetFontSDF(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_POINTER(font, SDL_FALSE);
|
|
return font->render_sdf;
|
|
}
|
|
|
|
void TTF_SetFontWrappedAlign(TTF_Font *font, int align)
|
|
{
|
|
TTF_CHECK_POINTER(font,);
|
|
|
|
/* input not checked, unknown values assumed to be TTF_WRAPPED_ALIGN_LEFT */
|
|
if (align == TTF_WRAPPED_ALIGN_CENTER) {
|
|
font->horizontal_align = TTF_WRAPPED_ALIGN_CENTER;
|
|
} else if (align == TTF_WRAPPED_ALIGN_RIGHT) {
|
|
font->horizontal_align = TTF_WRAPPED_ALIGN_RIGHT;
|
|
} else {
|
|
font->horizontal_align = TTF_WRAPPED_ALIGN_LEFT;
|
|
}
|
|
}
|
|
|
|
int TTF_GetFontWrappedAlign(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_POINTER(font,-1);
|
|
return font->horizontal_align;
|
|
}
|
|
|
|
void TTF_Quit(void)
|
|
{
|
|
if (TTF_initialized) {
|
|
if (--TTF_initialized == 0) {
|
|
FT_Done_FreeType(library);
|
|
library = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
int TTF_WasInit(void)
|
|
{
|
|
return TTF_initialized;
|
|
}
|
|
|
|
/* don't use this function. It's just here for binary compatibility. */
|
|
int TTF_GetFontKerningSize(TTF_Font *font, int prev_index, int index)
|
|
{
|
|
FT_Vector delta;
|
|
|
|
TTF_CHECK_POINTER(font, -1);
|
|
|
|
FT_Get_Kerning(font->face, (FT_UInt)prev_index, (FT_UInt)index, FT_KERNING_DEFAULT, &delta);
|
|
return (int)(delta.x >> 6);
|
|
}
|
|
|
|
int TTF_GetFontKerningSizeGlyphs(TTF_Font *font, Uint16 previous_ch, Uint16 ch)
|
|
{
|
|
return TTF_GetFontKerningSizeGlyphs32(font, previous_ch, ch);
|
|
}
|
|
|
|
int TTF_GetFontKerningSizeGlyphs32(TTF_Font *font, Uint32 previous_ch, Uint32 ch)
|
|
{
|
|
FT_Error error;
|
|
c_glyph *prev_glyph, *glyph;
|
|
FT_Vector delta;
|
|
|
|
TTF_CHECK_POINTER(font, -1);
|
|
|
|
if (ch == UNICODE_BOM_NATIVE || ch == UNICODE_BOM_SWAPPED) {
|
|
return 0;
|
|
}
|
|
|
|
if (previous_ch == UNICODE_BOM_NATIVE || previous_ch == UNICODE_BOM_SWAPPED) {
|
|
return 0;
|
|
}
|
|
|
|
if (Find_GlyphMetrics(font, ch, &glyph) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (Find_GlyphMetrics(font, previous_ch, &prev_glyph) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
error = FT_Get_Kerning(font->face, prev_glyph->index, glyph->index, FT_KERNING_DEFAULT, &delta);
|
|
if (error) {
|
|
TTF_SetFTError("Couldn't get glyph kerning", error);
|
|
return -1;
|
|
}
|
|
return (int)(delta.x >> 6);
|
|
}
|
|
|
|
/* vi: set ts=4 sw=4 expandtab: */
|