6171 lines
205 KiB
C
6171 lines
205 KiB
C
/*
|
|
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
|
|
Copyright (C) 2001-2025 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 <SDL3/SDL.h>
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
|
#include <SDL3_ttf/SDL_textengine.h>
|
|
|
|
#include "SDL_hashtable.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_TRUETYPE_TABLES_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
|
|
#define DEFAULT_SDF_SPREAD 8
|
|
|
|
/* 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
|
|
|
|
// Enable PlutoSVG for color emoji rendering
|
|
#ifndef TTF_USE_PLUTOSVG
|
|
# define TTF_USE_PLUTOSVG 0
|
|
#endif
|
|
|
|
#ifndef TTF_DEFAULT_DPI
|
|
#define TTF_DEFAULT_DPI 72
|
|
#endif
|
|
|
|
// FT_FACE_FLAG_SVG and FT_HAS_SVG require freetype 2.12+
|
|
#ifndef FT_HAS_SVG
|
|
#define FT_FACE_FLAG_SVG ( 1L << 16 )
|
|
#define FT_HAS_SVG( face ) \
|
|
( !!( (face)->face_flags & FT_FACE_FLAG_SVG ) )
|
|
|
|
#define FT_GLYPH_FORMAT_SVG \
|
|
( ( (unsigned long)'S' << 24 ) | \
|
|
( (unsigned long)'V' << 16 ) | \
|
|
( (unsigned long)'G' << 8 ) | \
|
|
(unsigned long)' ' )
|
|
#endif // !FT_HAS_SVG
|
|
|
|
#define TTF_PROP_IOSTREAM_REFCOUNT "SDL_ttf.font.src.refcount"
|
|
|
|
/**
|
|
* ZERO WIDTH NO-BREAKSPACE (Unicode byte order mark)
|
|
*/
|
|
#define UNICODE_BOM_NATIVE 0xFEFF
|
|
#define UNICODE_BOM_SWAPPED 0xFFFE
|
|
|
|
#if defined(SDL_BUILD_MAJOR_VERSION)
|
|
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_MICRO_VERSION == SDL_BUILD_MICRO_VERSION);
|
|
#endif
|
|
|
|
// Limited by its encoding in SDL_VERSIONNUM
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MAJOR_VERSION_min, SDL_TTF_MAJOR_VERSION >= 0);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MAJOR_VERSION_max, SDL_TTF_MAJOR_VERSION <= 10);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MINOR_VERSION_min, SDL_TTF_MINOR_VERSION >= 0);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MINOR_VERSION_max, SDL_TTF_MINOR_VERSION <= 999);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MICRO_VERSION_min, SDL_TTF_MICRO_VERSION >= 0);
|
|
SDL_COMPILE_TIME_ASSERT(SDL_TTF_MICRO_VERSION_max, SDL_TTF_MICRO_VERSION <= 999);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
#include <hb.h>
|
|
#include <hb-ft.h>
|
|
#endif
|
|
|
|
#if TTF_USE_PLUTOSVG
|
|
#include <plutosvg.h>
|
|
#endif
|
|
|
|
// Round glyph to 16 bytes width and use SSE2 instructions
|
|
#if defined(__SSE2__)
|
|
# define HAVE_SSE2_INTRINSICS 1
|
|
#include <emmintrin.h>
|
|
#endif
|
|
|
|
// Round glyph width to 16 bytes use NEON instructions
|
|
#if defined(__ARM_NEON)
|
|
# define HAVE_NEON_INTRINSICS 1
|
|
#include <arm_neon.h>
|
|
#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 GlyphPosition {
|
|
TTF_Font *font;
|
|
FT_UInt index;
|
|
c_glyph *glyph;
|
|
int x_offset;
|
|
int y_offset;
|
|
int x_advance;
|
|
int y_advance;
|
|
int x;
|
|
int y;
|
|
int offset;
|
|
} GlyphPosition;
|
|
|
|
typedef struct GlyphPositions {
|
|
GlyphPosition *pos;
|
|
int len;
|
|
int width26dot6;
|
|
int height26dot6;
|
|
int num_clusters;
|
|
int maxlen;
|
|
} GlyphPositions;
|
|
|
|
typedef struct CachedGlyphPositions {
|
|
TTF_Direction direction;
|
|
Uint32 script;
|
|
char *text;
|
|
size_t length;
|
|
GlyphPositions positions;
|
|
} CachedGlyphPositions;
|
|
|
|
// A structure maintaining a list of fonts
|
|
typedef struct TTF_FontList {
|
|
TTF_Font *font;
|
|
struct TTF_FontList *next;
|
|
} TTF_FontList;
|
|
|
|
// The structure used to hold internal font information
|
|
struct TTF_Font {
|
|
// The name of the font
|
|
char *name;
|
|
|
|
// Freetype2 maintains all sorts of useful info itself
|
|
FT_Face face;
|
|
long face_index;
|
|
|
|
// Properties exposed to the application
|
|
SDL_PropertiesID props;
|
|
|
|
// The current font generation, changes when glyphs need to be rebuilt
|
|
Uint32 generation;
|
|
|
|
// Text objects using this font
|
|
SDL_HashTable *text;
|
|
|
|
// We'll cache these ourselves
|
|
float ptsize;
|
|
int hdpi;
|
|
int vdpi;
|
|
int height;
|
|
int ascent;
|
|
int descent;
|
|
int lineskip;
|
|
|
|
// The font style
|
|
TTF_FontStyleFlags style;
|
|
int weight;
|
|
int outline;
|
|
FT_Stroker stroker;
|
|
|
|
// Whether kerning is desired
|
|
bool enable_kerning;
|
|
#if !TTF_USE_HARFBUZZ
|
|
bool use_kerning;
|
|
#endif
|
|
|
|
// 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
|
|
SDL_HashTable *glyphs;
|
|
SDL_HashTable *glyph_indices;
|
|
|
|
// We are responsible for closing the font stream
|
|
SDL_IOStream *src;
|
|
Sint64 src_offset;
|
|
bool closeio;
|
|
FT_Open_Args args;
|
|
|
|
/* Internal buffer to store positions computed by TTF_Size_Internal()
|
|
* for rendered string by Render_Line() */
|
|
int next_cached_positions;
|
|
CachedGlyphPositions cached_positions[8];
|
|
GlyphPositions *positions;
|
|
|
|
// Hinting modes
|
|
int ft_load_target;
|
|
int render_subpixel;
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_font_t *hb_font;
|
|
hb_language_t hb_language;
|
|
#endif
|
|
Uint32 script; // ISO 15924 script tag
|
|
TTF_Direction direction;
|
|
bool render_sdf;
|
|
|
|
// Extra layout setting for wrapped text
|
|
TTF_HorizontalAlignment horizontal_align;
|
|
|
|
// Fallback fonts
|
|
TTF_FontList *fallbacks;
|
|
TTF_FontList *fallback_for;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
const char *text;
|
|
size_t length;
|
|
} TTF_Line;
|
|
|
|
// 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 struct
|
|
{
|
|
SDL_InitState init;
|
|
SDL_AtomicInt refcount;
|
|
SDL_AtomicInt generation;
|
|
SDL_Mutex *lock;
|
|
FT_Library library;
|
|
} TTF_state;
|
|
|
|
#define TTF_CHECK_INITIALIZED(errval) \
|
|
if (SDL_ShouldInit(&TTF_state.init)) { \
|
|
SDL_SetInitialized(&TTF_state.init, false); \
|
|
SDL_SetError("Library not initialized"); \
|
|
return errval; \
|
|
}
|
|
|
|
#define TTF_CHECK_POINTER(name, p, errval) \
|
|
if (!(p)) { \
|
|
SDL_InvalidParamError(name); \
|
|
return errval; \
|
|
}
|
|
|
|
#define TTF_CHECK_FONT(font, errval) \
|
|
TTF_CHECK_POINTER("font", font, errval)
|
|
|
|
typedef enum {
|
|
RENDER_SOLID = 0,
|
|
RENDER_SHADED,
|
|
RENDER_BLENDED,
|
|
RENDER_LCD
|
|
} render_mode_t;
|
|
|
|
#define NO_MEASUREMENT \
|
|
false, 0, NULL, NULL
|
|
|
|
|
|
static Uint32 TTF_GetNextFontGeneration(void)
|
|
{
|
|
Uint32 id = (Uint32)SDL_AtomicIncRef(&TTF_state.generation) + 1;
|
|
if (id == 0) {
|
|
id = (Uint32)SDL_AtomicIncRef(&TTF_state.generation) + 1;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
static bool 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);
|
|
|
|
#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 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 == 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(
|
|
/* prevent misaligned load: tmp = *src++; */
|
|
// eventually, we can expect the compiler to replace the memcpy call with something optimized
|
|
SDL_memcpy(&tmp, src++, sizeof(tmp));
|
|
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 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(
|
|
/* prevent misaligned load: tmp = *src++; */
|
|
SDL_memcpy(&tmp, src++, sizeof(tmp));
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
// Blended Opaque
|
|
static 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++ |= ((Uint32)*src++) << 24;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
// Blended non-opaque
|
|
static 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 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++ |= ((Uint32)*src++) << 24;
|
|
*dst++ |= ((Uint32)*src++) << 24;
|
|
*dst++ |= ((Uint32)*src++) << 24;
|
|
*dst++ |= ((Uint32)*src++) << 24;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src += srcskip;
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
|
|
static 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 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 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 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;
|
|
const uint8x16_t zero = vdupq_n_u8(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, (uint8x16_t)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, (uint32x4_t)sx01.val[0]); // or
|
|
r1 = vorrq_u32(d1, (uint32x4_t)sx01.val[1]); // or
|
|
r2 = vorrq_u32(d2, (uint32x4_t)sx23.val[0]); // or
|
|
r3 = vorrq_u32(d3, (uint32x4_t)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 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;
|
|
uint16x8x2_t sxm;
|
|
|
|
const uint16x8_t alpha = vmovq_n_u16(fg_alpha);
|
|
const uint16x8_t one = vmovq_n_u16(1);
|
|
const uint8x16_t zero = vdupq_n_u8(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((uint8x16_t)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
|
|
|
|
sxm.val[0] = vmulq_u16((uint16x8_t)sx.val[0], alpha); // x := i * fg.a
|
|
sxm.val[1] = vmulq_u16((uint16x8_t)sx.val[1], alpha);
|
|
|
|
Ls8 = vshrq_n_u16(sxm.val[0], 8); // x >> 8
|
|
Hs8 = vshrq_n_u16(sxm.val[1], 8);
|
|
|
|
sxm.val[0] = vaddq_u16(sxm.val[0], one); // x + 1
|
|
sxm.val[1] = vaddq_u16(sxm.val[1], one);
|
|
|
|
sxm.val[0] = vaddq_u16(sxm.val[0], Ls8); // x + 1 + (x >> 8)
|
|
sxm.val[1] = vaddq_u16(sxm.val[1], Hs8);
|
|
|
|
sxm.val[0] = vshrq_n_u16(sxm.val[0], 8); // ((x + 1 + (x >> 8)) >> 8
|
|
sxm.val[1] = vshrq_n_u16(sxm.val[1], 8);
|
|
|
|
sxm.val[0] = vshlq_n_u16(sxm.val[0], 8); // shift << 8, so we're prepared
|
|
sxm.val[1] = vshlq_n_u16(sxm.val[1], 8); // to have final format << 24
|
|
|
|
sx01 = vzipq_u8(zero, (uint8x16_t)sxm.val[0]); // interleave
|
|
sx23 = vzipq_u8(zero, (uint8x16_t)sxm.val[1]); // interleave
|
|
// already shifted by 24
|
|
|
|
r0 = vorrq_u32(d0, (uint32x4_t)sx01.val[0]); // or
|
|
r1 = vorrq_u32(d1, (uint32x4_t)sx01.val[1]); // or
|
|
r2 = vorrq_u32(d2, (uint32x4_t)sx23.val[0]); // or
|
|
r3 = vorrq_u32(d3, (uint32x4_t)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 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 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;
|
|
Uint64 tmp;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
/* prevent misaligned load: *dst++ |= *src++; */
|
|
SDL_memcpy(&tmp, src++, sizeof(tmp));
|
|
*dst++ |= tmp;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint64 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint64 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#elif defined(HAVE_BLIT_GLYPH_32)
|
|
static 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;
|
|
Uint32 tmp;
|
|
|
|
while (height--) {
|
|
/* *INDENT-OFF* */
|
|
DUFFS_LOOP4(
|
|
/* prevent misaligned load: *dst++ |= *src++; */
|
|
SDL_memcpy(&tmp, src++, sizeof(tmp));
|
|
*dst++ |= tmp;
|
|
, width);
|
|
/* *INDENT-ON* */
|
|
src = (const Uint32 *)((const Uint8 *)src + srcskip);
|
|
dst = (Uint32 *)((Uint8 *)dst + dstskip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_SSE2_INTRINSICS)
|
|
static 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 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_Direction direction, 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 * SDL_BYTESPERPIXEL(textbuf->format);
|
|
Uint8 *dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch + x_offset;
|
|
|
|
// No Underline/Strikethrough style if direction is vertical
|
|
if (direction == TTF_DIRECTION_TTB || direction == TTF_DIRECTION_BTT) {
|
|
return;
|
|
}
|
|
|
|
/* 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 Draw_Line_TextEngine(TTF_Direction direction, int width, int height, int column, int row, int line_width, int line_thickness, TTF_DrawOperation *ops, int *current_op)
|
|
{
|
|
int op_index = *current_op;
|
|
TTF_DrawOperation *op = &ops[op_index];
|
|
int tmp = row + line_thickness - height;
|
|
|
|
// No Underline/Strikethrough style if direction is vertical
|
|
if (direction == TTF_DIRECTION_TTB || direction == TTF_DIRECTION_BTT) {
|
|
return;
|
|
}
|
|
|
|
/* 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 'width'
|
|
line_width = SDL_min(line_width, width);
|
|
|
|
op = &ops[op_index++];
|
|
op->cmd = TTF_DRAW_COMMAND_FILL;
|
|
op->fill.rect.x = column;
|
|
op->fill.rect.y = row;
|
|
op->fill.rect.w = line_width;
|
|
op->fill.rect.h = line_thickness;
|
|
*current_op = op_index;
|
|
}
|
|
|
|
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 bool 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); \
|
|
int i; \
|
|
Uint8 fg_alpha = (fg ? fg->a : SDL_ALPHA_OPAQUE); \
|
|
for (i = 0; i < font->positions->len; i++) { \
|
|
GlyphPosition *pos = &font->positions->pos[i]; \
|
|
TTF_Font *glyph_font = pos->font; \
|
|
FT_UInt idx = pos->index; \
|
|
int x = pos->x; \
|
|
int y = pos->y; \
|
|
TTF_Image *image; \
|
|
\
|
|
if (Find_GlyphByIndex(glyph_font, idx, WB_WP_WC, WS, x & 63, NULL, &image)) { \
|
|
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 if (image->is_color == 0) { \
|
|
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 if (image->is_color == 0) { \
|
|
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 false; \
|
|
} \
|
|
} \
|
|
\
|
|
return true; \
|
|
} \
|
|
\
|
|
|
|
#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
|
|
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
static bool 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 (positions) 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 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
|
|
}
|
|
|
|
static bool Render_Line_TextEngine(TTF_Font *font, TTF_Direction direction, int xstart, int ystart, int width, int height, TTF_DrawOperation *ops, int *current_op, TTF_SubString *clusters, int *current_cluster, int cluster_offset, int line_index)
|
|
{
|
|
int i;
|
|
int op_index = *current_op;
|
|
int cluster_index = *current_cluster;
|
|
int last_offset = -1;
|
|
TTF_SubString *cluster = NULL;
|
|
|
|
SDL_Rect bounds;
|
|
bounds.x = xstart;
|
|
bounds.y = ystart;
|
|
bounds.w = 0;
|
|
bounds.h = font->height;
|
|
|
|
for (i = 0; i < font->positions->len; i++) {
|
|
GlyphPosition *pos = &font->positions->pos[i];
|
|
TTF_Font *glyph_font = pos->font;
|
|
FT_UInt idx = pos->index;
|
|
int x = pos->x;
|
|
int y = pos->y;
|
|
int offset = pos->offset;
|
|
c_glyph *glyph = pos->glyph;
|
|
int above_w, above_h;
|
|
int glyph_x = 0;
|
|
int glyph_y = 0;
|
|
int glyph_width = glyph->sz_width;
|
|
int glyph_rows = glyph->sz_rows;
|
|
TTF_DrawOperation *op;
|
|
|
|
// Position updated after glyph rendering
|
|
x = xstart + FT_FLOOR(x) + glyph->sz_left;
|
|
y = ystart + FT_FLOOR(y) - glyph->sz_top;
|
|
|
|
if (!glyph_font->render_sdf) {
|
|
// Make sure glyph is inside text area
|
|
above_w = x + glyph_width - width;
|
|
above_h = y + glyph_rows - height;
|
|
|
|
if (x < 0) {
|
|
int tmp = -x;
|
|
x = 0;
|
|
glyph_x += tmp;
|
|
glyph_width -= tmp;
|
|
}
|
|
if (above_w > 0) {
|
|
glyph_width -= above_w;
|
|
}
|
|
if (y < 0) {
|
|
int tmp = -y;
|
|
y = 0;
|
|
glyph_y += tmp;
|
|
glyph_rows -= tmp;
|
|
}
|
|
if (above_h > 0) {
|
|
glyph_rows -= above_h;
|
|
}
|
|
}
|
|
|
|
if (glyph_width > 0 && glyph_rows > 0) {
|
|
op = &ops[op_index++];
|
|
op->cmd = TTF_DRAW_COMMAND_COPY;
|
|
op->copy.text_offset = offset;
|
|
op->copy.glyph_font = glyph_font;
|
|
op->copy.glyph_index = idx;
|
|
op->copy.src.x = glyph_x;
|
|
op->copy.src.y = glyph_y;
|
|
op->copy.src.w = glyph_width + 2 * font->outline;
|
|
op->copy.src.h = glyph_rows + 2 * font->outline;
|
|
op->copy.dst.x = x;
|
|
op->copy.dst.y = y;
|
|
op->copy.dst.w = op->copy.src.w;
|
|
op->copy.dst.h = op->copy.src.h;
|
|
if (glyph_font->render_sdf) {
|
|
op->copy.dst.x -= DEFAULT_SDF_SPREAD;
|
|
op->copy.dst.y -= DEFAULT_SDF_SPREAD;
|
|
op->copy.dst.w -= DEFAULT_SDF_SPREAD;
|
|
op->copy.dst.h -= DEFAULT_SDF_SPREAD;
|
|
}
|
|
} else {
|
|
// Use the distance to the next glyph as our bounds width
|
|
glyph_width = FT_FLOOR(pos->x_advance) + 2 * font->outline;
|
|
}
|
|
|
|
bounds.x = x;
|
|
bounds.w = glyph_width;
|
|
if (offset != last_offset) {
|
|
cluster = &clusters[cluster_index++];
|
|
cluster->offset = cluster_offset + offset;
|
|
cluster->line_index = line_index;
|
|
if (direction == TTF_DIRECTION_INVALID) {
|
|
if (last_offset == -1) {
|
|
if (i < (font->positions->len - 1)) {
|
|
GlyphPosition *next = &font->positions->pos[i + 1];
|
|
if (offset < next->offset) {
|
|
cluster->flags = TTF_DIRECTION_LTR;
|
|
} else {
|
|
cluster->flags = TTF_DIRECTION_RTL;
|
|
}
|
|
} else {
|
|
cluster->flags = TTF_DIRECTION_INVALID;
|
|
}
|
|
} else {
|
|
if (offset > last_offset) {
|
|
cluster->flags = TTF_DIRECTION_LTR;
|
|
} else {
|
|
cluster->flags = TTF_DIRECTION_RTL;
|
|
}
|
|
}
|
|
} else {
|
|
cluster->flags = direction;
|
|
}
|
|
SDL_copyp(&cluster->rect, &bounds);
|
|
|
|
last_offset = offset;
|
|
} else if (cluster) {
|
|
SDL_GetRectUnion(&cluster->rect, &bounds, &cluster->rect);
|
|
}
|
|
}
|
|
|
|
*current_op = op_index;
|
|
*current_cluster = cluster_index;
|
|
return true;
|
|
}
|
|
|
|
/* 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_PixelFormat 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;
|
|
void *pixels;
|
|
size_t pitch;
|
|
|
|
/*
|
|
* 1/ Line size is "width * bytes_per_pixel"
|
|
*
|
|
* 2/ We add a right padding, because we process glyph from source to destination by
|
|
* blocks of 'alignment + 1' bytes. (Using SSE 128 instruction for instance when renderering,
|
|
* but this isn't always the case for all modes).
|
|
*
|
|
* We need to make sure the last transfer doesn't go too much outside!
|
|
*
|
|
* Considerer also for instance, that when we read 1 block of 16 bytes from source, for the blended
|
|
* format (bbp == 4), it writes(and reads) 4 blocks of 16 in the dest, like BG_Blended_SSE()).
|
|
*
|
|
* Remark: for Solid/Shaded, block ratio read/write is 1:1.
|
|
* For Color / LCD / SDF, it is byte vs byte or int. They are also fallback for
|
|
* Solid/Shaded/Blend, when it isn't contained in textbuf, see clip_glyph()
|
|
*
|
|
* So the pitch must contain "width * bytes_per_pixel", plus in the
|
|
* worst case, writing at last pixel (1 * bytes_per_pixel), an extra "alignment * bytes_per_pixel".
|
|
* (Using the destination bytes_per_pixel is a safe upper bound for the ratio).
|
|
*
|
|
* Also, we always write at a block-aligned adresses.
|
|
* - address is aligned 'SDL_aligned_alloc((alignment + 1), size)'
|
|
* - the pitch is aligned 'pitch &= ~alignment'
|
|
* So that each line is aligned.
|
|
*
|
|
* Remark: we can safely align the pitch (pitch &= ~alignment), without adding more to pitch,
|
|
* becaJuse we know we always write blocks at block-aligned addresses.
|
|
* So pitch won't need to be more than a multiple of block size.
|
|
*
|
|
* So pitch is:
|
|
* ((width * bytes_per_pixel) + (alignment * bytes_per_pixel) ) & ~alignment
|
|
* ==
|
|
* ((width + alignment) * bytes_per_pixel ) & ~alignment
|
|
*
|
|
* which is different from:
|
|
* ((width + alignment) & ~alignment)) * bytes_per_pixel (which fails.)
|
|
*
|
|
* Remark: to test memory issues, it is useful to patch SDL to use real memalign/free
|
|
* so that valgrind check more precisely out of bounds.
|
|
*/
|
|
if (width > SDL_MAX_SINT32 ||
|
|
height > SDL_MAX_SINT32 ||
|
|
!SDL_size_add_check_overflow(width, alignment, &pitch) ||
|
|
!SDL_size_mul_check_overflow(pitch, bytes_per_pixel, &pitch) ||
|
|
pitch > SDL_MAX_SINT32) {
|
|
return NULL;
|
|
}
|
|
pitch &= ~alignment;
|
|
|
|
if (!SDL_size_mul_check_overflow(height, pitch, &size)) {
|
|
// Overflow...
|
|
return NULL;
|
|
}
|
|
|
|
pixels = SDL_aligned_alloc(alignment + 1, size);
|
|
if (pixels == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
textbuf = SDL_CreateSurfaceFrom((int)width, (int)height, format, pixels, (int)pitch);
|
|
if (textbuf == NULL) {
|
|
SDL_aligned_free(pixels);
|
|
return NULL;
|
|
}
|
|
|
|
// Let SDL handle the memory allocation
|
|
textbuf->flags &= ~SDL_SURFACE_PREALLOCATED;
|
|
textbuf->flags |= SDL_SURFACE_SIMD_ALIGNED;
|
|
|
|
// Allocate a palette if needed
|
|
if (SDL_ISPIXELFORMAT_INDEXED(format)) {
|
|
if (!SDL_CreateSurfacePalette(textbuf)) {
|
|
SDL_DestroySurface(textbuf);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (bytes_per_pixel == 4) {
|
|
SDL_memset4(pixels, bgcolor, size / 4);
|
|
}
|
|
else {
|
|
SDL_memset(pixels, (bgcolor & 0xff), size);
|
|
}
|
|
|
|
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 = SDL_GetSurfacePalette(textbuf);
|
|
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_SetSurfaceColorKey(textbuf, 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 = SDL_GetSurfacePalette(textbuf);
|
|
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 | ((Uint32)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;
|
|
}
|
|
}
|
|
|
|
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 = (((Uint32)bg.a) << 24) | (bg.r << 16) | (bg.g << 8) | bg.b;
|
|
|
|
// Underline/Strikethrough color style
|
|
*color = (((Uint32)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;
|
|
}
|
|
}
|
|
|
|
return textbuf;
|
|
}
|
|
|
|
|
|
// rcg06192001 get linked library's version.
|
|
int TTF_Version(void)
|
|
{
|
|
return SDL_TTF_VERSION;
|
|
}
|
|
|
|
#if defined(USE_FREETYPE_ERRORS)
|
|
static bool 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";
|
|
}
|
|
return SDL_SetError("%s: %s", msg, err_msg);
|
|
}
|
|
#else
|
|
#define TTF_SetFTError(msg, error) SDL_SetError(msg)
|
|
#endif // USE_FREETYPE_ERRORS
|
|
|
|
bool TTF_Init(void)
|
|
{
|
|
bool result = true;
|
|
|
|
// 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
|
|
|
|
SDL_AtomicIncRef(&TTF_state.refcount);
|
|
|
|
if (!SDL_ShouldInit(&TTF_state.init)) {
|
|
return true;
|
|
}
|
|
|
|
FT_Error error = FT_Init_FreeType(&TTF_state.library);
|
|
if (error) {
|
|
TTF_SetFTError("Couldn't init FreeType engine", error);
|
|
result = false;
|
|
}
|
|
|
|
if (result) {
|
|
#if TTF_USE_SDF
|
|
#if 0
|
|
// Set various properties of the renderers.
|
|
int spread = DEFAULT_SDF_SPREAD;
|
|
int overlaps = 0;
|
|
FT_Property_Set(TTF_state.library, "bsdf", "spread", &spread);
|
|
FT_Property_Set(TTF_state.library, "sdf", "spread", &spread);
|
|
FT_Property_Set(TTF_state.library, "sdf", "overlaps", &overlaps);
|
|
#endif
|
|
#endif
|
|
|
|
#if TTF_USE_PLUTOSVG
|
|
FT_Property_Set(TTF_state.library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
|
|
#endif
|
|
|
|
TTF_state.lock = SDL_CreateMutex();
|
|
} else {
|
|
(void)SDL_AtomicDecRef(&TTF_state.refcount);
|
|
}
|
|
SDL_SetInitialized(&TTF_state.init, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
void TTF_GetFreeTypeVersion(int *major, int *minor, int *patch)
|
|
{
|
|
FT_Int ft_major = 0;
|
|
FT_Int ft_minor = 0;
|
|
FT_Int ft_patch = 0;
|
|
|
|
if (SDL_ShouldInit(&TTF_state.init)) {
|
|
SDL_SetInitialized(&TTF_state.init, false);
|
|
} else {
|
|
SDL_LockMutex(TTF_state.lock);
|
|
FT_Library_Version(TTF_state.library, &ft_major, &ft_minor, &ft_patch);
|
|
SDL_UnlockMutex(TTF_state.lock);
|
|
}
|
|
|
|
if (major) {
|
|
*major = (int)ft_major;
|
|
}
|
|
if (minor) {
|
|
*minor = (int)ft_minor;
|
|
}
|
|
if (patch) {
|
|
*patch = (int)ft_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 IOread(
|
|
FT_Stream stream,
|
|
unsigned long offset,
|
|
unsigned char *buffer,
|
|
unsigned long count
|
|
)
|
|
{
|
|
TTF_Font *font = (TTF_Font *)stream->descriptor.pointer;
|
|
SDL_SeekIO(font->src, font->src_offset + offset, SDL_IO_SEEK_SET);
|
|
return (unsigned long)SDL_ReadIO(font->src, buffer, count);
|
|
}
|
|
|
|
static void TTF_CloseFontSource(SDL_IOStream *src)
|
|
{
|
|
SDL_PropertiesID src_props = SDL_GetIOProperties(src);
|
|
int refcount = (int)SDL_GetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, 0);
|
|
if (refcount > 0) {
|
|
--refcount;
|
|
SDL_SetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, refcount);
|
|
return;
|
|
}
|
|
SDL_CloseIO(src);
|
|
}
|
|
|
|
TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
|
|
{
|
|
TTF_Font *existing_font = SDL_GetPointerProperty(props, TTF_PROP_FONT_CREATE_EXISTING_FONT, NULL);
|
|
const char *file = SDL_GetStringProperty(props, TTF_PROP_FONT_CREATE_FILENAME_STRING, NULL);
|
|
SDL_IOStream *src = SDL_GetPointerProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_POINTER, NULL);
|
|
Sint64 src_offset = SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_OFFSET_NUMBER, 0);
|
|
bool closeio = SDL_GetBooleanProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN, false);
|
|
float ptsize = SDL_GetFloatProperty(props, TTF_PROP_FONT_CREATE_SIZE_FLOAT, 0);
|
|
long face_index = (long)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_FACE_NUMBER, -1);
|
|
unsigned int hdpi = (unsigned int)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER, 0);
|
|
unsigned int vdpi = (unsigned int)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER, 0);
|
|
TTF_Font *font;
|
|
FT_Error error;
|
|
FT_Face face;
|
|
FT_Stream stream;
|
|
FT_CharMap found;
|
|
Sint64 position;
|
|
int i;
|
|
|
|
if (SDL_ShouldInit(&TTF_state.init)) {
|
|
SDL_SetInitialized(&TTF_state.init, false);
|
|
SDL_SetError("Library not initialized");
|
|
if (src && closeio) {
|
|
SDL_CloseIO(src);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
if (existing_font) {
|
|
if (!src) {
|
|
src = existing_font->src;
|
|
src_offset = existing_font->src_offset;
|
|
closeio = existing_font->closeio;
|
|
|
|
if (closeio) {
|
|
SDL_PropertiesID src_props = SDL_GetIOProperties(src);
|
|
int refcount = (int)SDL_GetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, 0);
|
|
++refcount;
|
|
SDL_SetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, refcount);
|
|
}
|
|
}
|
|
} else {
|
|
if (!src) {
|
|
if (!file) {
|
|
SDL_SetError("You must set either TTF_PROP_FONT_CREATE_FILENAME_STRING or TTF_PROP_FONT_CREATE_IOSTREAM_POINTER");
|
|
return NULL;
|
|
}
|
|
|
|
src = SDL_IOFromFile(file, "rb");
|
|
if (!src) {
|
|
return NULL;
|
|
}
|
|
closeio = true;
|
|
}
|
|
}
|
|
|
|
// Check to make sure we can seek in this stream
|
|
position = SDL_TellIO(src);
|
|
if (position < 0) {
|
|
SDL_SetError("Can't seek in stream");
|
|
if (closeio) {
|
|
TTF_CloseFontSource(src);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
font = (TTF_Font *)SDL_calloc(1, sizeof (*font));
|
|
if (font == NULL) {
|
|
SDL_SetError("Out of memory");
|
|
if (closeio) {
|
|
TTF_CloseFontSource(src);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
font->src = src;
|
|
font->src_offset = src_offset;
|
|
font->closeio = closeio;
|
|
font->generation = TTF_GetNextFontGeneration();
|
|
|
|
if (existing_font) {
|
|
if (existing_font->name) {
|
|
font->name = SDL_strdup(existing_font->name);
|
|
}
|
|
if (face_index == -1) {
|
|
face_index = existing_font->face_index;
|
|
}
|
|
if (ptsize == 0.0f) {
|
|
ptsize = existing_font->ptsize;
|
|
}
|
|
if (hdpi == 0) {
|
|
hdpi = existing_font->hdpi;
|
|
}
|
|
if (vdpi == 0) {
|
|
vdpi = existing_font->vdpi;
|
|
}
|
|
} else {
|
|
if (file) {
|
|
const char *name = SDL_strrchr(file, '/');
|
|
if (name) {
|
|
name += 1;
|
|
} else {
|
|
name = SDL_strrchr(file, '\\');
|
|
if (name) {
|
|
name += 1;
|
|
} else {
|
|
name = file;
|
|
}
|
|
}
|
|
font->name = SDL_strdup(name);
|
|
}
|
|
}
|
|
if (face_index < 0) {
|
|
face_index = 0;
|
|
}
|
|
if (hdpi == 0) {
|
|
hdpi = TTF_DEFAULT_DPI;
|
|
}
|
|
if (vdpi == 0) {
|
|
vdpi = TTF_DEFAULT_DPI;
|
|
}
|
|
|
|
font->text = SDL_CreateHashTable(0, false, SDL_HashPointer, SDL_KeyMatchPointer, NULL, NULL);
|
|
if (!font->text) {
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
|
|
font->glyphs = SDL_CreateHashTable(128, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL);
|
|
if (!font->glyphs) {
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
|
|
font->glyph_indices = SDL_CreateHashTable(128, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
|
|
if (!font->glyph_indices) {
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
|
|
stream = (FT_Stream)SDL_malloc(sizeof (*stream));
|
|
if (stream == NULL) {
|
|
SDL_SetError("Out of memory");
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
SDL_memset(stream, 0, sizeof (*stream));
|
|
|
|
stream->read = IOread;
|
|
stream->descriptor.pointer = font;
|
|
stream->pos = 0;
|
|
stream->size = (unsigned long)(SDL_GetIOSize(src) - src_offset);
|
|
|
|
font->args.flags = FT_OPEN_STREAM;
|
|
font->args.stream = stream;
|
|
|
|
SDL_LockMutex(TTF_state.lock);
|
|
error = FT_Open_Face(TTF_state.library, &font->args, face_index, &face);
|
|
SDL_UnlockMutex(TTF_state.lock);
|
|
if (error || face == NULL) {
|
|
TTF_SetFTError("Couldn't load font file", error);
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
font->face = face;
|
|
font->face_index = face_index;
|
|
|
|
// 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
|
|
if (existing_font) {
|
|
font->style = existing_font->style;
|
|
font->weight = existing_font->weight;
|
|
font->outline = existing_font->outline;
|
|
font->ft_load_target = existing_font->ft_load_target;
|
|
font->enable_kerning = existing_font->enable_kerning;
|
|
} else {
|
|
font->style = TTF_STYLE_NORMAL;
|
|
font->outline = 0;
|
|
font->ft_load_target = FT_LOAD_TARGET_NORMAL;
|
|
TTF_SetFontKerning(font, true);
|
|
|
|
// Retrieve the weight from the OS2 TrueType table
|
|
const TT_OS2 *os2_table = (const TT_OS2 *)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
|
|
if (os2_table != NULL && os2_table->usWeightClass != 0) {
|
|
font->weight = os2_table->usWeightClass;
|
|
} else if (face->style_flags & FT_STYLE_FLAG_BOLD) {
|
|
font->weight = TTF_FONT_WEIGHT_BOLD;
|
|
} else {
|
|
font->weight = TTF_FONT_WEIGHT_NORMAL;
|
|
}
|
|
}
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
font->hb_font = hb_ft_font_create(face, NULL);
|
|
if (font->hb_font == NULL) {
|
|
SDL_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);
|
|
|
|
font->hb_language = hb_language_from_string("", -1);
|
|
#endif
|
|
|
|
if (!TTF_SetFontSizeDPI(font, ptsize, hdpi, vdpi)) {
|
|
TTF_SetFTError("Couldn't set font size", error);
|
|
TTF_CloseFont(font);
|
|
return NULL;
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
TTF_Font *TTF_OpenFont(const char *file, float ptsize)
|
|
{
|
|
TTF_Font *font = NULL;
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
if (props) {
|
|
SDL_SetStringProperty(props, TTF_PROP_FONT_CREATE_FILENAME_STRING, file);
|
|
SDL_SetFloatProperty(props, TTF_PROP_FONT_CREATE_SIZE_FLOAT, ptsize);
|
|
font = TTF_OpenFontWithProperties(props);
|
|
SDL_DestroyProperties(props);
|
|
}
|
|
return font;
|
|
}
|
|
|
|
TTF_Font *TTF_OpenFontIO(SDL_IOStream *src, bool closeio, float ptsize)
|
|
{
|
|
TTF_Font *font = NULL;
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
if (props) {
|
|
SDL_SetPointerProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_POINTER, src);
|
|
SDL_SetBooleanProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN, closeio);
|
|
SDL_SetFloatProperty(props, TTF_PROP_FONT_CREATE_SIZE_FLOAT, ptsize);
|
|
font = TTF_OpenFontWithProperties(props);
|
|
SDL_DestroyProperties(props);
|
|
}
|
|
return font;
|
|
}
|
|
|
|
TTF_Font *TTF_CopyFont(TTF_Font *existing_font)
|
|
{
|
|
TTF_Font *font = NULL;
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
if (props) {
|
|
SDL_SetPointerProperty(props, TTF_PROP_FONT_CREATE_EXISTING_FONT, existing_font);
|
|
font = TTF_OpenFontWithProperties(props);
|
|
SDL_DestroyProperties(props);
|
|
}
|
|
return font;
|
|
}
|
|
|
|
static bool AddFontTextReference(TTF_Font *font, TTF_Text *text)
|
|
{
|
|
return SDL_InsertIntoHashTable(font->text, text, NULL, true);
|
|
}
|
|
|
|
static bool RemoveFontTextReference(TTF_Font *font, TTF_Text *text)
|
|
{
|
|
return SDL_RemoveFromHashTable(font->text, text);
|
|
}
|
|
|
|
static bool SDLCALL UpdateFontTextCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
|
|
{
|
|
TTF_Text *text = (TTF_Text *)key;
|
|
(void)userdata;
|
|
(void)table;
|
|
(void)value;
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
static void UpdateFontText(TTF_Font *font, TTF_Font *initial_font)
|
|
{
|
|
if (!initial_font) {
|
|
initial_font = font;
|
|
} else if (font == initial_font) {
|
|
// font fallback loop
|
|
return;
|
|
}
|
|
|
|
if (font->text) {
|
|
SDL_IterateHashTable(font->text, UpdateFontTextCallback, NULL);
|
|
}
|
|
|
|
for (TTF_FontList *list = font->fallback_for; list; list = list->next) {
|
|
UpdateFontText(list->font, initial_font);
|
|
}
|
|
}
|
|
|
|
SDL_PropertiesID TTF_GetFontProperties(TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
if (!font->props) {
|
|
font->props = SDL_CreateProperties();
|
|
}
|
|
return font->props;
|
|
}
|
|
|
|
Uint32 TTF_GetFontGeneration(TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return font->generation;
|
|
}
|
|
|
|
bool TTF_AddFallbackFont(TTF_Font *font, TTF_Font *fallback)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
TTF_CHECK_POINTER("fallback", fallback, false);
|
|
|
|
TTF_FontList *fallback_entry = (TTF_FontList *)SDL_calloc(1, sizeof(*fallback_entry));
|
|
TTF_FontList *fallback_for_entry = (TTF_FontList *)SDL_calloc(1, sizeof(*fallback_entry));
|
|
if (!fallback_entry || !fallback_for_entry) {
|
|
SDL_free(fallback_entry);
|
|
SDL_free(fallback_for_entry);
|
|
return false;
|
|
}
|
|
|
|
TTF_FontList *prev = NULL;
|
|
for (TTF_FontList *list = font->fallbacks; list; prev = list, list = list->next) {
|
|
continue;
|
|
}
|
|
fallback_entry->font = fallback;
|
|
if (prev) {
|
|
prev->next = fallback_entry;
|
|
} else {
|
|
font->fallbacks = fallback_entry;
|
|
}
|
|
|
|
prev = NULL;
|
|
for (TTF_FontList *list = fallback->fallback_for; list; prev = list, list = list->next) {
|
|
continue;
|
|
}
|
|
fallback_for_entry->font = font;
|
|
if (prev) {
|
|
prev->next = fallback_for_entry;
|
|
} else {
|
|
fallback->fallback_for = fallback_for_entry;
|
|
}
|
|
|
|
UpdateFontText(font, NULL);
|
|
return true;
|
|
}
|
|
|
|
void TTF_RemoveFallbackFont(TTF_Font *font, TTF_Font *fallback)
|
|
{
|
|
if (!font || !fallback) {
|
|
return;
|
|
}
|
|
|
|
TTF_FontList *prev = NULL;
|
|
for (TTF_FontList *list = font->fallbacks; list; prev = list, list = list->next) {
|
|
if (fallback == list->font) {
|
|
if (prev) {
|
|
prev->next = list->next;
|
|
} else {
|
|
font->fallbacks = list->next;
|
|
}
|
|
SDL_free(list);
|
|
break;
|
|
}
|
|
}
|
|
|
|
prev = NULL;
|
|
for (TTF_FontList *list = fallback->fallback_for; list; prev = list, list = list->next) {
|
|
if (font == list->font) {
|
|
if (prev) {
|
|
prev->next = list->next;
|
|
} else {
|
|
fallback->fallback_for = list->next;
|
|
}
|
|
SDL_free(list);
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
|
|
void TTF_ClearFallbackFonts(TTF_Font *font)
|
|
{
|
|
if (!font) {
|
|
return;
|
|
}
|
|
|
|
while (font->fallbacks) {
|
|
TTF_RemoveFallbackFont(font, font->fallbacks->font);
|
|
}
|
|
}
|
|
|
|
// Update font parameter depending on a style change
|
|
static void 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, translate positionning by 1 * outline */
|
|
if (font->outline > 0) {
|
|
int fo = font->outline;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
Flush_Glyph_Image(&glyph->pixmap);
|
|
Flush_Glyph_Image(&glyph->bitmap);
|
|
}
|
|
|
|
static bool SDLCALL FlushCacheCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
|
|
{
|
|
c_glyph *glyph = (c_glyph *)value;
|
|
(void)userdata;
|
|
(void)table;
|
|
(void)key;
|
|
if (glyph->stored) {
|
|
Flush_Glyph(glyph);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void Flush_Cache(TTF_Font *font)
|
|
{
|
|
SDL_IterateHashTable(font->glyphs, FlushCacheCallback, NULL);
|
|
|
|
for (unsigned int i = 0; i < SDL_arraysize(font->cached_positions); ++i) {
|
|
CachedGlyphPositions *cached = &font->cached_positions[i];
|
|
if (cached->text) {
|
|
SDL_free(cached->text);
|
|
cached->text = NULL;
|
|
cached->length = 0;
|
|
}
|
|
if (cached->positions.pos) {
|
|
SDL_free(cached->positions.pos);
|
|
cached->positions.pos = NULL;
|
|
cached->positions.len = 0;
|
|
cached->positions.maxlen = 0;
|
|
}
|
|
}
|
|
font->positions = NULL;
|
|
|
|
font->generation = TTF_GetNextFontGeneration();
|
|
}
|
|
|
|
static bool 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
|
|
|
|
if (FT_HAS_SVG(font->face)) {
|
|
// We won't get metrics unless we add FT_LOAD_COLOR
|
|
ft_load |= FT_LOAD_COLOR;
|
|
}
|
|
|
|
error = FT_Load_Glyph(font->face, cached->index, ft_load);
|
|
if (error) {
|
|
return TTF_SetFTError("FT_Load_Glyph() failed", error);
|
|
}
|
|
|
|
// Get our glyph shortcut
|
|
slot = font->face->glyph;
|
|
|
|
if (want & CACHED_LCD) {
|
|
if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
|
|
return SDL_SetError("LCD mode not possible with bitmap font");
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
cached->sz_width += 2 * DEFAULT_SDF_SPREAD;
|
|
cached->sz_rows += 2 * DEFAULT_SDF_SPREAD;
|
|
}
|
|
|
|
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 && slot->format != FT_GLYPH_FORMAT_SVG) {
|
|
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 > 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) {
|
|
return TTF_SetFTError("FT_Get_Glyph() failed", error);
|
|
}
|
|
|
|
if (font->outline > 0) {
|
|
FT_Glyph_Stroke(&glyph, font->stroker, 1 /* delete the original glyph */);
|
|
}
|
|
|
|
// Render the glyph
|
|
error = FT_Glyph_To_Bitmap(&glyph, ft_render_mode, 0, 1);
|
|
if (error) {
|
|
FT_Done_Glyph(glyph);
|
|
return TTF_SetFTError("FT_Glyph_To_Bitmap() failed", error);
|
|
}
|
|
|
|
// 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) {
|
|
// Don't fail entirely, just use a 0x0 sized bitmap
|
|
//return TTF_SetFTError("FT_Render_Glyph() failed", error);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/* Some robustess: loading an SVG emoji (FT_LOAD_COLOR), and rendering it a solid (FT_RENDER_MODE_MONO)
|
|
* makes previous FT_Render_Glyph() failed */
|
|
if (!src->buffer) {
|
|
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 && (want & CACHED_COLOR)) {
|
|
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) {
|
|
return false;
|
|
}
|
|
|
|
// 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 if (src->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
|
while (quotient--) {
|
|
Uint8 r, g, b, a;
|
|
b = *srcp++;
|
|
g = *srcp++;
|
|
r = *srcp++;
|
|
a = *srcp++;
|
|
unsigned char c = 0;
|
|
if (a != 0) {
|
|
// Technically we should do this in linear colorspace
|
|
// Y = 0.2126 * R + 0.7152 * G + 0.0722 * B
|
|
c = 255 - (unsigned char)(((int)r * 54) / 255 +
|
|
((int)g * 182) / 255 +
|
|
((int)b * 18) / 255);
|
|
}
|
|
*dstp++ = (c >= 0x80) ? 1 : 0;
|
|
}
|
|
} 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) {
|
|
if (want & CACHED_COLOR) {
|
|
SDL_memcpy(dstp, srcp, 4 * src->width);
|
|
} else {
|
|
// Convert to grayscale
|
|
while (quotient--) {
|
|
Uint8 r, g, b, a;
|
|
b = *srcp++;
|
|
g = *srcp++;
|
|
r = *srcp++;
|
|
a = *srcp++;
|
|
if (a != 0) {
|
|
// Technically we should do this in linear colorspace
|
|
// Y = 0.2126 * R + 0.7152 * G + 0.0722 * B
|
|
*dstp++ = 255 - (Uint8)(((int)r * 54) / 255 +
|
|
((int)g * 182) / 255 +
|
|
((int)b * 18) / 255);
|
|
} else {
|
|
*dstp++ = 0;
|
|
}
|
|
}
|
|
}
|
|
#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 {
|
|
SDL_memcpy(dstp, srcp, src->width);
|
|
}
|
|
}
|
|
}
|
|
#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 && (want & CACHED_COLOR)) {
|
|
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 true;
|
|
}
|
|
|
|
static bool 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)
|
|
{
|
|
c_glyph *glyph = NULL;
|
|
if (!SDL_FindInHashTable(font->glyphs, (const void *)(uintptr_t)idx, (const void **)&glyph)) {
|
|
glyph = (c_glyph *)SDL_calloc(1, sizeof(*glyph));
|
|
if (!glyph) {
|
|
return false;
|
|
}
|
|
glyph->index = idx;
|
|
|
|
if (!SDL_InsertIntoHashTable(font->glyphs, (const void *)(uintptr_t)idx, (const void *)glyph, true)) {
|
|
SDL_free(glyph);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
/* Not 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 want = CACHED_METRICS | want_bitmap | want_pixmap | want_color | want_lcd | want_subpixel;
|
|
|
|
if (glyph->subpixel.translation == translation) {
|
|
want &= ~CACHED_SUBPIX;
|
|
}
|
|
|
|
if ((glyph->stored & want) == want) {
|
|
return true;
|
|
}
|
|
|
|
if (want_color || want_pixmap || want_lcd) {
|
|
if (glyph->stored & (CACHED_COLOR|CACHED_PIXMAP|CACHED_LCD)) {
|
|
Flush_Glyph(glyph);
|
|
}
|
|
}
|
|
|
|
return Load_Glyph(font, glyph, want, translation);
|
|
} else {
|
|
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) {
|
|
return true;
|
|
}
|
|
} else if (want_bitmap) {
|
|
if (glyph->stored & CACHED_BITMAP) {
|
|
return true;
|
|
}
|
|
} else if (want_color) {
|
|
if (glyph->stored & CACHED_COLOR) {
|
|
return true;
|
|
}
|
|
} else if (want_lcd) {
|
|
if (glyph->stored & CACHED_LCD) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// Get metrics
|
|
if (glyph->stored) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
return Load_Glyph(font, glyph, want, 0);
|
|
}
|
|
}
|
|
|
|
static FT_UInt get_char_index(TTF_Font *font, Uint32 ch)
|
|
{
|
|
FT_UInt idx = 0;
|
|
const void *value;
|
|
if (!SDL_FindInHashTable(font->glyph_indices, (const void *)(uintptr_t)ch, &value)) {
|
|
idx = FT_Get_Char_Index(font->face, ch);
|
|
SDL_InsertIntoHashTable(font->glyph_indices, (const void *)(uintptr_t)ch, (const void *)(uintptr_t)idx, true);
|
|
} else {
|
|
idx = (FT_UInt)(uintptr_t)value;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static FT_UInt get_char_index_fallback(TTF_Font *font, Uint32 ch, TTF_Font *initial_font, TTF_Font **glyph_font)
|
|
{
|
|
if (!initial_font) {
|
|
initial_font = font;
|
|
} else if (font == initial_font) {
|
|
// font fallback loop
|
|
return 0;
|
|
}
|
|
|
|
FT_UInt idx = get_char_index(font, ch);
|
|
if (idx > 0) {
|
|
if (glyph_font) {
|
|
*glyph_font = font;
|
|
}
|
|
} else {
|
|
for (TTF_FontList *list = font->fallbacks; list; list = list->next) {
|
|
idx = get_char_index_fallback(list->font, ch, initial_font, glyph_font);
|
|
if (idx > 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
|
|
|
|
static bool Find_GlyphMetrics(TTF_Font *font, Uint32 ch, c_glyph **out_glyph, bool allow_fallback)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
TTF_Font *glyph_font = font;
|
|
FT_UInt idx;
|
|
if (allow_fallback) {
|
|
idx = get_char_index_fallback(font, ch, NULL, &glyph_font);
|
|
} else {
|
|
idx = get_char_index(font, ch);
|
|
}
|
|
return Find_GlyphByIndex(glyph_font, idx, 0, 0, 0, 0, 0, 0, out_glyph, NULL);
|
|
}
|
|
|
|
bool TTF_FontHasGlyph(TTF_Font *font, Uint32 ch)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
return (get_char_index_fallback(font, ch, NULL, NULL) > 0);
|
|
}
|
|
|
|
SDL_Surface *TTF_GetGlyphImage(TTF_Font *font, Uint32 ch, TTF_ImageType *image_type)
|
|
{
|
|
FT_UInt idx;
|
|
|
|
if (image_type) {
|
|
*image_type = TTF_IMAGE_INVALID;
|
|
}
|
|
|
|
TTF_CHECK_FONT(font, NULL);
|
|
|
|
TTF_Font *glyph_font = NULL;
|
|
idx = get_char_index_fallback(font, ch, NULL, &glyph_font);
|
|
if (idx == 0) {
|
|
SDL_SetError("Codepoint not in font");
|
|
return NULL;
|
|
}
|
|
|
|
return TTF_GetGlyphImageForIndex(glyph_font, idx, image_type);
|
|
}
|
|
|
|
SDL_Surface *TTF_GetGlyphImageForIndex(TTF_Font *font, Uint32 glyph_index, TTF_ImageType *image_type)
|
|
{
|
|
const int alignment = Get_Alignment() - 1;
|
|
TTF_Image *image;
|
|
SDL_Surface *surface;
|
|
const Uint8 *src;
|
|
|
|
if (image_type) {
|
|
*image_type = TTF_IMAGE_INVALID;
|
|
}
|
|
|
|
TTF_CHECK_FONT(font, NULL);
|
|
|
|
if (!Find_GlyphByIndex(font, glyph_index, COLOR, 0, 0, NULL, &image)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (image->width == 0 || image->rows == 0) {
|
|
return SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_ARGB8888);
|
|
}
|
|
|
|
surface = SDL_CreateSurface(image->width, image->rows, SDL_PIXELFORMAT_ARGB8888);
|
|
if (!surface) {
|
|
return NULL;
|
|
}
|
|
|
|
src = image->buffer + alignment;
|
|
|
|
if (image->is_color) {
|
|
if (image_type) {
|
|
// We can't tell the difference between SDF data and say, color emoji
|
|
// Hopefully the application sets the right mode on the font.
|
|
*image_type = (font->render_sdf ? TTF_IMAGE_SDF : TTF_IMAGE_COLOR);
|
|
}
|
|
|
|
if (surface->pitch == image->pitch) {
|
|
SDL_memcpy(surface->pixels, src, image->rows * image->pitch);
|
|
} else {
|
|
int row;
|
|
Uint8 *dst = (Uint8 *)surface->pixels;
|
|
size_t length = image->width * 4;
|
|
for (row = 0; row < image->rows; ++row) {
|
|
SDL_memcpy(dst, src, length);
|
|
src += image->pitch;
|
|
dst += surface->pitch;
|
|
}
|
|
}
|
|
} else {
|
|
if (image_type) {
|
|
*image_type = TTF_IMAGE_ALPHA;
|
|
}
|
|
|
|
int row, col;
|
|
Uint32 *dst = (Uint32 *)surface->pixels;
|
|
int skip = (surface->pitch - surface->w * 4) / 4;
|
|
for (row = 0; row < image->rows; ++row) {
|
|
for (col = 0; col < image->width; ++col) {
|
|
Uint32 v = src[col];
|
|
*dst++ = (0x00FFFFFF | v << 24);
|
|
}
|
|
src += image->pitch;
|
|
dst += skip;
|
|
}
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
bool TTF_GetGlyphMetrics(TTF_Font *font, Uint32 ch, int *minx, int *maxx, int *miny, int *maxy, int *advance)
|
|
{
|
|
c_glyph *glyph;
|
|
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
if (!Find_GlyphMetrics(font, ch, &glyph, true)) {
|
|
return false;
|
|
}
|
|
|
|
if (minx) {
|
|
*minx = glyph->sz_left;
|
|
}
|
|
if (maxx) {
|
|
*maxx = glyph->sz_left + glyph->sz_width;
|
|
*maxx += 2 * font->outline;
|
|
}
|
|
if (miny) {
|
|
*miny = glyph->sz_top - glyph->sz_rows;
|
|
}
|
|
if (maxy) {
|
|
*maxy = glyph->sz_top;
|
|
*maxy += 2 * font->outline;
|
|
}
|
|
if (advance) {
|
|
*advance = FT_CEIL(glyph->advance);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetGlyphKerning(TTF_Font *font, Uint32 previous_ch, Uint32 ch, int *kerning)
|
|
{
|
|
FT_Error error;
|
|
c_glyph *prev_glyph, *glyph;
|
|
FT_Vector delta;
|
|
|
|
if (kerning) {
|
|
*kerning = 0;
|
|
}
|
|
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
if (ch == UNICODE_BOM_NATIVE || ch == UNICODE_BOM_SWAPPED) {
|
|
return true;
|
|
}
|
|
|
|
if (previous_ch == UNICODE_BOM_NATIVE || previous_ch == UNICODE_BOM_SWAPPED) {
|
|
return true;
|
|
}
|
|
|
|
if (Find_GlyphMetrics(font, ch, &glyph, false) &&
|
|
Find_GlyphMetrics(font, previous_ch, &prev_glyph, false)) {
|
|
error = FT_Get_Kerning(font->face, prev_glyph->index, glyph->index, FT_KERNING_DEFAULT, &delta);
|
|
if (error) {
|
|
return TTF_SetFTError("Couldn't get glyph kerning", error);
|
|
}
|
|
|
|
if (kerning) {
|
|
*kerning = (int)(delta.x >> 6);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool CollectGlyphsFromFont(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, GlyphPositions *positions)
|
|
{
|
|
#if TTF_USE_HARFBUZZ
|
|
// Create a buffer for harfbuzz to use
|
|
hb_buffer_t *hb_buffer = hb_buffer_create();
|
|
if (!hb_buffer) {
|
|
SDL_SetError("Cannot create harfbuzz buffer");
|
|
return false;
|
|
}
|
|
|
|
// Set global configuration
|
|
hb_buffer_set_language(hb_buffer, font->hb_language);
|
|
hb_buffer_set_direction(hb_buffer, (hb_direction_t)direction);
|
|
hb_buffer_set_script(hb_buffer, hb_script_from_iso15924_tag(script));
|
|
|
|
// Layout the text
|
|
hb_buffer_add_utf8(hb_buffer, text, (int)length, 0, -1);
|
|
hb_buffer_guess_segment_properties(hb_buffer);
|
|
|
|
hb_feature_t userfeatures[1];
|
|
userfeatures[0].tag = HB_TAG('k','e','r','n');
|
|
userfeatures[0].value = font->enable_kerning;
|
|
userfeatures[0].start = HB_FEATURE_GLOBAL_START;
|
|
userfeatures[0].end = HB_FEATURE_GLOBAL_END;
|
|
|
|
hb_shape(font->hb_font, hb_buffer, userfeatures, 1);
|
|
|
|
// Get the result
|
|
unsigned int glyph_count_u = 0;
|
|
hb_glyph_info_t *hb_glyph_info = hb_buffer_get_glyph_infos(hb_buffer, &glyph_count_u);
|
|
hb_glyph_position_t *hb_glyph_position = hb_buffer_get_glyph_positions(hb_buffer, &glyph_count_u);
|
|
|
|
// Adjust for bold text
|
|
int advance_if_bold = 0;
|
|
if (TTF_HANDLE_STYLE_BOLD(font)) {
|
|
advance_if_bold = F26Dot6(font->glyph_overhang);
|
|
}
|
|
|
|
// Realloc, if needed
|
|
int glyph_count = (int)glyph_count_u;
|
|
if (glyph_count > positions->maxlen) {
|
|
GlyphPosition *saved = positions->pos;
|
|
positions->pos = (GlyphPosition *)SDL_realloc(positions->pos, glyph_count * sizeof(*positions->pos));
|
|
if (positions->pos) {
|
|
positions->maxlen = glyph_count;
|
|
} else {
|
|
positions->pos = saved;
|
|
hb_buffer_destroy(hb_buffer);
|
|
return false;
|
|
}
|
|
}
|
|
positions->len = glyph_count;
|
|
|
|
for (int i = 0; i < glyph_count; ++i) {
|
|
GlyphPosition *pos = &positions->pos[i];
|
|
pos->font = font;
|
|
pos->index = hb_glyph_info[i].codepoint;
|
|
pos->x_advance = hb_glyph_position[i].x_advance + advance_if_bold;
|
|
pos->y_advance = hb_glyph_position[i].y_advance;
|
|
pos->x_offset = hb_glyph_position[i].x_offset;
|
|
pos->y_offset = hb_glyph_position[i].y_offset;
|
|
pos->offset = (int)hb_glyph_info[i].cluster;
|
|
if (!Find_GlyphByIndex(font, pos->index, 0, 0, 0, 0, 0, 0, &pos->glyph, NULL)) {
|
|
return SDL_SetError("Couldn't find glyph %u in font", pos->index);
|
|
}
|
|
}
|
|
hb_buffer_destroy(hb_buffer);
|
|
|
|
#else
|
|
(void)direction;
|
|
(void)script;
|
|
bool skip_first = true;
|
|
FT_UInt prev_index = 0;
|
|
FT_Pos prev_delta = 0;
|
|
|
|
positions->len = 0;
|
|
|
|
// Load each character and sum it's bounding box
|
|
const char *start = text;
|
|
int offset = 0;
|
|
while (length > 0) {
|
|
offset = (int)(text - start);
|
|
Uint32 c = SDL_StepUTF8(&text, &length);
|
|
if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
|
|
continue;
|
|
}
|
|
|
|
if (c == 0 && length > 0) {
|
|
--length;
|
|
}
|
|
|
|
FT_UInt idx = get_char_index(font, c);
|
|
c_glyph *glyph = NULL;
|
|
if (!Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
|
|
return SDL_SetError("Couldn't find glyph %u in font", idx);
|
|
}
|
|
|
|
// Realloc, if needed
|
|
if (positions->len >= positions->maxlen) {
|
|
GlyphPosition *saved = positions->pos;
|
|
int maxlen = (positions->maxlen ? positions->maxlen * 2 : 16);
|
|
positions->pos = (GlyphPosition *)SDL_realloc(positions->pos, maxlen * sizeof(*positions->pos));
|
|
if (positions->pos) {
|
|
positions->maxlen = maxlen;
|
|
} else {
|
|
positions->pos = saved;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Compute positions
|
|
GlyphPosition *pos = &positions->pos[positions->len++];
|
|
pos->font = font;
|
|
pos->index = idx;
|
|
pos->glyph = glyph;
|
|
pos->offset = offset;
|
|
pos->x_advance = glyph->advance;
|
|
pos->y_advance = 0;
|
|
pos->x_offset = 0;
|
|
pos->y_offset = 0;
|
|
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);
|
|
pos->x_offset += delta.x;
|
|
}
|
|
prev_index = glyph->index;
|
|
}
|
|
// FT SUBPIXEL : LCD_MODE_LIGHT_SUBPIXEL
|
|
if (font->render_subpixel) {
|
|
// Increment by prev_glyph->lsb_delta - prev_glyph->rsb_delta;
|
|
pos->x_advance += 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 = false;
|
|
} else {
|
|
if (prev_delta - glyph->kerning_smart.lsb_delta > 32 ) {
|
|
pos->x_offset -= 64;
|
|
} else if (prev_delta - glyph->kerning_smart.lsb_delta < -31 ) {
|
|
pos->x_offset += 64;
|
|
}
|
|
}
|
|
prev_delta = glyph->kerning_smart.rsb_delta;
|
|
pos->x_offset = ((pos->x_offset + 32) & -64); // ROUND()
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ReplaceGlyphPositions(GlyphPositions *positions, int start, int length, GlyphPositions *replacement)
|
|
{
|
|
int initial_offset = SDL_min(positions->pos[start].offset, positions->pos[start + length - 1].offset);
|
|
|
|
int length_delta = (replacement->len - length);
|
|
if (length_delta != 0) {
|
|
int newlen = (positions->len + length_delta);
|
|
if (newlen > positions->maxlen) {
|
|
GlyphPosition *pos = SDL_realloc(positions->pos, newlen * sizeof(*pos));
|
|
if (!pos) {
|
|
return false;
|
|
}
|
|
positions->pos = pos;
|
|
positions->maxlen = newlen;
|
|
}
|
|
|
|
int remainder = positions->len - (start + length);
|
|
if (remainder > 0) {
|
|
SDL_memmove(&positions->pos[start + replacement->len], &positions->pos[start + length], (remainder * sizeof(*positions->pos)));
|
|
}
|
|
positions->len = newlen;
|
|
}
|
|
|
|
SDL_memcpy(&positions->pos[start], replacement->pos, replacement->len * sizeof(*positions->pos));
|
|
|
|
for (int i = 0; i < replacement->len; ++i) {
|
|
positions->pos[start + i].offset += initial_offset;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, GlyphPositions *positions, TTF_Font *initial_font);
|
|
|
|
static int FillFallbackSpan(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, GlyphPositions *positions, TTF_Font *initial_font, int start, int stop)
|
|
{
|
|
// Set the starting offset
|
|
int min_offset = positions->pos[start].offset;
|
|
int max_offset = min_offset;
|
|
|
|
for (int i = start; i < stop; ++i) {
|
|
if (positions->pos[i].offset < min_offset) {
|
|
min_offset = positions->pos[i].offset;
|
|
}
|
|
if (positions->pos[i].offset > max_offset) {
|
|
max_offset = positions->pos[i].offset;
|
|
}
|
|
}
|
|
if (stop < positions->len && positions->pos[stop].offset > max_offset) {
|
|
max_offset = positions->pos[stop].offset;
|
|
} else {
|
|
const char *last_text = text + max_offset;
|
|
SDL_StepUTF8(&last_text, NULL);
|
|
max_offset = (int)(uintptr_t)(last_text - text);
|
|
}
|
|
if (max_offset > (int)length) {
|
|
max_offset = (int)length;
|
|
}
|
|
|
|
GlyphPositions span;
|
|
SDL_zero(span);
|
|
int span_length = (max_offset - min_offset);
|
|
CollectGlyphsWithFallbacks(font, text + min_offset, span_length, direction, script, &span, initial_font);
|
|
if (span.len > 0) {
|
|
ReplaceGlyphPositions(positions, start, (stop - start), &span);
|
|
SDL_free(span.pos);
|
|
}
|
|
return span.len;
|
|
}
|
|
|
|
static bool CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, GlyphPositions *positions, TTF_Font *initial_font)
|
|
{
|
|
if (!initial_font) {
|
|
initial_font = font;
|
|
} else if (font == initial_font) {
|
|
// font fallback loop
|
|
return true;
|
|
}
|
|
|
|
if (!CollectGlyphsFromFont(font, text, length, direction, script, positions)) {
|
|
return false;
|
|
}
|
|
|
|
// Create spans of missing characters and fill them in from fallback fonts
|
|
bool complete = false;
|
|
TTF_FontList *fallback = font->fallbacks;
|
|
while (!complete && fallback) {
|
|
complete = true;
|
|
int start = -1;
|
|
for (int i = 0; i < positions->len; ++i) {
|
|
GlyphPosition *pos = &positions->pos[i];
|
|
if (pos->index == 0) {
|
|
complete = false;
|
|
if (start < 0) {
|
|
start = i;
|
|
}
|
|
} else if (start >= 0) {
|
|
// Fill in this span with the fallback font
|
|
int replaced = FillFallbackSpan(fallback->font, text, length, direction, script, positions, initial_font, start, i);
|
|
if (replaced > 0) {
|
|
i = start + replaced;
|
|
}
|
|
start = -1;
|
|
}
|
|
}
|
|
if (start >= 0) {
|
|
// Fill in this span with the fallback font
|
|
FillFallbackSpan(fallback->font, text, length, direction, script, positions, initial_font, start, positions->len);
|
|
}
|
|
fallback = fallback->next;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool CollectGlyphs(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, GlyphPositions *positions)
|
|
{
|
|
if (!CollectGlyphsWithFallbacks(font, text, length, direction, script, positions, NULL)) {
|
|
return false;
|
|
}
|
|
|
|
// Calculate the glyph positions and number of clusters
|
|
int x = 0, y = 0;
|
|
int last_offset = -1;
|
|
positions->num_clusters = 0;
|
|
for (int i = 0; i < positions->len; ++i) {
|
|
GlyphPosition *pos = &positions->pos[i];
|
|
|
|
// Missing characters use the tofu from the initial font
|
|
if (pos->index == 0 && pos->font != font) {
|
|
pos->font = font;
|
|
if (!Find_GlyphByIndex(font, pos->index, 0, 0, 0, 0, 0, 0, &pos->glyph, NULL)) {
|
|
return SDL_SetError("Couldn't find glyph %u in font", pos->index);
|
|
}
|
|
pos->x_advance = pos->glyph->advance;
|
|
pos->y_advance = 0;
|
|
pos->x_offset = 0;
|
|
pos->y_offset = 0;
|
|
}
|
|
|
|
// Compute positions
|
|
pos->x = x + pos->x_offset;
|
|
pos->y = y + F26Dot6(pos->font->ascent) - pos->y_offset;
|
|
x += pos->x_advance;
|
|
y += pos->y_advance;
|
|
#if !TTF_USE_HARFBUZZ
|
|
if (!font->render_subpixel) {
|
|
x = ((x + 32) & -64); // ROUND()
|
|
}
|
|
#endif
|
|
|
|
// Save the number of clusters we've seen
|
|
if (pos->offset != last_offset) {
|
|
++positions->num_clusters;
|
|
last_offset = pos->offset;
|
|
}
|
|
}
|
|
positions->width26dot6 = x;
|
|
positions->height26dot6 = y;
|
|
|
|
return true;
|
|
}
|
|
|
|
static GlyphPositions *GetCachedGlyphPositions(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script)
|
|
{
|
|
CachedGlyphPositions *cached;
|
|
|
|
font->positions = NULL;
|
|
for (unsigned int i = 0; i < SDL_arraysize(font->cached_positions); ++i) {
|
|
cached = &font->cached_positions[i];
|
|
if (direction == cached->direction &&
|
|
script == cached->script &&
|
|
length == cached->length &&
|
|
SDL_memcmp(text, cached->text, length) == 0) {
|
|
#ifdef DEBUG_TTF_CACHE
|
|
SDL_Log("Found cached positions for '%s'\n", cached->text);
|
|
#endif
|
|
font->positions = &cached->positions;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!font->positions) {
|
|
// We could do something fancy like an LRU cache, but it's probably not worth the complexity
|
|
cached = &font->cached_positions[font->next_cached_positions];
|
|
font->next_cached_positions = (font->next_cached_positions + 1) % SDL_arraysize(font->cached_positions);
|
|
font->positions = &cached->positions;
|
|
|
|
SDL_free(cached->text);
|
|
cached->direction = direction;
|
|
cached->script = script;
|
|
cached->text = (char *)SDL_malloc(length + 1);
|
|
if (cached->text) {
|
|
SDL_memcpy(cached->text, text, length);
|
|
cached->text[length] = '\0';
|
|
cached->length = length;
|
|
} else {
|
|
cached->length = 0;
|
|
return NULL;
|
|
}
|
|
|
|
if (!CollectGlyphs(font, text, length, direction, script, font->positions)) {
|
|
cached->length = 0;
|
|
return NULL;
|
|
}
|
|
#ifdef DEBUG_TTF_CACHE
|
|
SDL_Log("Added cached positions for '%s'\n", cached->text);
|
|
#endif
|
|
}
|
|
return font->positions;
|
|
}
|
|
|
|
static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, int *w, int *h, int *xstart, int *ystart, bool measure_width, int max_width, int *measured_width, size_t *measured_length, bool include_spread)
|
|
{
|
|
int x = 0;
|
|
int pos_x, pos_y;
|
|
int minx, maxx;
|
|
int miny, maxy;
|
|
int spread_adjustment;
|
|
|
|
if (w) {
|
|
*w = 0;
|
|
}
|
|
if (h) {
|
|
*h = 0;
|
|
}
|
|
if (measured_width) {
|
|
*measured_width = 0;
|
|
}
|
|
if (measured_length) {
|
|
*measured_length = 0;
|
|
}
|
|
|
|
TTF_CHECK_INITIALIZED(false);
|
|
TTF_CHECK_POINTER("font", font, false);
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (measured_length) {
|
|
*measured_length = length;
|
|
}
|
|
|
|
if (font->render_sdf && !include_spread) {
|
|
spread_adjustment = DEFAULT_SDF_SPREAD;
|
|
} else {
|
|
spread_adjustment = 0;
|
|
}
|
|
|
|
GlyphPositions *positions = GetCachedGlyphPositions(font, text, length, direction, script);
|
|
if (!positions) {
|
|
return false;
|
|
}
|
|
|
|
minx = 0;
|
|
maxx = 0;
|
|
miny = 0;
|
|
maxy = font->height;
|
|
|
|
if (font->render_sdf) {
|
|
// The glyph top and left positions jitter around when doing SDF rendering,
|
|
// but the overall bounding box height is stable.
|
|
//
|
|
// This is almost certainly not the right fix, but it seems to work.
|
|
miny = INT_MAX;
|
|
}
|
|
|
|
if (positions->len > 0) {
|
|
if (positions->pos[0].offset == 0) {
|
|
// Left to right layout
|
|
for (int i = 0; i < positions->len; ++i) {
|
|
GlyphPosition *pos = &positions->pos[i];
|
|
c_glyph *glyph = pos->glyph;
|
|
|
|
// Compute provisional global bounding box
|
|
pos_x = FT_FLOOR(pos->x) + glyph->sz_left + spread_adjustment;
|
|
pos_y = FT_FLOOR(pos->y) - glyph->sz_top + spread_adjustment;
|
|
|
|
minx = SDL_min(minx, pos_x);
|
|
maxx = SDL_max(maxx, pos_x + glyph->sz_width - 2 * spread_adjustment);
|
|
miny = SDL_min(miny, pos_y);
|
|
maxy = SDL_max(maxy, pos_y + glyph->sz_rows - 2 * spread_adjustment);
|
|
|
|
x += pos->x_advance;
|
|
#if !TTF_USE_HARFBUZZ
|
|
if (!font->render_subpixel) {
|
|
x = ((x + 32) & -64); // ROUND()
|
|
}
|
|
#endif
|
|
// Measurement mode
|
|
if (measure_width) {
|
|
int cw = SDL_max(maxx, FT_FLOOR(x)) - minx;
|
|
cw += 2 * font->outline;
|
|
if (!max_width || cw <= max_width) {
|
|
if (measured_width) {
|
|
*measured_width = cw;
|
|
}
|
|
} else {
|
|
if (measured_length) {
|
|
*measured_length = (size_t)pos->offset;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Right to left layout
|
|
x = positions->width26dot6;
|
|
minx = FT_CEIL(positions->width26dot6);
|
|
for (int i = positions->len; i--; ) {
|
|
GlyphPosition *pos = &positions->pos[i];
|
|
c_glyph *glyph = pos->glyph;
|
|
|
|
// Compute provisional global bounding box
|
|
pos_x = FT_FLOOR(pos->x) + glyph->sz_left + spread_adjustment;
|
|
pos_y = FT_FLOOR(pos->y) - glyph->sz_top + spread_adjustment;
|
|
|
|
minx = SDL_min(minx, pos_x);
|
|
maxx = SDL_max(maxx, pos_x + glyph->sz_width - 2 * spread_adjustment);
|
|
miny = SDL_min(miny, pos_y);
|
|
maxy = SDL_max(maxy, pos_y + glyph->sz_rows - 2 * spread_adjustment);
|
|
|
|
// Measurement mode
|
|
if (measure_width) {
|
|
int cw = SDL_max(maxx, FT_FLOOR(x)) - minx;
|
|
cw += 2 * font->outline;
|
|
if (!max_width || cw <= max_width) {
|
|
if (measured_width) {
|
|
*measured_width = cw;
|
|
}
|
|
} else {
|
|
if (measured_length) {
|
|
*measured_length = (size_t)pos->offset;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
x -= pos->x_advance;
|
|
#if !TTF_USE_HARFBUZZ
|
|
if (!font->render_subpixel) {
|
|
x = ((x + 32) & -64); // ROUND()
|
|
}
|
|
#endif
|
|
}
|
|
if (minx > 0) {
|
|
minx = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allows to render a string with only one space (bug 4344).
|
|
maxx = SDL_max(maxx, FT_FLOOR(x));
|
|
|
|
/* 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;
|
|
if (font->render_sdf && include_spread) {
|
|
*xstart += DEFAULT_SDF_SPREAD;
|
|
}
|
|
}
|
|
|
|
// Initial y start: compensation for a negative y offset
|
|
if (ystart) {
|
|
*ystart = (miny < 0) ? -miny : 0;
|
|
*ystart += font->outline;
|
|
}
|
|
|
|
// Fill the bounds rectangle
|
|
if (w) {
|
|
*w = (maxx - minx);
|
|
if (*w != 0) {
|
|
*w += 2 * font->outline;
|
|
}
|
|
}
|
|
if (h) {
|
|
*h = (maxy - miny);
|
|
*h += 2 * font->outline;
|
|
if (font->render_sdf && include_spread) {
|
|
*h += (2 * DEFAULT_SDF_SPREAD);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetStringSize(TTF_Font *font, const char *text, size_t length, int *w, int *h)
|
|
{
|
|
if (!length && text) {
|
|
length = SDL_strlen(text);
|
|
}
|
|
return TTF_Size_Internal(font, text, length, font->direction, font->script, w, h, NULL, NULL, NO_MEASUREMENT, true);
|
|
}
|
|
|
|
bool TTF_MeasureString(TTF_Font *font, const char *text, size_t length, int max_width, int *measured_width, size_t *measured_length)
|
|
{
|
|
if (!length && text) {
|
|
length = SDL_strlen(text);
|
|
}
|
|
return TTF_Size_Internal(font, text, length, font->direction, font->script, NULL, NULL, NULL, NULL, true, max_width, measured_width, measured_length, true);
|
|
}
|
|
|
|
static SDL_Surface* TTF_Render_Internal(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, const render_mode_t render_mode)
|
|
{
|
|
Uint32 color;
|
|
int xstart, ystart, width, height;
|
|
SDL_Surface *textbuf = NULL;
|
|
|
|
TTF_CHECK_INITIALIZED(NULL);
|
|
TTF_CHECK_POINTER("font", font, NULL);
|
|
TTF_CHECK_POINTER("text", text, NULL);
|
|
|
|
if (!length) {
|
|
length = SDL_strlen(text);
|
|
}
|
|
|
|
if (render_mode == RENDER_LCD && !FT_IS_SCALABLE(font->face)) {
|
|
SDL_SetError("LCD rendering is not available for non-scalable font");
|
|
goto failure;
|
|
}
|
|
|
|
#if TTF_USE_SDF
|
|
// Invalid cache if we were using SDF
|
|
if (render_mode != RENDER_BLENDED) {
|
|
if (font->render_sdf) {
|
|
font->render_sdf = false;
|
|
Flush_Cache(font);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Get the dimensions of the text surface
|
|
if (!TTF_Size_Internal(font, text, length, font->direction, font->script, &width, &height, &xstart, &ystart, NO_MEASUREMENT, true) || !width) {
|
|
SDL_SetError("Text has zero width");
|
|
goto failure;
|
|
}
|
|
|
|
// Create surface for rendering
|
|
if (fg.a == SDL_ALPHA_TRANSPARENT) {
|
|
fg.a = SDL_ALPHA_OPAQUE;
|
|
}
|
|
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)) {
|
|
goto failure;
|
|
}
|
|
|
|
// Apply underline or strikethrough style, if needed
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
Draw_Line(font->direction, textbuf, 0, ystart + font->underline_top_row, width, font->line_thickness, color, render_mode);
|
|
}
|
|
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
Draw_Line(font->direction, textbuf, 0, ystart + font->strikethrough_top_row, width, font->line_thickness, color, render_mode);
|
|
}
|
|
|
|
return textbuf;
|
|
failure:
|
|
if (textbuf) {
|
|
SDL_DestroySurface(textbuf);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Solid(TTF_Font *font, const char *text, size_t length, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, text, length, fg, fg /* unused */, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_Solid(TTF_Font *font, Uint32 ch, SDL_Color fg)
|
|
{
|
|
char utf8[4], *end;
|
|
|
|
end = SDL_UCS4ToUTF8(ch, utf8);
|
|
|
|
return TTF_RenderText_Solid(font, utf8, (end - utf8), fg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Shaded(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, text, length, fg, bg, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_Shaded(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
char utf8[4], *end;
|
|
|
|
end = SDL_UCS4ToUTF8(ch, utf8);
|
|
|
|
return TTF_RenderText_Shaded(font, utf8, (end - utf8), fg, bg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Blended(TTF_Font *font, const char *text, size_t length, SDL_Color fg)
|
|
{
|
|
return TTF_Render_Internal(font, text, length, fg, fg /* unused */, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderGlyph_Blended(TTF_Font *font, Uint32 ch, SDL_Color fg)
|
|
{
|
|
char utf8[4], *end;
|
|
|
|
end = SDL_UCS4ToUTF8(ch, utf8);
|
|
|
|
return TTF_RenderText_Blended(font, utf8, (end - utf8), fg);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_LCD(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
return TTF_Render_Internal(font, text, length, fg, bg, RENDER_LCD);
|
|
}
|
|
|
|
|
|
SDL_Surface* TTF_RenderGlyph_LCD(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg)
|
|
{
|
|
char utf8[4], *end;
|
|
|
|
end = SDL_UCS4ToUTF8(ch, utf8);
|
|
|
|
return TTF_RenderText_LCD(font, utf8, (end - utf8), fg, bg);
|
|
}
|
|
|
|
static bool CharacterIsDelimiter(Uint32 c)
|
|
{
|
|
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CharacterIsNewLine(Uint32 c)
|
|
{
|
|
if (c == '\n') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, int xoffset, int wrap_width, bool trim_whitespace, TTF_Line **lines, int *num_lines, int *w, int *h, bool include_spread)
|
|
{
|
|
int width, height;
|
|
int i, numLines = 0, rowHeight;
|
|
TTF_Line *strLines = NULL;
|
|
bool result = false;
|
|
|
|
if (w) {
|
|
*w = 0;
|
|
}
|
|
if (h) {
|
|
*h = 0;
|
|
}
|
|
|
|
TTF_CHECK_INITIALIZED(false);
|
|
TTF_CHECK_POINTER("font", font, false);
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
if (wrap_width < 0) {
|
|
return SDL_InvalidParamError("wrap_width");
|
|
}
|
|
|
|
if (!length) {
|
|
length = SDL_strlen(text);
|
|
}
|
|
|
|
// Get the dimensions of the text surface
|
|
if (!TTF_Size_Internal(font, text, length, direction, script, &width, &height, NULL, NULL, NO_MEASUREMENT, include_spread) || !width) {
|
|
return SDL_SetError("Text has zero width");
|
|
}
|
|
|
|
if (*text) {
|
|
int maxNumLines = 0;
|
|
const char *spot = text;
|
|
size_t left = length;
|
|
|
|
do {
|
|
const char *save_text = NULL;
|
|
size_t save_length = (size_t)(-1);
|
|
|
|
if (numLines >= maxNumLines) {
|
|
TTF_Line *new_lines;
|
|
if (wrap_width == 0) {
|
|
maxNumLines += 32;
|
|
} else {
|
|
maxNumLines += (width / wrap_width) + 1;
|
|
}
|
|
new_lines = (TTF_Line *)SDL_realloc(strLines, maxNumLines * sizeof (*strLines));
|
|
if (new_lines == NULL) {
|
|
goto done;
|
|
}
|
|
strLines = new_lines;
|
|
}
|
|
|
|
if (trim_whitespace && spot > text && spot[-1] != '\n') {
|
|
const char *next_spot = spot;
|
|
size_t next_left = left;
|
|
for (;;) {
|
|
Uint32 c = SDL_StepUTF8(&next_spot, &next_left);
|
|
if (c == 0 || (c != ' ' && c != '\t')) {
|
|
break;
|
|
}
|
|
spot = next_spot;
|
|
left = next_left;
|
|
}
|
|
}
|
|
|
|
if (numLines > 0) {
|
|
strLines[numLines - 1].length = spot - strLines[numLines - 1].text;
|
|
}
|
|
if (*spot == '\0') {
|
|
break;
|
|
}
|
|
strLines[numLines].text = spot;
|
|
strLines[numLines].length = left;
|
|
++numLines;
|
|
|
|
int max_width = wrap_width;
|
|
if (max_width > 0) {
|
|
max_width = SDL_max(max_width - xoffset, 1);
|
|
}
|
|
size_t max_length = 0;
|
|
if (!TTF_Size_Internal(font, spot, left, direction, script, NULL, NULL, NULL, NULL, true, max_width, NULL, &max_length, include_spread)) {
|
|
SDL_SetError("Error measure text");
|
|
goto done;
|
|
}
|
|
|
|
if (wrap_width != 0) {
|
|
// The first line can be empty if we have a text position that's
|
|
// at the edge of the wrap length, but subsequent lines should have
|
|
// at least one character per line.
|
|
if (max_length == 0 && numLines > 1) {
|
|
max_length = 1;
|
|
}
|
|
}
|
|
|
|
const char *end = spot + max_length;
|
|
while (spot < end) {
|
|
int is_delim;
|
|
Uint32 c = SDL_StepUTF8(&spot, &left);
|
|
|
|
if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
|
|
continue;
|
|
}
|
|
|
|
// With wrap_width == 0, normal text rendering but newline aware
|
|
is_delim = (wrap_width > 0) ? CharacterIsDelimiter(c) : CharacterIsNewLine(c);
|
|
|
|
// Record last delimiter position
|
|
if (is_delim) {
|
|
save_text = spot;
|
|
save_length = left;
|
|
// Break, if new line
|
|
if (c == '\n' || (c == '\r' && *spot != '\n')) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cut at last delimiter/new lines, otherwise in the middle of the word
|
|
if (save_text && left > 0) {
|
|
spot = save_text;
|
|
left = save_length;
|
|
}
|
|
|
|
// First line is complete, start the next at offset 0
|
|
xoffset = 0;
|
|
|
|
} while (left > 0);
|
|
|
|
for (i = 0; i < numLines; ++i) {
|
|
TTF_Line *line = &strLines[i];
|
|
if (line->length == 0) {
|
|
continue;
|
|
}
|
|
|
|
// The line doesn't include any delimiter that caused it to be wrapped.
|
|
if (CharacterIsNewLine(line->text[line->length - 1])) {
|
|
--line->length;
|
|
if (line->length > 0 && line->text[line->length - 1] == '\r') {
|
|
--line->length;
|
|
}
|
|
} else if (i < (numLines - 1) &&
|
|
CharacterIsDelimiter(line->text[line->length - 1])) {
|
|
--line->length;
|
|
}
|
|
|
|
if (trim_whitespace) {
|
|
while (line->length > 0 &&
|
|
CharacterIsDelimiter(line->text[line->length - 1])) {
|
|
--line->length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rowHeight = SDL_max(height, font->lineskip);
|
|
|
|
if (wrap_width == 0) {
|
|
// Find the max of all line lengths
|
|
if (numLines > 1) {
|
|
width = 0;
|
|
for (i = 0; i < numLines; i++) {
|
|
int w_tmp, h_tmp;
|
|
|
|
if (TTF_Size_Internal(font, strLines[i].text, strLines[i].length, font->direction, font->script, &w_tmp, &h_tmp, NULL, NULL, NO_MEASUREMENT, include_spread)) {
|
|
width = SDL_max(w_tmp, width);
|
|
}
|
|
}
|
|
// In case there are all newlines
|
|
width = SDL_max(width, 1);
|
|
}
|
|
} else {
|
|
if (numLines <= 1 && font->horizontal_align == TTF_HORIZONTAL_ALIGN_LEFT) {
|
|
// Don't go above wrap_width if you have only 1 line which hasn't been cut
|
|
width = SDL_min((int)wrap_width, width);
|
|
} else {
|
|
width = wrap_width;
|
|
}
|
|
}
|
|
height = rowHeight + font->lineskip * (numLines - 1);
|
|
|
|
result = true;
|
|
|
|
done:
|
|
if (result) {
|
|
if (lines) {
|
|
*lines = strLines;
|
|
} else {
|
|
SDL_free(strLines);
|
|
}
|
|
if (num_lines) {
|
|
*num_lines = numLines;
|
|
}
|
|
if (w) {
|
|
*w = width;
|
|
}
|
|
if (h) {
|
|
*h = height;
|
|
}
|
|
} else {
|
|
SDL_free(strLines);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool TTF_GetStringSizeWrapped(TTF_Font *font, const char *text, size_t length, int wrap_width, int *w, int *h)
|
|
{
|
|
return GetWrappedLines(font, text, length, font->direction, font->script, 0, wrap_width, true, NULL, NULL, w, h, true);
|
|
}
|
|
|
|
static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrap_width, const render_mode_t render_mode)
|
|
{
|
|
Uint32 color;
|
|
int width, height;
|
|
SDL_Surface *textbuf = NULL;
|
|
int i, numLines = 0;
|
|
TTF_Line *strLines = NULL;
|
|
|
|
if (!GetWrappedLines(font, text, length, font->direction, font->script, 0, wrap_width, true, &strLines, &numLines, &width, &height, true)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (render_mode == RENDER_LCD && !FT_IS_SCALABLE(font->face)) {
|
|
SDL_SetError("LCD rendering is not available for non-scalable font");
|
|
goto failure;
|
|
}
|
|
|
|
// Create surface for rendering
|
|
if (fg.a == SDL_ALPHA_TRANSPARENT) {
|
|
fg.a = SDL_ALPHA_OPAQUE;
|
|
}
|
|
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;
|
|
|
|
// Initialize xstart, ystart and compute positions
|
|
if (!TTF_Size_Internal(font, strLines[i].text, strLines[i].length, font->direction, font->script, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT, true)) {
|
|
goto failure;
|
|
}
|
|
|
|
// Move to i-th line
|
|
ystart += i * font->lineskip;
|
|
|
|
// Control left/right/center align of each bit of text
|
|
if (font->horizontal_align == TTF_HORIZONTAL_ALIGN_RIGHT) {
|
|
xoffset = (width - line_width);
|
|
} else if (font->horizontal_align == TTF_HORIZONTAL_ALIGN_CENTER) {
|
|
xoffset = (width - line_width) / 2;
|
|
} else {
|
|
xoffset = 0;
|
|
}
|
|
xoffset = SDL_max(0, xoffset);
|
|
|
|
// Render one text line to textbuf at (xstart, ystart)
|
|
if (!Render_Line(render_mode, font->render_subpixel, font, textbuf, xstart + xoffset, ystart, fg)) {
|
|
goto failure;
|
|
}
|
|
|
|
// Apply underline or strikethrough style, if needed
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
Draw_Line(font->direction, textbuf, xoffset, ystart + font->underline_top_row, line_width, font->line_thickness, color, render_mode);
|
|
}
|
|
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
Draw_Line(font->direction, textbuf, xoffset, ystart + font->strikethrough_top_row, line_width, font->line_thickness, color, render_mode);
|
|
}
|
|
}
|
|
|
|
if (strLines) {
|
|
SDL_free(strLines);
|
|
}
|
|
return textbuf;
|
|
|
|
failure:
|
|
if (textbuf) {
|
|
SDL_DestroySurface(textbuf);
|
|
}
|
|
if (strLines) {
|
|
SDL_free(strLines);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Solid_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, int wrap_width)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, length, fg, fg /* unused */, wrap_width, RENDER_SOLID);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Shaded_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrap_width)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, length, fg, bg, wrap_width, RENDER_SHADED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_Blended_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, int wrap_width)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, length, fg, fg /* unused */, wrap_width, RENDER_BLENDED);
|
|
}
|
|
|
|
SDL_Surface* TTF_RenderText_LCD_Wrapped(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrap_width)
|
|
{
|
|
return TTF_Render_Wrapped_Internal(font, text, length, fg, bg, wrap_width, RENDER_LCD);
|
|
}
|
|
|
|
struct TTF_TextLayout
|
|
{
|
|
TTF_Direction direction;
|
|
Uint32 script; // ISO 15924 script tag
|
|
int font_height;
|
|
int wrap_length;
|
|
bool wrap_whitespace_visible;
|
|
int *lines;
|
|
};
|
|
|
|
typedef struct TTF_InternalText
|
|
{
|
|
TTF_Text text;
|
|
TTF_TextData internal;
|
|
TTF_TextLayout layout;
|
|
} TTF_InternalText;
|
|
|
|
TTF_Text *TTF_CreateText(TTF_TextEngine *engine, TTF_Font *font, const char *text, size_t length)
|
|
{
|
|
if (engine && engine->version < sizeof(*engine)) {
|
|
// Update this to handle older versions of this interface
|
|
SDL_SetError("Invalid engine, should be initialized with SDL_INIT_INTERFACE()");
|
|
return NULL;
|
|
}
|
|
|
|
TTF_InternalText *mem = (TTF_InternalText *)SDL_calloc(1, sizeof(*mem));
|
|
if (!mem) {
|
|
return NULL;
|
|
}
|
|
|
|
TTF_Text *result = &mem->text;
|
|
result->internal = &mem->internal;
|
|
result->internal->layout = &mem->layout;
|
|
result->internal->font = font;
|
|
result->internal->color.r = 1.0f;
|
|
result->internal->color.g = 1.0f;
|
|
result->internal->color.b = 1.0f;
|
|
result->internal->color.a = 1.0f;
|
|
result->internal->needs_layout_update = true;
|
|
result->internal->engine = engine;
|
|
if (text && *text) {
|
|
if (length == 0) {
|
|
length = SDL_strlen(text);
|
|
}
|
|
|
|
result->text = (char *)SDL_malloc(length + 1);
|
|
if (!result->text) {
|
|
SDL_free(mem);
|
|
return NULL;
|
|
}
|
|
SDL_memcpy(result->text, text, length);
|
|
result->text[length] = '\0';
|
|
}
|
|
|
|
if (font) {
|
|
AddFontTextReference(font, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int SDLCALL SortClusters(const void *a, const void *b)
|
|
{
|
|
TTF_SubString *A = (TTF_SubString *)a;
|
|
TTF_SubString *B = (TTF_SubString *)b;
|
|
|
|
if (A->offset == B->offset) {
|
|
if (A->flags & TTF_SUBSTRING_LINE_END) {
|
|
return -1;
|
|
}
|
|
if (B->flags & TTF_SUBSTRING_LINE_END) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return (A->offset - B->offset);
|
|
}
|
|
|
|
static int CalculateClusterLengths(TTF_Text *text, TTF_SubString *clusters, int num_clusters, size_t length, int *lines)
|
|
{
|
|
SDL_qsort(clusters, num_clusters, sizeof(*clusters), SortClusters);
|
|
|
|
TTF_Font *font = text->internal->font;
|
|
int i;
|
|
const TTF_SubString *src = clusters;
|
|
TTF_SubString *last = NULL;
|
|
int cluster_index = 0;
|
|
for (i = 0; i < num_clusters; ++i, ++src) {
|
|
// Merge zero width clusters
|
|
if (last && src->offset == last->offset) {
|
|
if (!(last->flags & TTF_SUBSTRING_LINE_END)) {
|
|
last->flags |= src->flags;
|
|
SDL_GetRectUnion(&last->rect, &src->rect, &last->rect);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
TTF_SubString *cluster = &clusters[cluster_index];
|
|
if (src != cluster) {
|
|
SDL_copyp(cluster, src);
|
|
}
|
|
|
|
if (!last || cluster->line_index != last->line_index) {
|
|
if (!last) {
|
|
cluster->flags |= TTF_SUBSTRING_TEXT_START;
|
|
}
|
|
cluster->flags |= TTF_SUBSTRING_LINE_START;
|
|
|
|
if (lines && cluster->line_index > 0) {
|
|
lines[cluster->line_index - 1] = cluster_index;
|
|
}
|
|
}
|
|
|
|
cluster->cluster_index = cluster_index++;
|
|
|
|
if (cluster->flags & TTF_SUBSTRING_LINE_END) {
|
|
if (cluster->flags & TTF_SUBSTRING_LINE_START) {
|
|
cluster->rect.y = cluster->line_index * font->lineskip;
|
|
cluster->rect.h = font->height;
|
|
} else {
|
|
SDL_copyp(&cluster->rect, &clusters[cluster->cluster_index - 1].rect);
|
|
if ((cluster->flags & TTF_SUBSTRING_DIRECTION_MASK) != TTF_DIRECTION_RTL) {
|
|
cluster->rect.x += cluster->rect.w;
|
|
}
|
|
cluster->rect.w = 0;
|
|
}
|
|
} else if (cluster->flags & TTF_SUBSTRING_TEXT_END) {
|
|
if (last) {
|
|
if (last->length > 0 && text->text[last->offset + last->length - 1] == '\n') {
|
|
cluster->line_index = last->line_index + 1;
|
|
cluster->rect.y = (cluster->line_index * font->lineskip);
|
|
cluster->rect.h = font->height;
|
|
} else {
|
|
cluster->line_index = last->line_index;
|
|
SDL_copyp(&cluster->rect, &last->rect);
|
|
if ((cluster->flags & TTF_SUBSTRING_DIRECTION_MASK) != TTF_DIRECTION_RTL) {
|
|
cluster->rect.x += cluster->rect.w;
|
|
}
|
|
cluster->rect.w = 0;
|
|
}
|
|
} else {
|
|
cluster->rect.h = font->height;
|
|
}
|
|
}
|
|
|
|
if (i < (num_clusters - 1)) {
|
|
cluster->length = clusters[i + 1].offset - cluster->offset;
|
|
} else {
|
|
SDL_assert(cluster->flags & TTF_SUBSTRING_TEXT_END);
|
|
SDL_assert(cluster->offset == (int)length);
|
|
}
|
|
last = cluster;
|
|
}
|
|
return cluster_index;
|
|
}
|
|
|
|
static TTF_SubStringFlags GetPreviousClusterDirection(TTF_SubString *clusters, int num_clusters, TTF_Direction direction)
|
|
{
|
|
if (num_clusters > 1) {
|
|
return (clusters[num_clusters - 1].flags & TTF_SUBSTRING_DIRECTION_MASK);
|
|
} else {
|
|
return (TTF_SubStringFlags)direction;
|
|
}
|
|
}
|
|
|
|
static bool LayoutText(TTF_Text *text)
|
|
{
|
|
TTF_Font *font = text->internal->font;
|
|
int wrap_width = text->internal->layout->wrap_length;
|
|
bool trim_whitespace = !text->internal->layout->wrap_whitespace_visible;
|
|
size_t length = SDL_strlen(text->text);
|
|
int i, width = 0, height = 0, numLines = 0;
|
|
TTF_Line *strLines = NULL;
|
|
TTF_DrawOperation *ops = NULL, *new_ops;
|
|
int num_ops = 0, max_ops = 0, extra_ops = 0, additional_ops;
|
|
TTF_SubString *clusters = NULL, *new_clusters, *cluster;
|
|
int num_clusters = 0, max_clusters = 0, cluster_offset;
|
|
int *lines = NULL;
|
|
bool result = false;
|
|
TTF_Direction direction = TTF_GetTextDirection(text);
|
|
Uint32 script = TTF_GetTextScript(text);
|
|
|
|
if (!GetWrappedLines(font, text->text, length, direction, script, text->internal->x, wrap_width, trim_whitespace, &strLines, &numLines, &width, &height, false)) {
|
|
return true;
|
|
}
|
|
height += text->internal->y;
|
|
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
++extra_ops;
|
|
}
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
++extra_ops;
|
|
}
|
|
|
|
if (numLines > 1) {
|
|
lines = (int *)SDL_malloc((numLines - 1) * sizeof(*lines));
|
|
if (!lines) {
|
|
goto done;
|
|
}
|
|
for (i = 0; i < (numLines - 1); ++i) {
|
|
lines[i] = -1;
|
|
}
|
|
}
|
|
|
|
max_clusters = numLines + 1;
|
|
clusters = (TTF_SubString *)SDL_calloc(max_clusters, sizeof(*clusters));
|
|
if (!clusters) {
|
|
goto done;
|
|
}
|
|
|
|
// Render each line
|
|
for (i = 0; i < numLines; i++) {
|
|
int xstart, ystart, line_width, xoffset;
|
|
|
|
if (strLines[i].length == 0) {
|
|
cluster = &clusters[num_clusters++];
|
|
cluster->flags = GetPreviousClusterDirection(clusters, num_clusters - 1, direction) | TTF_SUBSTRING_LINE_END;
|
|
cluster->offset = (int)(uintptr_t)(strLines[i].text - text->text);
|
|
cluster->line_index = i;
|
|
continue;
|
|
}
|
|
|
|
// Initialize xstart, ystart and compute positions
|
|
if (!TTF_Size_Internal(font, strLines[i].text, strLines[i].length, direction, script, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT, false)) {
|
|
goto done;
|
|
}
|
|
|
|
// Move to i-th line
|
|
ystart += i * font->lineskip;
|
|
|
|
ystart += text->internal->y;
|
|
|
|
// Control left/right/center align of each bit of text
|
|
if (font->horizontal_align == TTF_HORIZONTAL_ALIGN_RIGHT) {
|
|
xoffset = (width - line_width);
|
|
} else if (font->horizontal_align == TTF_HORIZONTAL_ALIGN_CENTER) {
|
|
xoffset = (width - line_width) / 2;
|
|
} else {
|
|
xoffset = 0;
|
|
}
|
|
xoffset = SDL_max(0, xoffset);
|
|
|
|
if (i == 0) {
|
|
xoffset += text->internal->x;
|
|
}
|
|
|
|
// Allocate space for the operations on this line
|
|
additional_ops = (font->positions->len + extra_ops);
|
|
new_ops = (TTF_DrawOperation *)SDL_realloc(ops, (max_ops + additional_ops) * sizeof(*new_ops));
|
|
if (!new_ops) {
|
|
goto done;
|
|
}
|
|
SDL_memset(new_ops + max_ops, 0, additional_ops * sizeof(*new_ops));
|
|
ops = new_ops;
|
|
max_ops += additional_ops;
|
|
|
|
// Allocate space for the clusters on this line
|
|
new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + font->positions->num_clusters) * sizeof(*new_clusters));
|
|
if (!new_clusters) {
|
|
goto done;
|
|
}
|
|
SDL_memset(new_clusters + max_clusters, 0, font->positions->num_clusters * sizeof(*new_clusters));
|
|
clusters = new_clusters;
|
|
max_clusters += font->positions->num_clusters;
|
|
cluster_offset = (int)(uintptr_t)(strLines[i].text - text->text);
|
|
|
|
// Create the text drawing operations
|
|
if (!Render_Line_TextEngine(font, direction, xstart + xoffset, ystart, width, height, ops, &num_ops, clusters, &num_clusters, cluster_offset, i)) {
|
|
goto done;
|
|
}
|
|
cluster = &clusters[num_clusters++];
|
|
cluster->flags = GetPreviousClusterDirection(clusters, num_clusters - 1, direction) | TTF_SUBSTRING_LINE_END;
|
|
cluster->offset = (int)(uintptr_t)(strLines[i].text - text->text + strLines[i].length);
|
|
cluster->line_index = i;
|
|
|
|
// Apply underline or strikethrough style, if needed
|
|
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
|
|
Draw_Line_TextEngine(direction, width, height, xoffset, ystart + font->underline_top_row, line_width, font->line_thickness, ops, &num_ops);
|
|
}
|
|
|
|
if (TTF_HANDLE_STYLE_STRIKETHROUGH(font)) {
|
|
Draw_Line_TextEngine(direction, width, height, xoffset, ystart + font->strikethrough_top_row, line_width, font->line_thickness, ops, &num_ops);
|
|
}
|
|
}
|
|
cluster = &clusters[num_clusters++];
|
|
cluster->flags = GetPreviousClusterDirection(clusters, num_clusters - 1, direction) | TTF_SUBSTRING_TEXT_END;
|
|
cluster->offset = (int)length;
|
|
|
|
num_clusters = CalculateClusterLengths(text, clusters, num_clusters, length, lines);
|
|
|
|
result = true;
|
|
|
|
done:
|
|
if (result) {
|
|
text->num_lines = numLines;
|
|
text->internal->w = width;
|
|
text->internal->h = height;
|
|
text->internal->num_ops = num_ops;
|
|
text->internal->ops = ops;
|
|
text->internal->num_clusters = num_clusters;
|
|
text->internal->clusters = clusters;
|
|
text->internal->layout->lines = lines;
|
|
} else {
|
|
SDL_free(ops);
|
|
SDL_free(clusters);
|
|
SDL_free(lines);
|
|
}
|
|
SDL_free(strLines);
|
|
return result;
|
|
}
|
|
|
|
static void DestroyEngineText(TTF_Text *text)
|
|
{
|
|
TTF_TextEngine *engine = text->internal->engine;
|
|
if (engine && engine->DestroyText &&
|
|
text->internal->engine_text) {
|
|
engine->DestroyText(engine->userdata, text);
|
|
text->internal->engine_text = NULL;
|
|
}
|
|
}
|
|
|
|
static bool CreateEngineText(TTF_Text *text)
|
|
{
|
|
TTF_TextEngine *engine = text->internal->engine;
|
|
if (engine && engine->CreateText && text->internal->num_ops > 0) {
|
|
if (!engine->CreateText(engine->userdata, text)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SDL_PropertiesID TTF_GetTextProperties(TTF_Text *text)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, 0);
|
|
|
|
if (!text->internal->props) {
|
|
text->internal->props = SDL_CreateProperties();
|
|
}
|
|
return text->internal->props;
|
|
}
|
|
|
|
bool TTF_SetTextEngine(TTF_Text *text, TTF_TextEngine *engine)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (engine == text->internal->engine) {
|
|
return true;
|
|
}
|
|
|
|
if (engine && engine->version < sizeof(*engine)) {
|
|
// Update this to handle older versions of this interface
|
|
return SDL_SetError("Invalid engine, should be initialized with SDL_INIT_INTERFACE()");
|
|
}
|
|
|
|
DestroyEngineText(text);
|
|
|
|
text->internal->engine = engine;
|
|
text->internal->needs_engine_update = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
TTF_TextEngine *TTF_GetTextEngine(TTF_Text *text)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, NULL);
|
|
|
|
return text->internal->engine;
|
|
}
|
|
|
|
bool TTF_SetTextFont(TTF_Text *text, TTF_Font *font)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (font == text->internal->font) {
|
|
return true;
|
|
}
|
|
|
|
if (text->internal->font) {
|
|
RemoveFontTextReference(text->internal->font, text);
|
|
}
|
|
|
|
text->internal->font = font;
|
|
|
|
if (text->internal->font) {
|
|
AddFontTextReference(text->internal->font, text);
|
|
|
|
text->internal->layout->font_height = font->height;
|
|
} else {
|
|
text->internal->layout->font_height = 0;
|
|
}
|
|
text->internal->needs_layout_update = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
TTF_Font *TTF_GetTextFont(TTF_Text *text)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, NULL);
|
|
|
|
return text->internal->font;
|
|
}
|
|
|
|
bool TTF_SetTextDirection(TTF_Text *text, TTF_Direction direction)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (direction == text->internal->layout->direction) {
|
|
return true;
|
|
}
|
|
|
|
#if !TTF_USE_HARFBUZZ
|
|
if (direction != TTF_DIRECTION_INVALID && direction != TTF_DIRECTION_LTR) {
|
|
return SDL_Unsupported();
|
|
}
|
|
#endif
|
|
|
|
text->internal->layout->direction = direction;
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
TTF_Direction TTF_GetTextDirection(TTF_Text *text)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, TTF_DIRECTION_INVALID);
|
|
|
|
if (text->internal->layout->direction != TTF_DIRECTION_INVALID) {
|
|
return text->internal->layout->direction;
|
|
}
|
|
return TTF_GetFontDirection(text->internal->font);
|
|
}
|
|
|
|
bool TTF_SetTextScript(TTF_Text *text, Uint32 script)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
text->internal->layout->script = script;
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
#else
|
|
(void) script;
|
|
return SDL_Unsupported();
|
|
#endif
|
|
}
|
|
|
|
Uint32 TTF_GetTextScript(TTF_Text *text)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, 0);
|
|
|
|
if (text->internal->layout->script) {
|
|
return text->internal->layout->script;
|
|
}
|
|
return TTF_GetFontScript(text->internal->font);
|
|
}
|
|
|
|
bool TTF_SetTextColor(TTF_Text *text, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
|
|
{
|
|
const float fR = (float)r / 255.0f;
|
|
const float fG = (float)g / 255.0f;
|
|
const float fB = (float)b / 255.0f;
|
|
const float fA = (float)a / 255.0f;
|
|
|
|
return TTF_SetTextColorFloat(text, fR, fG, fB, fA);
|
|
}
|
|
|
|
bool TTF_SetTextColorFloat(TTF_Text *text, float r, float g, float b, float a)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
text->internal->color.r = r;
|
|
text->internal->color.g = g;
|
|
text->internal->color.b = b;
|
|
text->internal->color.a = a;
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextColor(TTF_Text *text, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)
|
|
{
|
|
float fR = 1.0f, fG = 1.0f, fB = 1.0f, fA = 1.0f;
|
|
|
|
if (!TTF_GetTextColorFloat(text, &fR, &fG, &fB, &fA)) {
|
|
if (r) {
|
|
*r = 255;
|
|
}
|
|
if (g) {
|
|
*g = 255;
|
|
}
|
|
if (b) {
|
|
*b = 255;
|
|
}
|
|
if (b) {
|
|
*b = 255;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (r) {
|
|
*r = (Uint8)SDL_roundf(SDL_clamp(fR, 0.0f, 1.0f) * 255.0f);
|
|
}
|
|
if (g) {
|
|
*g = (Uint8)SDL_roundf(SDL_clamp(fG, 0.0f, 1.0f) * 255.0f);
|
|
}
|
|
if (b) {
|
|
*b = (Uint8)SDL_roundf(SDL_clamp(fB, 0.0f, 1.0f) * 255.0f);
|
|
}
|
|
if (a) {
|
|
*a = (Uint8)SDL_roundf(SDL_clamp(fA, 0.0f, 1.0f) * 255.0f);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextColorFloat(TTF_Text *text, float *r, float *g, float *b, float *a)
|
|
{
|
|
SDL_FColor color;
|
|
|
|
if (r) {
|
|
*r = 1.0f;
|
|
}
|
|
if (g) {
|
|
*g = 1.0f;
|
|
}
|
|
if (b) {
|
|
*b = 1.0f;
|
|
}
|
|
if (a) {
|
|
*a = 1.0f;
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
color = text->internal->color;
|
|
|
|
if (r) {
|
|
*r = color.r;
|
|
}
|
|
if (g) {
|
|
*g = color.g;
|
|
}
|
|
if (b) {
|
|
*b = color.b;
|
|
}
|
|
if (a) {
|
|
*a = color.a;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_SetTextPosition(TTF_Text *text, int x, int y)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (x != text->internal->x || y != text->internal->y) {
|
|
text->internal->x = x;
|
|
text->internal->y = y;
|
|
text->internal->needs_layout_update = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextPosition(TTF_Text *text, int *x, int *y)
|
|
{
|
|
if (x) {
|
|
*x = 0;
|
|
}
|
|
if (y) {
|
|
*y = 0;
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (x) {
|
|
*x = text->internal->x;
|
|
}
|
|
if (y) {
|
|
*y = text->internal->y;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_SetTextWrapWidth(TTF_Text *text, int wrap_width)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (wrap_width == text->internal->layout->wrap_length) {
|
|
return true;
|
|
}
|
|
|
|
text->internal->layout->wrap_length = SDL_max(wrap_width, 0);
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextWrapWidth(TTF_Text *text, int *wrap_width)
|
|
{
|
|
if (wrap_width) {
|
|
*wrap_width = 0;
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (wrap_width) {
|
|
*wrap_width = text->internal->layout->wrap_length;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_SetTextWrapWhitespaceVisible(TTF_Text *text, bool visible)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (visible == text->internal->layout->wrap_whitespace_visible) {
|
|
return true;
|
|
}
|
|
|
|
text->internal->layout->wrap_whitespace_visible = visible;
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
bool TTF_TextWrapWhitespaceVisible(TTF_Text *text)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
return text->internal->layout->wrap_whitespace_visible;
|
|
}
|
|
|
|
bool TTF_SetTextString(TTF_Text *text, const char *string, size_t length)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (!string || !*string) {
|
|
if (!text->text) {
|
|
return true;
|
|
}
|
|
|
|
SDL_free(text->text);
|
|
text->text = NULL;
|
|
} else {
|
|
if (!length) {
|
|
length = SDL_strlen(string);
|
|
}
|
|
|
|
if (text->text && length == SDL_strlen(text->text) && SDL_memcmp(string, text->text, length) == 0) {
|
|
return true;
|
|
}
|
|
|
|
char *new_string = SDL_malloc(length + 1);
|
|
if (!new_string) {
|
|
return false;
|
|
}
|
|
SDL_memcpy(new_string, string, length);
|
|
new_string[length] = '\0';
|
|
|
|
SDL_free(text->text);
|
|
text->text = new_string;
|
|
}
|
|
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
bool TTF_InsertTextString(TTF_Text *text, int offset, const char *string, size_t length)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (!string || !*string) {
|
|
return true;
|
|
}
|
|
|
|
if (!length) {
|
|
length = SDL_strlen(string);
|
|
}
|
|
|
|
if (!text->text) {
|
|
return TTF_SetTextString(text, string, length);
|
|
}
|
|
|
|
int old_length = (int)SDL_strlen(text->text);
|
|
size_t new_length = old_length + length;
|
|
char *new_string = (char *)SDL_realloc(text->text, new_length + 1);
|
|
if (!new_string) {
|
|
return false;
|
|
}
|
|
|
|
if (offset < 0) {
|
|
offset = old_length + 1 + offset;
|
|
if (offset < 0) {
|
|
offset = 0;
|
|
}
|
|
} else {
|
|
if (offset > old_length) {
|
|
offset = old_length;
|
|
}
|
|
}
|
|
|
|
int shift = (old_length - offset);
|
|
if (shift > 0) {
|
|
SDL_memmove(new_string + offset + length, new_string + offset, shift);
|
|
}
|
|
SDL_memcpy(new_string + offset, string, length);
|
|
new_string[new_length] = '\0';
|
|
|
|
text->text = new_string;
|
|
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
bool TTF_AppendTextString(TTF_Text *text, const char *string, size_t length)
|
|
{
|
|
return TTF_InsertTextString(text, -1, string, length);
|
|
}
|
|
|
|
bool TTF_DeleteTextString(TTF_Text *text, int offset, int length)
|
|
{
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (!length || !text->text) {
|
|
return true;
|
|
}
|
|
|
|
int old_length = (int)SDL_strlen(text->text);
|
|
if (offset < 0) {
|
|
offset = old_length + 1 + offset;
|
|
if (offset < 0) {
|
|
offset = 0;
|
|
}
|
|
} else {
|
|
if (offset >= old_length) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (length < 0 || length >= (old_length - offset)) {
|
|
if (offset == 0) {
|
|
return TTF_SetTextString(text, NULL, 0);
|
|
}
|
|
text->text[offset] = '\0';
|
|
} else {
|
|
int shift = (old_length - length - offset);
|
|
SDL_memcpy(&text->text[offset], &text->text[offset + length], shift);
|
|
text->text[offset + shift] = '\0';
|
|
}
|
|
|
|
text->internal->needs_layout_update = true;
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextSize(TTF_Text *text, int *w, int *h)
|
|
{
|
|
if (w) {
|
|
*w = 0;
|
|
}
|
|
if (h) {
|
|
*h = 0;
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
|
|
if (!TTF_UpdateText(text)) {
|
|
return false;
|
|
}
|
|
|
|
if (w) {
|
|
*w = text->internal->w;
|
|
}
|
|
if (h) {
|
|
*h = text->internal->h;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring)
|
|
{
|
|
if (substring) {
|
|
SDL_zerop(substring);
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
TTF_CHECK_POINTER("substring", substring, false);
|
|
|
|
if (!TTF_UpdateText(text)) {
|
|
return false;
|
|
}
|
|
|
|
if (text->internal->num_clusters == 0) {
|
|
substring->rect.h = text->internal->layout->font_height;
|
|
return true;
|
|
}
|
|
|
|
int num_clusters = text->internal->num_clusters;
|
|
TTF_SubString *clusters = text->internal->clusters;
|
|
|
|
if (offset < 0) {
|
|
SDL_copyp(substring, &clusters[0]);
|
|
substring->length = 0;
|
|
substring->rect.w = 0;
|
|
return true;
|
|
}
|
|
|
|
int length = (int)SDL_strlen(text->text);
|
|
if (offset >= length) {
|
|
SDL_copyp(substring, &clusters[num_clusters - 1]);
|
|
return true;
|
|
}
|
|
|
|
// Make a quick guess that works for ASCII text
|
|
const TTF_SubString *cluster = &clusters[offset];
|
|
if (offset < num_clusters && cluster->offset == offset) {
|
|
if ((cluster->flags & TTF_SUBSTRING_LINE_END) && cluster->length == 0 &&
|
|
offset < (num_clusters - 1)) {
|
|
++cluster;
|
|
}
|
|
SDL_copyp(substring, cluster);
|
|
return true;
|
|
}
|
|
|
|
// Do a binary search to find the cluster
|
|
int low = 0;
|
|
int high = num_clusters - 1;
|
|
while (low <= high) {
|
|
int mid = low + (high - low) / 2;
|
|
cluster = &clusters[mid];
|
|
|
|
// If we're a zero length line ending, check the next cluster
|
|
if ((cluster->flags & TTF_SUBSTRING_LINE_END) && cluster->length == 0 &&
|
|
mid < (num_clusters - 1)) {
|
|
++cluster;
|
|
}
|
|
if (offset >= cluster->offset && offset < (cluster->offset + cluster->length)) {
|
|
SDL_copyp(substring, &clusters[mid]);
|
|
break;
|
|
}
|
|
|
|
if (cluster->offset < offset) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid - 1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substring)
|
|
{
|
|
if (substring) {
|
|
SDL_zerop(substring);
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
TTF_CHECK_POINTER("substring", substring, false);
|
|
|
|
if (!TTF_UpdateText(text)) {
|
|
return false;
|
|
}
|
|
|
|
if (text->internal->num_clusters == 0) {
|
|
substring->rect.h = text->internal->layout->font_height;
|
|
return true;
|
|
}
|
|
|
|
int num_clusters = text->internal->num_clusters;
|
|
TTF_SubString *clusters = text->internal->clusters;
|
|
|
|
if (line < 0) {
|
|
SDL_copyp(substring, &clusters[0]);
|
|
substring->length = 0;
|
|
substring->rect.w = 0;
|
|
return true;
|
|
}
|
|
|
|
if (line >= text->num_lines) {
|
|
SDL_copyp(substring, &clusters[num_clusters - 1]);
|
|
return true;
|
|
}
|
|
|
|
int *lines = text->internal->layout->lines;
|
|
if (line == 0) {
|
|
SDL_copyp(substring, &clusters[0]);
|
|
} else {
|
|
SDL_copyp(substring, &clusters[lines[line - 1]]);
|
|
}
|
|
if (line == text->num_lines - 1) {
|
|
substring->length = (int)SDL_strlen(text->text) - substring->offset;
|
|
} else {
|
|
substring->length = clusters[lines[line]].offset - substring->offset;
|
|
}
|
|
for (int i = substring->cluster_index + 1; i < num_clusters; ++i) {
|
|
TTF_SubString *cluster = &clusters[i];
|
|
if (cluster->line_index != line) {
|
|
break;
|
|
}
|
|
substring->flags |= cluster->flags;
|
|
SDL_GetRectUnion(&substring->rect, &cluster->rect, &substring->rect);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TTF_SubString **TTF_GetTextSubStringsForRange(TTF_Text *text, int offset, int length, int *count)
|
|
{
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, NULL);
|
|
|
|
if (!TTF_UpdateText(text)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (text->internal->num_clusters == 0) {
|
|
TTF_SubString **result = (TTF_SubString **)SDL_malloc(2 * sizeof(*result) + 1 * sizeof(**result));
|
|
if (!result) {
|
|
return NULL;
|
|
}
|
|
|
|
TTF_SubString *substring = (TTF_SubString *)(result + 2);
|
|
result[0] = substring;
|
|
result[1] = NULL;
|
|
SDL_zerop(substring);
|
|
substring->rect.h = text->internal->layout->font_height;
|
|
|
|
if (count) {
|
|
*count = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (length < 0) {
|
|
length = (int)SDL_strlen(text->text);
|
|
}
|
|
|
|
TTF_SubString substring1, substring2;
|
|
int offset1 = offset;
|
|
int offset2 = offset + length;
|
|
if (!TTF_GetTextSubString(text, offset1, &substring1) ||
|
|
!TTF_GetTextSubString(text, offset2, &substring2) ||
|
|
!TTF_GetPreviousTextSubString(text, &substring2, &substring2)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (substring1.cluster_index == substring2.cluster_index) {
|
|
TTF_SubString **result = (TTF_SubString **)SDL_malloc(2 * sizeof(*result) + 1 * sizeof(**result));
|
|
if (!result) {
|
|
return NULL;
|
|
}
|
|
|
|
TTF_SubString *substring = (TTF_SubString *)(result + 2);
|
|
result[0] = substring;
|
|
result[1] = NULL;
|
|
SDL_copyp(substring, &substring1);
|
|
if (length == 0) {
|
|
substring->length = 0;
|
|
if ((substring->flags & TTF_SUBSTRING_DIRECTION_MASK) != TTF_DIRECTION_RTL) {
|
|
substring->rect.x += substring->rect.w;
|
|
}
|
|
substring->rect.w = 0;
|
|
}
|
|
|
|
if (count) {
|
|
*count = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Build a list of contiguous substrings
|
|
TTF_SubString *clusters = text->internal->clusters;
|
|
int num_results = 1;
|
|
TTF_SubString *last = &clusters[substring1.cluster_index];
|
|
for (int i = substring1.cluster_index + 1; i <= substring2.cluster_index; ++i) {
|
|
TTF_SubString *cluster = &clusters[i];
|
|
if (cluster->line_index != last->line_index) {
|
|
++num_results;
|
|
last = cluster;
|
|
}
|
|
}
|
|
|
|
TTF_SubString **result = (TTF_SubString **)SDL_malloc((num_results + 1) * sizeof(*result) + num_results * sizeof(**result));
|
|
if (!result) {
|
|
return NULL;
|
|
}
|
|
|
|
TTF_SubString *substrings = (TTF_SubString *)(result + num_results + 1);
|
|
for (int i = 0; i < num_results; ++i) {
|
|
result[i] = &substrings[i];
|
|
}
|
|
result[num_results] = NULL;
|
|
|
|
TTF_SubString *substring = substrings;
|
|
SDL_copyp(substring, &substring1);
|
|
for (int i = substring1.cluster_index + 1; i <= substring2.cluster_index; ++i) {
|
|
TTF_SubString *cluster = &clusters[i];
|
|
if (cluster->line_index == substring->line_index) {
|
|
substring->flags |= cluster->flags;
|
|
SDL_GetRectUnion(&substring->rect, &cluster->rect, &substring->rect);
|
|
} else {
|
|
substring->length = (cluster->offset - substring->offset);
|
|
++substring;
|
|
SDL_copyp(substring, cluster);
|
|
}
|
|
}
|
|
substring->length = (substring2.offset - substring->offset) + substring2.length;
|
|
|
|
if (count) {
|
|
*count = num_results;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *substring)
|
|
{
|
|
if (substring) {
|
|
SDL_zerop(substring);
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
TTF_CHECK_POINTER("substring", substring, false);
|
|
|
|
if (!TTF_UpdateText(text)) {
|
|
return false;
|
|
}
|
|
|
|
if (text->internal->num_clusters == 0) {
|
|
substring->rect.h = text->internal->layout->font_height;
|
|
return true;
|
|
}
|
|
|
|
TTF_Direction direction = TTF_GetTextDirection(text);
|
|
bool prefer_row = (direction != TTF_DIRECTION_TTB && direction != TTF_DIRECTION_BTT);
|
|
const TTF_SubString *closest = NULL;
|
|
int closest_dist = INT_MAX;
|
|
int wrap_cost = 100;
|
|
SDL_Point point = { x, y };
|
|
for (int i = 0; i < text->internal->num_clusters; ++i) {
|
|
const TTF_SubString *cluster = &text->internal->clusters[i];
|
|
int center_x = (cluster->rect.x + cluster->rect.w / 2);
|
|
int center_y = (cluster->rect.y + cluster->rect.h / 2);
|
|
int dist;
|
|
|
|
if (cluster->flags & TTF_SUBSTRING_LINE_END) {
|
|
if (prefer_row && (cluster->flags & TTF_SUBSTRING_LINE_END)) {
|
|
bool line_ends_left = ((cluster->flags & TTF_SUBSTRING_DIRECTION_MASK) == TTF_DIRECTION_RTL);
|
|
if ((y >= cluster->rect.y && y < (cluster->rect.y + cluster->rect.h)) &&
|
|
((!line_ends_left && x >= cluster->rect.x) ||
|
|
(line_ends_left && x <= cluster->rect.x))) {
|
|
closest = cluster;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (prefer_row && (cluster->flags & TTF_SUBSTRING_LINE_START)) {
|
|
bool line_ends_left = ((cluster->flags & TTF_SUBSTRING_DIRECTION_MASK) == TTF_DIRECTION_RTL);
|
|
if ((y >= cluster->rect.y && y < (cluster->rect.y + cluster->rect.h)) &&
|
|
((!line_ends_left && x < cluster->rect.x) ||
|
|
(line_ends_left && x > cluster->rect.x))) {
|
|
closest = cluster;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SDL_PointInRect(&point, &cluster->rect)) {
|
|
closest = cluster;
|
|
break;
|
|
}
|
|
}
|
|
if (prefer_row) {
|
|
dist = SDL_abs(center_y - y) * wrap_cost + SDL_abs(center_x - x);
|
|
} else {
|
|
dist = SDL_abs(center_x - x) * wrap_cost + SDL_abs(center_y - y);
|
|
}
|
|
if (dist < closest_dist) {
|
|
closest = cluster;
|
|
closest_dist = dist;
|
|
}
|
|
}
|
|
|
|
if (closest) {
|
|
SDL_copyp(substring, closest);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetPreviousTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *previous)
|
|
{
|
|
if (previous && previous != substring) {
|
|
SDL_zerop(previous);
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
TTF_CHECK_POINTER("substring", substring, false);
|
|
TTF_CHECK_POINTER("previous", previous, false);
|
|
|
|
int num_clusters = text->internal->num_clusters;
|
|
const TTF_SubString *clusters = text->internal->clusters;
|
|
if (substring->cluster_index < 0 || substring->cluster_index >= num_clusters) {
|
|
return SDL_SetError("Cluster index out of range");
|
|
}
|
|
if (substring->offset != clusters[substring->cluster_index].offset) {
|
|
return SDL_SetError("Stale substring");
|
|
}
|
|
if (substring->cluster_index == 0) {
|
|
SDL_copyp(previous, &clusters[0]);
|
|
previous->length = 0;
|
|
previous->rect.w = 0;
|
|
} else {
|
|
SDL_copyp(previous, &clusters[substring->cluster_index - 1]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_GetNextTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *next)
|
|
{
|
|
if (next && next != substring) {
|
|
SDL_zerop(next);
|
|
}
|
|
|
|
TTF_CHECK_POINTER("text", text, false);
|
|
TTF_CHECK_POINTER("substring", substring, false);
|
|
TTF_CHECK_POINTER("next", next, false);
|
|
|
|
int num_clusters = text->internal->num_clusters;
|
|
const TTF_SubString *clusters = text->internal->clusters;
|
|
if (substring->cluster_index < 0 || substring->cluster_index >= num_clusters) {
|
|
return SDL_SetError("Cluster index out of range");
|
|
}
|
|
if (substring->offset != clusters[substring->cluster_index].offset) {
|
|
return SDL_SetError("Stale substring");
|
|
}
|
|
if (substring->cluster_index == (num_clusters - 1)) {
|
|
SDL_copyp(next, &clusters[num_clusters - 1]);
|
|
} else {
|
|
SDL_copyp(next, &clusters[substring->cluster_index + 1]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TTF_UpdateText(TTF_Text *text)
|
|
{
|
|
if (text->internal->needs_layout_update) {
|
|
DestroyEngineText(text);
|
|
text->internal->needs_engine_update = true;
|
|
|
|
if (text->internal->ops) {
|
|
SDL_free(text->internal->ops);
|
|
text->internal->ops = NULL;
|
|
text->internal->num_ops = 0;
|
|
}
|
|
if (text->internal->clusters) {
|
|
SDL_free(text->internal->clusters);
|
|
text->internal->clusters = NULL;
|
|
text->internal->num_clusters = 0;
|
|
}
|
|
if (text->internal->layout->lines) {
|
|
SDL_free(text->internal->layout->lines);
|
|
text->internal->layout->lines = NULL;
|
|
}
|
|
text->num_lines = 0;
|
|
text->internal->w = 0;
|
|
text->internal->h = 0;
|
|
|
|
if (text->internal->font && text->text) {
|
|
if (!LayoutText(text)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
text->internal->needs_layout_update = false;
|
|
}
|
|
|
|
if (text->internal->needs_engine_update) {
|
|
if (!CreateEngineText(text)) {
|
|
return false;
|
|
}
|
|
|
|
text->internal->needs_engine_update = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TTF_DestroyText(TTF_Text *text)
|
|
{
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
DestroyEngineText(text);
|
|
|
|
if (text->internal->ops) {
|
|
SDL_free(text->internal->ops);
|
|
}
|
|
if (text->internal->clusters) {
|
|
SDL_free(text->internal->clusters);
|
|
}
|
|
if (text->internal->layout->lines) {
|
|
SDL_free(text->internal->layout->lines);
|
|
}
|
|
|
|
TTF_SetTextFont(text, NULL);
|
|
SDL_DestroyProperties(text->internal->props);
|
|
SDL_free(text->text);
|
|
SDL_free(text);
|
|
}
|
|
|
|
bool TTF_SetFontSize(TTF_Font *font, float ptsize)
|
|
{
|
|
return TTF_SetFontSizeDPI(font, ptsize, 0, 0);
|
|
}
|
|
|
|
bool TTF_SetFontSizeDPI(TTF_Font *font, float ptsize, int hdpi, int vdpi)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
if (ptsize <= 0.0f) {
|
|
return SDL_InvalidParamError("ptsize");
|
|
}
|
|
|
|
if (hdpi <= 0 && vdpi <= 0) {
|
|
hdpi = font->hdpi;
|
|
vdpi = font->vdpi;
|
|
} else if (hdpi <= 0) {
|
|
hdpi = vdpi;
|
|
} else if (vdpi <= 0) {
|
|
vdpi = hdpi;
|
|
}
|
|
|
|
if (ptsize == font->ptsize && hdpi == font->hdpi && vdpi == font->vdpi) {
|
|
return true;
|
|
}
|
|
|
|
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, (int)SDL_roundf(ptsize * 64), (FT_UInt)hdpi, (FT_UInt)vdpi);
|
|
if (error) {
|
|
return TTF_SetFTError("Couldn't set font size", error);
|
|
}
|
|
} 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) {
|
|
return SDL_SetError("Couldn't select size : no num_fixed_sizes");
|
|
}
|
|
|
|
// within [0; num_fixed_sizes - 1]
|
|
int index = (int)ptsize;
|
|
index = SDL_max(index, 0);
|
|
index = SDL_min(index, face->num_fixed_sizes - 1);
|
|
|
|
error = FT_Select_Size(face, index);
|
|
if (error) {
|
|
return TTF_SetFTError("Couldn't select size", error);
|
|
}
|
|
}
|
|
|
|
TTF_InitFontMetrics(font);
|
|
|
|
font->ptsize = ptsize;
|
|
font->hdpi = hdpi;
|
|
font->vdpi = vdpi;
|
|
|
|
Flush_Cache(font);
|
|
UpdateFontText(font, NULL);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
// Call when size or variations settings on underlying FT_Face change.
|
|
hb_ft_font_changed(font->hb_font);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
float TTF_GetFontSize(TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0.0f);
|
|
|
|
return font->ptsize;
|
|
}
|
|
|
|
bool TTF_GetFontDPI(TTF_Font *font, int *hdpi, int *vdpi)
|
|
{
|
|
if (hdpi) {
|
|
*hdpi = 0;
|
|
}
|
|
if (vdpi) {
|
|
*vdpi = 0;
|
|
}
|
|
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
if (hdpi) {
|
|
*hdpi = font->hdpi;
|
|
}
|
|
if (vdpi) {
|
|
*vdpi = font->vdpi;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TTF_SetFontStyle(TTF_Font *font, TTF_FontStyleFlags style)
|
|
{
|
|
TTF_FontStyleFlags prev_style;
|
|
TTF_FontStyleFlags face_style;
|
|
|
|
TTF_CHECK_FONT(font,);
|
|
|
|
prev_style = font->style;
|
|
face_style = (TTF_FontStyleFlags)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;
|
|
}
|
|
|
|
if (font->style == style) {
|
|
return;
|
|
}
|
|
|
|
font->style = style;
|
|
|
|
TTF_InitFontMetrics(font);
|
|
|
|
/* Flush the cache if styles that impact glyph drawing have changed */
|
|
if ((font->style | TTF_STYLE_NO_GLYPH_CHANGE) != (prev_style | TTF_STYLE_NO_GLYPH_CHANGE)) {
|
|
Flush_Cache(font);
|
|
}
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
|
|
TTF_FontStyleFlags TTF_GetFontStyle(const TTF_Font *font)
|
|
{
|
|
int style;
|
|
long face_style;
|
|
|
|
TTF_CHECK_FONT(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;
|
|
}
|
|
|
|
bool TTF_SetFontOutline(TTF_Font *font, int outline)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
outline = SDL_max(0, outline);
|
|
|
|
if (outline == font->outline) {
|
|
return true;
|
|
}
|
|
|
|
if (outline > 0) {
|
|
if (!font->stroker) {
|
|
FT_Error error;
|
|
|
|
SDL_LockMutex(TTF_state.lock);
|
|
error = FT_Stroker_New(TTF_state.library, &font->stroker);
|
|
SDL_UnlockMutex(TTF_state.lock);
|
|
if (error) {
|
|
return TTF_SetFTError("Couldn't create font stroker", error);
|
|
}
|
|
}
|
|
|
|
SDL_PropertiesID props = TTF_GetFontProperties(font);
|
|
FT_Stroker_LineCap line_cap = (FT_Stroker_LineCap)SDL_GetNumberProperty(props, TTF_PROP_FONT_OUTLINE_LINE_CAP_NUMBER, FT_STROKER_LINECAP_ROUND);
|
|
FT_Stroker_LineJoin line_join = (FT_Stroker_LineJoin)SDL_GetNumberProperty(props, TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER, FT_STROKER_LINEJOIN_ROUND);
|
|
FT_Fixed miter_limit = (FT_Fixed)SDL_GetNumberProperty(props, TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER, 0);
|
|
FT_Stroker_Set(font->stroker, outline * 64, line_cap, line_join, miter_limit);
|
|
} else {
|
|
if (font->stroker) {
|
|
FT_Stroker_Done(font->stroker);
|
|
font->stroker = NULL;
|
|
}
|
|
}
|
|
|
|
font->outline = outline;
|
|
|
|
TTF_InitFontMetrics(font);
|
|
Flush_Cache(font);
|
|
UpdateFontText(font, NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
int TTF_GetFontOutline(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, -1);
|
|
|
|
return font->outline;
|
|
}
|
|
|
|
void TTF_SetFontHinting(TTF_Font *font, TTF_HintingFlags hinting)
|
|
{
|
|
TTF_CHECK_FONT(font,);
|
|
|
|
int ft_load_target;
|
|
if (hinting == TTF_HINTING_LIGHT || hinting == TTF_HINTING_LIGHT_SUBPIXEL) {
|
|
ft_load_target = FT_LOAD_TARGET_LIGHT;
|
|
} else if (hinting == TTF_HINTING_MONO) {
|
|
ft_load_target = FT_LOAD_TARGET_MONO;
|
|
} else if (hinting == TTF_HINTING_NONE) {
|
|
ft_load_target = FT_LOAD_NO_HINTING;
|
|
} else {
|
|
ft_load_target = FT_LOAD_TARGET_NORMAL;
|
|
}
|
|
|
|
int render_subpixel = (hinting == TTF_HINTING_LIGHT_SUBPIXEL) ? 1 : 0;
|
|
|
|
if (ft_load_target == font->ft_load_target && render_subpixel == font->render_subpixel) {
|
|
return;
|
|
}
|
|
|
|
font->ft_load_target = ft_load_target;
|
|
|
|
#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);
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
|
|
TTF_HintingFlags TTF_GetFontHinting(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, TTF_HINTING_INVALID);
|
|
|
|
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;
|
|
}
|
|
|
|
bool TTF_SetFontSDF(TTF_Font *font, bool enabled)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
#if TTF_USE_SDF
|
|
if (font->render_sdf != enabled) {
|
|
font->render_sdf = enabled;
|
|
Flush_Cache(font);
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
return true;
|
|
#else
|
|
(void)enabled;
|
|
return SDL_SetError("SDL_ttf compiled without SDF support");
|
|
#endif
|
|
}
|
|
|
|
bool TTF_GetFontSDF(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
return font->render_sdf;
|
|
}
|
|
|
|
int TTF_GetFontWeight(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, -1);
|
|
|
|
return font->weight;
|
|
}
|
|
|
|
void TTF_SetFontWrapAlignment(TTF_Font *font, TTF_HorizontalAlignment align)
|
|
{
|
|
TTF_CHECK_FONT(font,);
|
|
|
|
if (align == font->horizontal_align) {
|
|
return;
|
|
}
|
|
|
|
switch (align) {
|
|
case TTF_HORIZONTAL_ALIGN_LEFT:
|
|
case TTF_HORIZONTAL_ALIGN_CENTER:
|
|
case TTF_HORIZONTAL_ALIGN_RIGHT:
|
|
font->horizontal_align = align;
|
|
break;
|
|
default:
|
|
// Ignore invalid values
|
|
break;
|
|
}
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
|
|
TTF_HorizontalAlignment TTF_GetFontWrapAlignment(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, TTF_HORIZONTAL_ALIGN_INVALID);
|
|
|
|
return font->horizontal_align;
|
|
}
|
|
|
|
int TTF_GetFontHeight(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return font->height;
|
|
}
|
|
|
|
int TTF_GetFontAscent(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return font->ascent + 2 * font->outline;
|
|
}
|
|
|
|
int TTF_GetFontDescent(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return font->descent;
|
|
}
|
|
|
|
void TTF_SetFontLineSkip(TTF_Font *font, int lineskip)
|
|
{
|
|
TTF_CHECK_FONT(font,);
|
|
|
|
if (lineskip == font->lineskip) {
|
|
return;
|
|
}
|
|
|
|
font->lineskip = lineskip;
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
|
|
int TTF_GetFontLineSkip(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return font->lineskip;
|
|
}
|
|
|
|
void TTF_SetFontKerning(TTF_Font *font, bool enabled)
|
|
{
|
|
TTF_CHECK_FONT(font,);
|
|
|
|
if (enabled == font->enable_kerning) {
|
|
return;
|
|
}
|
|
|
|
font->enable_kerning = enabled;
|
|
#if TTF_USE_HARFBUZZ
|
|
// Harfbuzz can do kerning positioning even if the font hasn't the data
|
|
#else
|
|
font->use_kerning = enabled && FT_HAS_KERNING(font->face);
|
|
#endif
|
|
UpdateFontText(font, NULL);
|
|
}
|
|
|
|
bool TTF_GetFontKerning(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
return font->enable_kerning;
|
|
}
|
|
|
|
int TTF_GetNumFontFaces(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return (int)font->face->num_faces;
|
|
}
|
|
|
|
bool TTF_FontIsFixedWidth(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
return FT_IS_FIXED_WIDTH(font->face);
|
|
}
|
|
|
|
bool TTF_FontIsScalable(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
return FT_IS_SCALABLE(font->face);
|
|
}
|
|
|
|
const char *TTF_GetFontFamilyName(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, NULL);
|
|
|
|
return font->face->family_name;
|
|
}
|
|
|
|
const char *TTF_GetFontStyleName(const TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, NULL);
|
|
|
|
return font->face->style_name;
|
|
}
|
|
|
|
bool TTF_SetFontDirection(TTF_Font *font, TTF_Direction direction)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
if (direction == font->direction) {
|
|
return true;
|
|
}
|
|
|
|
#if !TTF_USE_HARFBUZZ
|
|
if (direction != TTF_DIRECTION_INVALID && direction != TTF_DIRECTION_LTR) {
|
|
return SDL_Unsupported();
|
|
}
|
|
#endif
|
|
|
|
font->direction = direction;
|
|
UpdateFontText(font, NULL);
|
|
return true;
|
|
}
|
|
|
|
TTF_Direction TTF_GetFontDirection(TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, TTF_DIRECTION_INVALID);
|
|
|
|
return font->direction;
|
|
}
|
|
|
|
Uint32 TTF_StringToTag(const char *string)
|
|
{
|
|
Uint8 bytes[4] = { 0, 0, 0, 0 };
|
|
|
|
if (string) {
|
|
for (size_t i = 0; i < 4 && string[i]; ++i) {
|
|
bytes[i] = (Uint8)string[i];
|
|
}
|
|
}
|
|
|
|
Uint32 tag = ((Uint32)bytes[0] << 24) |
|
|
((Uint32)bytes[1] << 16) |
|
|
((Uint32)bytes[2] << 8) |
|
|
((Uint32)bytes[3] << 0);
|
|
return tag;
|
|
}
|
|
|
|
void TTF_TagToString(Uint32 tag, char *string, size_t size)
|
|
{
|
|
if (!string || !size) {
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < 4 && i < size; ++i) {
|
|
string[i] = (char)(Uint8)(tag >> 24);
|
|
tag <<= 8;
|
|
}
|
|
if (size > 4) {
|
|
string[4] = '\0';
|
|
}
|
|
}
|
|
|
|
bool TTF_SetFontScript(TTF_Font *font, Uint32 script)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
font->script = script;
|
|
UpdateFontText(font, NULL);
|
|
return true;
|
|
#else
|
|
(void) script;
|
|
return SDL_Unsupported();
|
|
#endif
|
|
}
|
|
|
|
Uint32 TTF_GetFontScript(TTF_Font *font)
|
|
{
|
|
TTF_CHECK_FONT(font, 0);
|
|
|
|
return font->script;
|
|
}
|
|
|
|
Uint32 TTF_GetGlyphScript(Uint32 ch)
|
|
{
|
|
Uint32 script = 0;
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_buffer_t *hb_buffer = hb_buffer_create();
|
|
|
|
if (hb_buffer == NULL) {
|
|
SDL_SetError("Cannot create harfbuzz buffer");
|
|
return 0;
|
|
}
|
|
|
|
hb_unicode_funcs_t *hb_unicode_functions = hb_buffer_get_unicode_funcs(hb_buffer);
|
|
|
|
if (hb_unicode_functions == NULL) {
|
|
hb_buffer_destroy(hb_buffer);
|
|
SDL_SetError("Can't get harfbuzz unicode functions");
|
|
return 0;
|
|
}
|
|
|
|
hb_buffer_clear_contents(hb_buffer);
|
|
hb_buffer_set_content_type(hb_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
|
|
|
|
script = hb_script_to_iso15924_tag(hb_unicode_script(hb_unicode_functions, ch));
|
|
|
|
hb_buffer_destroy(hb_buffer);
|
|
#else
|
|
(void)ch;
|
|
#endif
|
|
|
|
if (script == 0) {
|
|
SDL_SetError("Unknown script");
|
|
}
|
|
return script;
|
|
}
|
|
|
|
bool TTF_SetFontLanguage(TTF_Font *font, const char *language_bcp47)
|
|
{
|
|
TTF_CHECK_FONT(font, false);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_language_t hb_language;
|
|
if (language_bcp47 == NULL) {
|
|
hb_language = hb_language_from_string("", -1);
|
|
} else {
|
|
hb_language = hb_language_from_string(language_bcp47, -1);
|
|
}
|
|
|
|
if (hb_language == font->hb_language) {
|
|
return true;
|
|
}
|
|
|
|
font->hb_language = hb_language;
|
|
UpdateFontText(font, NULL);
|
|
return true;
|
|
#else
|
|
(void) language_bcp47;
|
|
return SDL_Unsupported();
|
|
#endif
|
|
}
|
|
|
|
static bool RemoveOneTextCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
|
|
{
|
|
TTF_Font *font = (TTF_Font *)userdata;
|
|
TTF_Text *text = (TTF_Text *)key;
|
|
(void)table;
|
|
(void)value;
|
|
if (text->internal->font == font) {
|
|
TTF_SetTextFont(text, NULL);
|
|
} else {
|
|
RemoveFontTextReference(font, text);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TTF_CloseFont(TTF_Font *font)
|
|
{
|
|
if (!font) {
|
|
return;
|
|
}
|
|
|
|
if (font->text) {
|
|
while (!SDL_HashTableEmpty(font->text)) {
|
|
SDL_IterateHashTable(font->text, RemoveOneTextCallback, font);
|
|
}
|
|
SDL_DestroyHashTable(font->text);
|
|
font->text = NULL;
|
|
}
|
|
Flush_Cache(font);
|
|
|
|
TTF_ClearFallbackFonts(font);
|
|
while (font->fallback_for) {
|
|
TTF_RemoveFallbackFont(font->fallback_for->font, font);
|
|
}
|
|
|
|
SDL_DestroyHashTable(font->glyphs);
|
|
SDL_DestroyHashTable(font->glyph_indices);
|
|
|
|
#if TTF_USE_HARFBUZZ
|
|
hb_font_destroy(font->hb_font);
|
|
#endif
|
|
if (font->props) {
|
|
SDL_DestroyProperties(font->props);
|
|
}
|
|
if (font->face) {
|
|
FT_Done_Face(font->face);
|
|
}
|
|
if (font->stroker) {
|
|
FT_Stroker_Done(font->stroker);
|
|
}
|
|
if (font->args.stream) {
|
|
SDL_free(font->args.stream);
|
|
}
|
|
if (font->closeio) {
|
|
TTF_CloseFontSource(font->src);
|
|
}
|
|
SDL_free(font->name);
|
|
SDL_free(font);
|
|
}
|
|
|
|
void TTF_Quit(void)
|
|
{
|
|
if (!SDL_ShouldQuit(&TTF_state.init)) {
|
|
return;
|
|
}
|
|
|
|
if (!SDL_AtomicDecRef(&TTF_state.refcount)) {
|
|
SDL_SetInitialized(&TTF_state.init, true);
|
|
return;
|
|
}
|
|
|
|
if (TTF_state.library) {
|
|
FT_Done_FreeType(TTF_state.library);
|
|
TTF_state.library = NULL;
|
|
}
|
|
|
|
if (TTF_state.lock) {
|
|
SDL_DestroyMutex(TTF_state.lock);
|
|
TTF_state.lock = NULL;
|
|
}
|
|
|
|
SDL_SetInitialized(&TTF_state.init, false);
|
|
}
|
|
|
|
int TTF_WasInit(void)
|
|
{
|
|
return SDL_GetAtomicInt(&TTF_state.refcount);
|
|
}
|
|
|