2020-12-27 15:30:33 +02:00
/**************************************************************************/
/* resource_importer_imagefont.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
# include "resource_importer_imagefont.h"
# include "core/io/image_loader.h"
# include "core/io/resource_saver.h"
String ResourceImporterImageFont : : get_importer_name ( ) const {
return " font_data_image " ;
}
String ResourceImporterImageFont : : get_visible_name ( ) const {
2024-06-18 19:02:51 +03:00
return " Font Data (Image Font) " ;
2020-12-27 15:30:33 +02:00
}
void ResourceImporterImageFont : : get_recognized_extensions ( List < String > * p_extensions ) const {
if ( p_extensions ) {
ImageLoader : : get_recognized_extensions ( p_extensions ) ;
}
}
String ResourceImporterImageFont : : get_save_extension ( ) const {
return " fontdata " ;
}
String ResourceImporterImageFont : : get_resource_type ( ) const {
2022-05-09 12:47:10 +03:00
return " FontFile " ;
2020-12-27 15:30:33 +02:00
}
2022-05-13 15:04:37 +02:00
bool ResourceImporterImageFont : : get_option_visibility ( const String & p_path , const String & p_option , const HashMap < StringName , Variant > & p_options ) const {
2020-12-27 15:30:33 +02:00
return true ;
}
2021-11-14 14:02:38 -03:00
void ResourceImporterImageFont : : get_import_options ( const String & p_path , List < ImportOption > * r_options , int p_preset ) const {
2020-12-27 15:30:33 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : PACKED_STRING_ARRAY , " character_ranges " ) , Vector < String > ( ) ) ) ;
2024-02-07 19:21:49 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : PACKED_STRING_ARRAY , " kerning_pairs " ) , Vector < String > ( ) ) ) ;
2024-05-11 19:44:54 +10:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " columns " , PROPERTY_HINT_RANGE , " 1,1024,1,or_greater " ) , 1 ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " rows " , PROPERTY_HINT_RANGE , " 1,1024,1,or_greater " ) , 1 ) ) ;
2022-11-10 08:42:14 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : RECT2I , " image_margin " ) , Rect2i ( ) ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : RECT2I , " character_margin " ) , Rect2i ( ) ) ) ;
2024-02-07 19:21:49 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " ascent " ) , 0 ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " descent " ) , 0 ) ) ;
2022-05-09 12:47:10 +03:00
2022-11-22 16:10:41 +01:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : ARRAY , " fallbacks " , PROPERTY_HINT_ARRAY_TYPE , MAKE_RESOURCE_TYPE_HINT ( " Font " ) ) , Array ( ) ) ) ;
2022-05-09 12:47:10 +03:00
2020-12-27 15:30:33 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " compress " ) , true ) ) ;
2023-08-14 10:42:49 +03:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " scaling_mode " , PROPERTY_HINT_ENUM , " Disabled,Enabled (Integer),Enabled (Fractional) " ) , TextServer : : FIXED_SIZE_SCALE_ENABLED ) ) ;
2020-12-27 15:30:33 +02:00
}
2024-09-23 16:07:40 +02:00
Error ResourceImporterImageFont : : import ( ResourceUID : : ID p_source_id , const String & p_source_file , const String & p_save_path , const HashMap < StringName , Variant > & p_options , List < String > * r_platform_variants , List < String > * r_gen_files , Variant * r_metadata ) {
2020-12-27 15:30:33 +02:00
print_verbose ( " Importing image font from: " + p_source_file ) ;
int columns = p_options [ " columns " ] ;
int rows = p_options [ " rows " ] ;
2024-02-07 19:21:49 +02:00
int ascent = p_options [ " ascent " ] ;
int descent = p_options [ " descent " ] ;
2020-12-27 15:30:33 +02:00
Vector < String > ranges = p_options [ " character_ranges " ] ;
2024-02-07 19:21:49 +02:00
Vector < String > kern = p_options [ " kerning_pairs " ] ;
2022-05-09 12:47:10 +03:00
Array fallbacks = p_options [ " fallbacks " ] ;
2022-11-10 08:42:14 +02:00
Rect2i img_margin = p_options [ " image_margin " ] ;
Rect2i char_margin = p_options [ " character_margin " ] ;
2023-08-14 10:42:49 +03:00
TextServer : : FixedSizeScaleMode smode = ( TextServer : : FixedSizeScaleMode ) p_options [ " scaling_mode " ] . operator int ( ) ;
2022-11-10 08:42:14 +02:00
Ref < Image > img ;
img . instantiate ( ) ;
Error err = ImageLoader : : load_image ( p_source_file , img ) ;
2023-11-11 22:59:05 +01:00
ERR_FAIL_COND_V_MSG ( err ! = OK , ERR_FILE_CANT_READ , vformat ( " Can't load font texture: \" %s \" . " , p_source_file ) ) ;
2022-11-10 08:42:14 +02:00
2024-05-11 19:44:54 +10:00
ERR_FAIL_COND_V_MSG ( columns < = 0 , ERR_FILE_CANT_READ , vformat ( " Columns (%d) must be positive. " , columns ) ) ;
ERR_FAIL_COND_V_MSG ( rows < = 0 , ERR_FILE_CANT_READ , vformat ( " Rows (%d) must be positive. " , rows ) ) ;
2025-06-04 10:38:22 +03:00
int remaining = columns * rows ;
2022-11-10 08:42:14 +02:00
int chr_cell_width = ( img - > get_width ( ) - img_margin . position . x - img_margin . size . x ) / columns ;
int chr_cell_height = ( img - > get_height ( ) - img_margin . position . y - img_margin . size . y ) / rows ;
2023-11-11 22:59:05 +01:00
ERR_FAIL_COND_V_MSG ( chr_cell_width < = 0 | | chr_cell_height < = 0 , ERR_FILE_CANT_READ , " Image margin too big. " ) ;
2022-11-10 08:42:14 +02:00
int chr_width = chr_cell_width - char_margin . position . x - char_margin . size . x ;
int chr_height = chr_cell_height - char_margin . position . y - char_margin . size . y ;
2023-11-11 22:59:05 +01:00
ERR_FAIL_COND_V_MSG ( chr_width < = 0 | | chr_height < = 0 , ERR_FILE_CANT_READ , " Character margin too big. " ) ;
2020-12-27 15:30:33 +02:00
2022-05-09 12:47:10 +03:00
Ref < FontFile > font ;
2020-12-27 15:30:33 +02:00
font . instantiate ( ) ;
2022-08-12 14:03:28 +03:00
font - > set_antialiasing ( TextServer : : FONT_ANTIALIASING_NONE ) ;
2022-04-19 13:27:18 +03:00
font - > set_generate_mipmaps ( false ) ;
2020-12-27 15:30:33 +02:00
font - > set_multichannel_signed_distance_field ( false ) ;
2022-11-10 08:42:14 +02:00
font - > set_fixed_size ( chr_height ) ;
2022-01-10 10:13:22 +02:00
font - > set_subpixel_positioning ( TextServer : : SUBPIXEL_POSITIONING_DISABLED ) ;
2024-10-30 11:14:11 +02:00
font - > set_keep_rounding_remainders ( true ) ;
2020-12-27 15:30:33 +02:00
font - > set_force_autohinter ( false ) ;
2025-04-01 13:36:10 +03:00
font - > set_modulate_color_glyphs ( false ) ;
2022-11-21 15:04:01 +02:00
font - > set_allow_system_fallback ( false ) ;
2020-12-27 15:30:33 +02:00
font - > set_hinting ( TextServer : : HINTING_NONE ) ;
2022-05-09 12:47:10 +03:00
font - > set_fallbacks ( fallbacks ) ;
2022-11-10 08:42:14 +02:00
font - > set_texture_image ( 0 , Vector2i ( chr_height , 0 ) , 0 , img ) ;
2023-08-14 10:42:49 +03:00
font - > set_fixed_size_scale_mode ( smode ) ;
2020-12-27 15:30:33 +02:00
2024-02-07 19:21:49 +02:00
int32_t pos = 0 ;
for ( const String & range : ranges ) {
2025-06-04 10:38:22 +03:00
Vector < int32_t > list ;
2024-02-07 19:21:49 +02:00
int32_t start = - 1 ;
int32_t end = - 1 ;
int chr_adv = 0 ;
Vector2i chr_off ;
{
enum RangeParseStep {
STEP_START_BEGIN ,
STEP_START_READ_HEX ,
STEP_START_READ_DEC ,
STEP_END_BEGIN ,
STEP_END_READ_HEX ,
STEP_END_READ_DEC ,
STEP_ADVANCE_BEGIN ,
STEP_OFF_X_BEGIN ,
STEP_OFF_Y_BEGIN ,
STEP_FINISHED ,
} ;
RangeParseStep step = STEP_START_BEGIN ;
String token ;
for ( int c = 0 ; c < range . length ( ) ; c + + ) {
switch ( step ) {
case STEP_START_BEGIN :
case STEP_END_BEGIN : {
// Read range start/end first symbol.
if ( range [ c ] = = ' U ' | | range [ c ] = = ' u ' ) {
if ( ( c < = range . length ( ) - 2 ) & & range [ c + 1 ] = = ' + ' ) {
token = String ( ) ;
if ( step = = STEP_START_BEGIN ) {
step = STEP_START_READ_HEX ;
} else {
step = STEP_END_READ_HEX ;
}
c + + ; // Skip "+".
continue ;
}
2025-02-04 09:51:10 +02:00
} else if ( range [ c ] = = ' 0 ' & & ( c < = range . length ( ) - 2 ) & & ( range [ c + 1 ] = = ' x ' | | range [ c + 1 ] = = ' X ' ) ) {
2024-04-14 00:29:25 +02:00
// Read hexadecimal value, start.
token = String ( ) ;
if ( step = = STEP_START_BEGIN ) {
step = STEP_START_READ_HEX ;
} else {
step = STEP_END_READ_HEX ;
2024-02-07 19:21:49 +02:00
}
2024-04-14 00:29:25 +02:00
c + + ; // Skip "x".
continue ;
2024-02-07 19:21:49 +02:00
} else if ( range [ c ] = = ' \' ' | | range [ c ] = = ' \" ' ) {
if ( ( c < = range . length ( ) - 3 ) & & ( range [ c + 2 ] = = ' \' ' | | range [ c + 2 ] = = ' \" ' ) ) {
token = String ( ) ;
if ( step = = STEP_START_BEGIN ) {
start = range . unicode_at ( c + 1 ) ;
2025-06-04 10:38:22 +03:00
if ( ( c < = range . length ( ) - 4 ) & & ( range [ c + 3 ] = = ' , ' ) ) {
list . push_back ( start ) ;
step = STEP_START_BEGIN ;
} else {
step = STEP_END_BEGIN ;
}
2024-02-07 19:21:49 +02:00
} else {
end = range . unicode_at ( c + 1 ) ;
step = STEP_ADVANCE_BEGIN ;
}
c = c + 2 ; // Skip the rest or token.
continue ;
}
} else if ( is_digit ( range [ c ] ) ) {
// Read decimal value, start.
token = String ( ) ;
2024-04-14 00:29:25 +02:00
token + = range [ c ] ;
2024-02-07 19:21:49 +02:00
if ( step = = STEP_START_BEGIN ) {
step = STEP_START_READ_DEC ;
} else {
step = STEP_END_READ_DEC ;
}
continue ;
}
[[fallthrough]] ;
}
case STEP_ADVANCE_BEGIN :
case STEP_OFF_X_BEGIN :
case STEP_OFF_Y_BEGIN : {
// Read advance and offset.
if ( range [ c ] = = ' ' ) {
2024-11-16 18:52:15 +01:00
int next = range . find_char ( ' ' , c + 1 ) ;
2024-02-07 19:21:49 +02:00
if ( next < c ) {
next = range . length ( ) ;
}
if ( step = = STEP_OFF_X_BEGIN ) {
chr_off . x = range . substr ( c + 1 , next - ( c + 1 ) ) . to_int ( ) ;
step = STEP_OFF_Y_BEGIN ;
} else if ( step = = STEP_OFF_Y_BEGIN ) {
chr_off . y = range . substr ( c + 1 , next - ( c + 1 ) ) . to_int ( ) ;
step = STEP_FINISHED ;
} else {
chr_adv = range . substr ( c + 1 , next - ( c + 1 ) ) . to_int ( ) ;
step = STEP_OFF_X_BEGIN ;
}
c = next - 1 ;
continue ;
}
} break ;
case STEP_START_READ_HEX :
case STEP_END_READ_HEX : {
// Read hexadecimal value.
if ( is_hex_digit ( range [ c ] ) ) {
token + = range [ c ] ;
} else {
if ( step = = STEP_START_READ_HEX ) {
start = token . hex_to_int ( ) ;
2025-06-04 10:38:22 +03:00
if ( range [ c ] = = ' , ' ) {
list . push_back ( start ) ;
step = STEP_START_BEGIN ;
} else {
step = STEP_END_BEGIN ;
}
2024-02-07 19:21:49 +02:00
} else {
end = token . hex_to_int ( ) ;
step = STEP_ADVANCE_BEGIN ;
2024-06-12 11:38:41 +03:00
c - - ;
2024-02-07 19:21:49 +02:00
}
}
} break ;
case STEP_START_READ_DEC :
case STEP_END_READ_DEC : {
// Read decimal value.
if ( is_digit ( range [ c ] ) ) {
token + = range [ c ] ;
} else {
if ( step = = STEP_START_READ_DEC ) {
start = token . to_int ( ) ;
2025-06-04 10:38:22 +03:00
if ( range [ c ] = = ' , ' ) {
list . push_back ( start ) ;
step = STEP_START_BEGIN ;
} else {
step = STEP_END_BEGIN ;
}
2024-02-07 19:21:49 +02:00
} else {
end = token . to_int ( ) ;
step = STEP_ADVANCE_BEGIN ;
2024-06-12 11:38:41 +03:00
c - - ;
2024-02-07 19:21:49 +02:00
}
}
} break ;
default : {
WARN_PRINT ( vformat ( " Invalid character \" %d \" in the range: \" %s \" " , c , range ) ) ;
} break ;
}
}
2024-04-14 00:29:25 +02:00
if ( step = = STEP_START_READ_HEX ) {
start = token . hex_to_int ( ) ;
} else if ( step = = STEP_START_READ_DEC ) {
start = token . to_int ( ) ;
} else if ( step = = STEP_END_READ_HEX ) {
end = token . hex_to_int ( ) ;
} else if ( step = = STEP_END_READ_DEC ) {
end = token . to_int ( ) ;
}
2024-02-07 19:21:49 +02:00
if ( end = = - 1 ) {
end = start ;
2020-12-27 15:30:33 +02:00
}
2025-06-04 10:38:22 +03:00
if ( ! list . is_empty ( ) & & end ! = list [ list . size ( ) - 1 ] ) {
list . push_back ( end ) ;
}
2024-04-14 00:29:25 +02:00
2024-02-07 19:21:49 +02:00
if ( start = = - 1 ) {
WARN_PRINT ( vformat ( " Invalid range: \" %s \" " , range ) ) ;
2020-12-27 15:30:33 +02:00
continue ;
}
}
2024-02-07 19:21:49 +02:00
2025-06-04 10:38:22 +03:00
if ( ! list . is_empty ( ) ) {
ERR_FAIL_COND_V_MSG ( list . size ( ) > remaining , ERR_CANT_CREATE , vformat ( " Too many characters in range \" %s \" , got %d but expected be %d. " , range , list . size ( ) , remaining ) ) ;
for ( int32_t idx : list ) {
int x = pos % columns ;
int y = pos / columns ;
font - > set_glyph_advance ( 0 , chr_height , idx , Vector2 ( chr_width + chr_adv , 0 ) ) ;
font - > set_glyph_offset ( 0 , Vector2i ( chr_height , 0 ) , idx , Vector2i ( 0 , - 0.5 * chr_height ) + chr_off ) ;
font - > set_glyph_size ( 0 , Vector2i ( chr_height , 0 ) , idx , Vector2 ( chr_width , chr_height ) ) ;
font - > set_glyph_uv_rect ( 0 , Vector2i ( chr_height , 0 ) , idx , Rect2 ( img_margin . position . x + chr_cell_width * x + char_margin . position . x , img_margin . position . y + chr_cell_height * y + char_margin . position . y , chr_width , chr_height ) ) ;
font - > set_glyph_texture_idx ( 0 , Vector2i ( chr_height , 0 ) , idx , 0 ) ;
pos + + ;
}
remaining - = list . size ( ) ;
} else {
ERR_FAIL_COND_V_MSG ( Math : : abs ( end - start ) > remaining , ERR_CANT_CREATE , vformat ( " Too many characters in range \" %s \" , got %d but expected %d. " , range , Math : : abs ( end - start ) , remaining ) ) ;
for ( int32_t idx = MIN ( start , end ) ; idx < = MAX ( start , end ) ; idx + + ) {
int x = pos % columns ;
int y = pos / columns ;
font - > set_glyph_advance ( 0 , chr_height , idx , Vector2 ( chr_width + chr_adv , 0 ) ) ;
font - > set_glyph_offset ( 0 , Vector2i ( chr_height , 0 ) , idx , Vector2i ( 0 , - 0.5 * chr_height ) + chr_off ) ;
font - > set_glyph_size ( 0 , Vector2i ( chr_height , 0 ) , idx , Vector2 ( chr_width , chr_height ) ) ;
font - > set_glyph_uv_rect ( 0 , Vector2i ( chr_height , 0 ) , idx , Rect2 ( img_margin . position . x + chr_cell_width * x + char_margin . position . x , img_margin . position . y + chr_cell_height * y + char_margin . position . y , chr_width , chr_height ) ) ;
font - > set_glyph_texture_idx ( 0 , Vector2i ( chr_height , 0 ) , idx , 0 ) ;
pos + + ;
}
remaining - = abs ( end - start ) ;
2020-12-27 15:30:33 +02:00
}
}
2024-02-07 19:21:49 +02:00
for ( const String & kp : kern ) {
const Vector < String > & kp_tokens = kp . split ( " " ) ;
if ( kp_tokens . size ( ) ! = 3 ) {
WARN_PRINT ( vformat ( " Invalid kerning pairs string: \" %s \" " , kp ) ) ;
continue ;
}
2024-06-13 09:20:04 +03:00
String from_tokens ;
for ( int i = 0 ; i < kp_tokens [ 0 ] . length ( ) ; i + + ) {
2024-08-08 22:41:09 +03:00
if ( i < = kp_tokens [ 0 ] . length ( ) - 6 & & kp_tokens [ 0 ] [ i ] = = ' \\ ' & & kp_tokens [ 0 ] [ i + 1 ] = = ' u ' & & is_hex_digit ( kp_tokens [ 0 ] [ i + 2 ] ) & & is_hex_digit ( kp_tokens [ 0 ] [ i + 3 ] ) & & is_hex_digit ( kp_tokens [ 0 ] [ i + 4 ] ) & & is_hex_digit ( kp_tokens [ 0 ] [ i + 5 ] ) ) {
2024-06-13 09:20:04 +03:00
char32_t charcode = kp_tokens [ 0 ] . substr ( i + 2 , 4 ) . hex_to_int ( ) ;
from_tokens + = charcode ;
2024-08-08 22:41:09 +03:00
i + = 5 ;
2024-06-13 09:20:04 +03:00
} else {
from_tokens + = kp_tokens [ 0 ] [ i ] ;
}
}
String to_tokens ;
for ( int i = 0 ; i < kp_tokens [ 1 ] . length ( ) ; i + + ) {
2024-08-08 22:41:09 +03:00
if ( i < = kp_tokens [ 1 ] . length ( ) - 6 & & kp_tokens [ 1 ] [ i ] = = ' \\ ' & & kp_tokens [ 1 ] [ i + 1 ] = = ' u ' & & is_hex_digit ( kp_tokens [ 1 ] [ i + 2 ] ) & & is_hex_digit ( kp_tokens [ 1 ] [ i + 3 ] ) & & is_hex_digit ( kp_tokens [ 1 ] [ i + 4 ] ) & & is_hex_digit ( kp_tokens [ 1 ] [ i + 5 ] ) ) {
2024-06-13 09:20:04 +03:00
char32_t charcode = kp_tokens [ 1 ] . substr ( i + 2 , 4 ) . hex_to_int ( ) ;
to_tokens + = charcode ;
2024-08-08 22:41:09 +03:00
i + = 5 ;
2024-06-13 09:20:04 +03:00
} else {
to_tokens + = kp_tokens [ 1 ] [ i ] ;
}
}
2024-02-07 19:21:49 +02:00
int offset = kp_tokens [ 2 ] . to_int ( ) ;
2024-06-13 09:20:04 +03:00
for ( int a = 0 ; a < from_tokens . length ( ) ; a + + ) {
for ( int b = 0 ; b < to_tokens . length ( ) ; b + + ) {
font - > set_kerning ( 0 , chr_height , Vector2i ( from_tokens . unicode_at ( a ) , to_tokens . unicode_at ( b ) ) , Vector2 ( offset , 0 ) ) ;
2024-02-07 19:21:49 +02:00
}
}
}
if ( ascent > 0 ) {
font - > set_cache_ascent ( 0 , chr_height , ascent ) ;
} else {
font - > set_cache_ascent ( 0 , chr_height , 0.5 * chr_height ) ;
}
if ( descent > 0 ) {
font - > set_cache_descent ( 0 , chr_height , descent ) ;
} else {
font - > set_cache_descent ( 0 , chr_height , 0.5 * chr_height ) ;
}
2020-12-27 15:30:33 +02:00
2022-05-09 12:47:10 +03:00
int flg = 0 ;
2020-12-27 15:30:33 +02:00
if ( ( bool ) p_options [ " compress " ] ) {
flg | = ResourceSaver : : SaverFlags : : FLAG_COMPRESS ;
}
print_verbose ( " Saving to: " + p_save_path + " .fontdata " ) ;
2022-06-03 01:33:42 +02:00
err = ResourceSaver : : save ( font , p_save_path + " .fontdata " , flg ) ;
2020-12-27 15:30:33 +02:00
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Cannot save font to file \" " + p_save_path + " .res \" . " ) ;
print_verbose ( " Done saving to: " + p_save_path + " .fontdata " ) ;
return OK ;
}