2018-09-23 19:26:09 +02:00
/*
* This file is part of OpenTTD .
* OpenTTD is free software ; you can redistribute it and / or modify it under the terms of the GNU General Public License as published by the Free Software Foundation , version 2.
* OpenTTD is distributed in the hope that it will be useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
* See the GNU General Public License for more details . You should have received a copy of the GNU General Public License along with OpenTTD . If not , see < http : //www.gnu.org/licenses/>.
*/
/** @file string_osx.cpp Functions related to localized text support on OSX. */
# include "../../stdafx.h"
# include "string_osx.h"
2018-10-21 18:40:39 +02:00
# include "../../string_func.h"
2018-10-28 23:30:49 +01:00
# include "../../strings_func.h"
# include "../../table/control_codes.h"
# include "../../fontcache.h"
2022-12-18 00:34:19 +01:00
# include "../../zoom_func.h"
2018-09-23 19:26:09 +02:00
# include "macos.h"
# include <CoreFoundation/CoreFoundation.h>
2020-04-10 14:13:56 +02:00
/* CTRunDelegateCreate is supported since MacOS X 10.5, but was only included in the SDKs starting with the 10.9 SDK. */
# ifndef HAVE_OSX_109_SDK
extern " C " {
typedef const struct __CTRunDelegate * CTRunDelegateRef ;
2024-01-03 22:33:38 +01:00
typedef void ( * CTRunDelegateDeallocateCallback ) ( void * refCon ) ;
typedef CGFloat ( * CTRunDelegateGetAscentCallback ) ( void * refCon ) ;
typedef CGFloat ( * CTRunDelegateGetDescentCallback ) ( void * refCon ) ;
typedef CGFloat ( * CTRunDelegateGetWidthCallback ) ( void * refCon ) ;
2020-04-10 14:13:56 +02:00
typedef struct {
CFIndex version ;
CTRunDelegateDeallocateCallback dealloc ;
CTRunDelegateGetAscentCallback getAscent ;
CTRunDelegateGetDescentCallback getDescent ;
CTRunDelegateGetWidthCallback getWidth ;
} CTRunDelegateCallbacks ;
enum {
kCTRunDelegateVersion1 = 1 ,
kCTRunDelegateCurrentVersion = kCTRunDelegateVersion1
} ;
extern const CFStringRef kCTRunDelegateAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER ;
2024-01-03 22:33:38 +01:00
CTRunDelegateRef CTRunDelegateCreate ( const CTRunDelegateCallbacks * callbacks , void * refCon ) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER ;
2020-04-10 14:13:56 +02:00
}
# endif /* HAVE_OSX_109_SDK */
2018-10-28 23:30:49 +01:00
/** Cached current locale. */
2019-09-01 15:27:39 +02:00
static CFAutoRelease < CFLocaleRef > _osx_locale ;
2018-10-28 23:30:49 +01:00
/** CoreText cache for font information, cleared when OTTD changes fonts. */
2019-09-01 15:27:39 +02:00
static CFAutoRelease < CTFontRef > _font_cache [ FS_END ] ;
2018-10-28 23:30:49 +01:00
/**
* Wrapper for doing layouts with CoreText .
*/
class CoreTextParagraphLayout : public ParagraphLayouter {
private :
const CoreTextParagraphLayoutFactory : : CharType * text_buffer ;
ptrdiff_t length ;
2024-01-03 22:33:38 +01:00
const FontMap & font_map ;
2018-10-28 23:30:49 +01:00
2019-09-01 15:27:39 +02:00
CFAutoRelease < CTTypesetterRef > typesetter ;
2018-10-28 23:30:49 +01:00
CFIndex cur_offset = 0 ; ///< Offset from the start of the current run from where to output.
public :
/** Visual run contains data about the bit of text with the same font. */
class CoreTextVisualRun : public ParagraphLayouter : : VisualRun {
private :
std : : vector < GlyphID > glyphs ;
2024-01-18 22:06:43 +01:00
std : : vector < Point > positions ;
2018-10-28 23:30:49 +01:00
std : : vector < int > glyph_to_char ;
int total_advance = 0 ;
Font * font ;
public :
CoreTextVisualRun ( CTRunRef run , Font * font , const CoreTextParagraphLayoutFactory : : CharType * buff ) ;
2019-04-02 21:30:53 +02:00
CoreTextVisualRun ( CoreTextVisualRun & & other ) = default ;
2018-10-28 23:30:49 +01:00
2024-01-12 02:39:44 +01:00
const std : : vector < GlyphID > & GetGlyphs ( ) const override { return this - > glyphs ; }
2024-01-18 22:06:43 +01:00
const std : : vector < Point > & GetPositions ( ) const override { return this - > positions ; }
2024-01-12 02:39:44 +01:00
const std : : vector < int > & GetGlyphToCharMap ( ) const override { return this - > glyph_to_char ; }
2018-10-28 23:30:49 +01:00
2019-04-02 21:30:34 +02:00
const Font * GetFont ( ) const override { return this - > font ; }
int GetLeading ( ) const override { return this - > font - > fc - > GetHeight ( ) ; }
int GetGlyphCount ( ) const override { return ( int ) this - > glyphs . size ( ) ; }
2018-10-28 23:30:49 +01:00
int GetAdvance ( ) const { return this - > total_advance ; }
} ;
/** A single line worth of VisualRuns. */
2019-04-02 21:30:53 +02:00
class CoreTextLine : public std : : vector < CoreTextVisualRun > , public ParagraphLayouter : : Line {
2018-10-28 23:30:49 +01:00
public :
2019-09-01 15:27:39 +02:00
CoreTextLine ( CFAutoRelease < CTLineRef > line , const FontMap & fontMapping , const CoreTextParagraphLayoutFactory : : CharType * buff )
2018-10-28 23:30:49 +01:00
{
2019-09-01 15:27:39 +02:00
CFArrayRef runs = CTLineGetGlyphRuns ( line . get ( ) ) ;
2018-10-28 23:30:49 +01:00
for ( CFIndex i = 0 ; i < CFArrayGetCount ( runs ) ; i + + ) {
CTRunRef run = ( CTRunRef ) CFArrayGetValueAtIndex ( runs , i ) ;
/* Extract font information for this run. */
CFRange chars = CTRunGetStringRange ( run ) ;
2023-05-16 21:50:41 +02:00
auto map = fontMapping . upper_bound ( chars . location ) ;
2018-10-28 23:30:49 +01:00
2019-04-02 21:30:53 +02:00
this - > emplace_back ( run , map - > second , buff ) ;
2018-10-28 23:30:49 +01:00
}
}
2019-04-02 21:30:34 +02:00
int GetLeading ( ) const override ;
int GetWidth ( ) const override ;
int CountRuns ( ) const override { return this - > size ( ) ; }
2019-04-02 21:30:53 +02:00
const VisualRun & GetVisualRun ( int run ) const override { return this - > at ( run ) ; }
2018-10-28 23:30:49 +01:00
2023-05-08 19:01:06 +02:00
int GetInternalCharLength ( char32_t c ) const override
2018-10-28 23:30:49 +01:00
{
/* CoreText uses UTF-16 internally which means we need to account for surrogate pairs. */
return c > = 0x010000U ? 2 : 1 ;
}
} ;
2019-09-01 15:27:39 +02:00
CoreTextParagraphLayout ( CFAutoRelease < CTTypesetterRef > typesetter , const CoreTextParagraphLayoutFactory : : CharType * buffer , ptrdiff_t len , const FontMap & fontMapping ) : text_buffer ( buffer ) , length ( len ) , font_map ( fontMapping ) , typesetter ( std : : move ( typesetter ) )
2018-10-28 23:30:49 +01:00
{
this - > Reflow ( ) ;
}
2019-04-02 21:30:34 +02:00
void Reflow ( ) override
2018-10-28 23:30:49 +01:00
{
this - > cur_offset = 0 ;
}
2019-04-02 21:31:10 +02:00
std : : unique_ptr < const Line > NextLine ( int max_width ) override ;
2018-10-28 23:30:49 +01:00
} ;
/** Get the width of an encoded sprite font character. */
static CGFloat SpriteFontGetWidth ( void * ref_con )
{
FontSize fs = ( FontSize ) ( ( size_t ) ref_con > > 24 ) ;
2023-05-08 19:01:06 +02:00
char32_t c = ( char32_t ) ( ( size_t ) ref_con & 0xFFFFFF ) ;
2018-10-28 23:30:49 +01:00
return GetGlyphWidth ( fs , c ) ;
}
static CTRunDelegateCallbacks _sprite_font_callback = {
2019-04-10 23:07:06 +02:00
kCTRunDelegateCurrentVersion , nullptr , nullptr , nullptr ,
2018-10-28 23:30:49 +01:00
& SpriteFontGetWidth
} ;
/* static */ ParagraphLayouter * CoreTextParagraphLayoutFactory : : GetParagraphLayout ( CharType * buff , CharType * buff_end , FontMap & fontMapping )
{
2019-04-10 23:07:06 +02:00
if ( ! MacOSVersionIsAtLeast ( 10 , 5 , 0 ) ) return nullptr ;
2018-10-28 23:30:49 +01:00
/* Can't layout an empty string. */
ptrdiff_t length = buff_end - buff ;
2019-04-10 23:07:06 +02:00
if ( length = = 0 ) return nullptr ;
2018-10-28 23:30:49 +01:00
/* Can't layout our in-built sprite fonts. */
2019-02-17 12:20:52 +01:00
for ( const auto & i : fontMapping ) {
2019-04-10 23:07:06 +02:00
if ( i . second - > fc - > IsBuiltInFont ( ) ) return nullptr ;
2018-10-28 23:30:49 +01:00
}
/* Make attributed string with embedded font information. */
2019-09-01 15:27:39 +02:00
CFAutoRelease < CFMutableAttributedStringRef > str ( CFAttributedStringCreateMutable ( kCFAllocatorDefault , 0 ) ) ;
CFAttributedStringBeginEditing ( str . get ( ) ) ;
2018-10-28 23:30:49 +01:00
2019-09-01 15:27:39 +02:00
CFAutoRelease < CFStringRef > base ( CFStringCreateWithCharactersNoCopy ( kCFAllocatorDefault , buff , length , kCFAllocatorNull ) ) ;
CFAttributedStringReplaceString ( str . get ( ) , CFRangeMake ( 0 , 0 ) , base . get ( ) ) ;
2018-10-28 23:30:49 +01:00
2023-12-16 02:30:12 +01:00
const UniChar replacment_char = 0xFFFC ;
CFAutoRelease < CFStringRef > replacment_str ( CFStringCreateWithCharacters ( kCFAllocatorDefault , & replacment_char , 1 ) ) ;
2018-10-28 23:30:49 +01:00
/* Apply font and colour ranges to our string. This is important to make sure
* that we get proper glyph boundaries on style changes . */
int last = 0 ;
2019-02-17 12:20:52 +01:00
for ( const auto & i : fontMapping ) {
if ( i . first - last = = 0 ) continue ;
2018-10-28 23:30:49 +01:00
2021-02-13 22:51:18 +01:00
CTFontRef font = ( CTFontRef ) i . second - > fc - > GetOSHandle ( ) ;
if ( font = = nullptr ) {
if ( ! _font_cache [ i . second - > fc - > GetSize ( ) ] ) {
/* Cache font information. */
2023-05-26 20:32:41 +02:00
CFAutoRelease < CFStringRef > font_name ( CFStringCreateWithCString ( kCFAllocatorDefault , i . second - > fc - > GetFontName ( ) . c_str ( ) , kCFStringEncodingUTF8 ) ) ;
2021-02-13 22:51:18 +01:00
_font_cache [ i . second - > fc - > GetSize ( ) ] . reset ( CTFontCreateWithName ( font_name . get ( ) , i . second - > fc - > GetFontSize ( ) , nullptr ) ) ;
}
font = _font_cache [ i . second - > fc - > GetSize ( ) ] . get ( ) ;
2018-10-28 23:30:49 +01:00
}
2021-02-13 22:51:18 +01:00
CFAttributedStringSetAttribute ( str . get ( ) , CFRangeMake ( last , i . first - last ) , kCTFontAttributeName , font ) ;
2018-10-28 23:30:49 +01:00
2023-05-08 19:01:06 +02:00
CGColorRef color = CGColorCreateGenericGray ( ( uint8_t ) i . second - > colour / 255.0f , 1.0f ) ; // We don't care about the real colours, just that they are different.
2019-09-01 15:27:39 +02:00
CFAttributedStringSetAttribute ( str . get ( ) , CFRangeMake ( last , i . first - last ) , kCTForegroundColorAttributeName , color ) ;
2018-10-28 23:30:49 +01:00
CGColorRelease ( color ) ;
2023-12-16 00:06:19 +01:00
/* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */
2019-02-17 12:20:52 +01:00
for ( ssize_t c = last ; c < i . first ; c + + ) {
2023-12-16 00:06:19 +01:00
if ( buff [ c ] > = SCC_SPRITE_START & & buff [ c ] < = SCC_SPRITE_END & & i . second - > fc - > MapCharToGlyph ( buff [ c ] , false ) = = 0 ) {
2019-09-01 15:27:39 +02:00
CFAutoRelease < CTRunDelegateRef > del ( CTRunDelegateCreate ( & _sprite_font_callback , ( void * ) ( size_t ) ( buff [ c ] | ( i . second - > fc - > GetSize ( ) < < 24 ) ) ) ) ;
2023-12-16 02:30:12 +01:00
/* According to the offical documentation, if a run delegate is used, the char should always be 0xFFFC. */
CFAttributedStringReplaceString ( str . get ( ) , CFRangeMake ( c , 1 ) , replacment_str . get ( ) ) ;
2019-09-01 15:27:39 +02:00
CFAttributedStringSetAttribute ( str . get ( ) , CFRangeMake ( c , 1 ) , kCTRunDelegateAttributeName , del . get ( ) ) ;
2018-10-28 23:30:49 +01:00
}
}
2019-02-17 12:20:52 +01:00
last = i . first ;
2018-10-28 23:30:49 +01:00
}
2019-09-01 15:27:39 +02:00
CFAttributedStringEndEditing ( str . get ( ) ) ;
2018-10-28 23:30:49 +01:00
/* Create and return typesetter for the string. */
2019-09-01 15:27:39 +02:00
CFAutoRelease < CTTypesetterRef > typesetter ( CTTypesetterCreateWithAttributedString ( str . get ( ) ) ) ;
2018-10-28 23:30:49 +01:00
2019-09-01 15:27:39 +02:00
return typesetter ? new CoreTextParagraphLayout ( std : : move ( typesetter ) , buff , length , fontMapping ) : nullptr ;
2018-10-28 23:30:49 +01:00
}
2019-04-02 21:31:10 +02:00
/* virtual */ std : : unique_ptr < const ParagraphLayouter : : Line > CoreTextParagraphLayout : : NextLine ( int max_width )
2018-10-28 23:30:49 +01:00
{
2019-04-10 23:07:06 +02:00
if ( this - > cur_offset > = this - > length ) return nullptr ;
2018-10-28 23:30:49 +01:00
/* Get line break position, trying word breaking first and breaking somewhere if that doesn't work. */
2019-09-01 15:27:39 +02:00
CFIndex len = CTTypesetterSuggestLineBreak ( this - > typesetter . get ( ) , this - > cur_offset , max_width ) ;
if ( len < = 0 ) len = CTTypesetterSuggestClusterBreak ( this - > typesetter . get ( ) , this - > cur_offset , max_width ) ;
2018-10-28 23:30:49 +01:00
/* Create line. */
2019-09-01 15:27:39 +02:00
CFAutoRelease < CTLineRef > line ( CTTypesetterCreateLine ( this - > typesetter . get ( ) , CFRangeMake ( this - > cur_offset , len ) ) ) ;
2018-10-28 23:30:49 +01:00
this - > cur_offset + = len ;
2019-09-01 15:27:39 +02:00
return std : : unique_ptr < const Line > ( line ? new CoreTextLine ( std : : move ( line ) , this - > font_map , this - > text_buffer ) : nullptr ) ;
2018-10-28 23:30:49 +01:00
}
CoreTextParagraphLayout : : CoreTextVisualRun : : CoreTextVisualRun ( CTRunRef run , Font * font , const CoreTextParagraphLayoutFactory : : CharType * buff ) : font ( font )
{
this - > glyphs . resize ( CTRunGetGlyphCount ( run ) ) ;
/* Query map of glyphs to source string index. */
CFIndex map [ this - > glyphs . size ( ) ] ;
CTRunGetStringIndices ( run , CFRangeMake ( 0 , 0 ) , map ) ;
this - > glyph_to_char . resize ( this - > glyphs . size ( ) ) ;
for ( size_t i = 0 ; i < this - > glyph_to_char . size ( ) ; i + + ) this - > glyph_to_char [ i ] = ( int ) map [ i ] ;
CGPoint pts [ this - > glyphs . size ( ) ] ;
CTRunGetPositions ( run , CFRangeMake ( 0 , 0 ) , pts ) ;
2024-01-18 22:06:43 +01:00
this - > positions . reserve ( this - > glyphs . size ( ) + 1 ) ;
2018-10-28 23:30:49 +01:00
/* Convert glyph array to our data type. At the same time, substitute
* the proper glyphs for our private sprite glyphs . */
CGGlyph gl [ this - > glyphs . size ( ) ] ;
CTRunGetGlyphs ( run , CFRangeMake ( 0 , 0 ) , gl ) ;
for ( size_t i = 0 ; i < this - > glyphs . size ( ) ; i + + ) {
2023-12-16 02:30:12 +01:00
if ( buff [ this - > glyph_to_char [ i ] ] > = SCC_SPRITE_START & & buff [ this - > glyph_to_char [ i ] ] < = SCC_SPRITE_END & & ( gl [ i ] = = 0 | | gl [ i ] = = 3 ) ) {
/* A glyph of 0 indidicates not found, while apparently 3 is what char 0xFFFC maps to. */
2018-10-28 23:30:49 +01:00
this - > glyphs [ i ] = font - > fc - > MapCharToGlyph ( buff [ this - > glyph_to_char [ i ] ] ) ;
2024-01-18 22:06:43 +01:00
this - > positions . emplace_back ( pts [ i ] . x , ( font - > fc - > GetHeight ( ) - ScaleSpriteTrad ( FontCache : : GetDefaultFontHeight ( font - > fc - > GetSize ( ) ) ) ) / 2 ) ; // Align sprite font to centre
2018-10-28 23:30:49 +01:00
} else {
this - > glyphs [ i ] = gl [ i ] ;
2024-01-18 22:06:43 +01:00
this - > positions . emplace_back ( pts [ i ] . x , pts [ i ] . y ) ;
2018-10-28 23:30:49 +01:00
}
}
2023-02-18 11:50:20 +01:00
this - > total_advance = ( int ) std : : ceil ( CTRunGetTypographicBounds ( run , CFRangeMake ( 0 , 0 ) , nullptr , nullptr , nullptr ) ) ;
2024-01-18 22:06:43 +01:00
/* End-of-run position. */
this - > positions . emplace_back ( this - > positions . front ( ) . x + this - > total_advance , 0 ) ;
2018-10-28 23:30:49 +01:00
}
/**
* Get the height of the line .
* @ return The maximum height of the line .
*/
int CoreTextParagraphLayout : : CoreTextLine : : GetLeading ( ) const
{
int leading = 0 ;
2019-04-02 21:31:10 +02:00
for ( const auto & run : * this ) {
2021-01-08 11:16:18 +01:00
leading = std : : max ( leading , run . GetLeading ( ) ) ;
2018-10-28 23:30:49 +01:00
}
return leading ;
}
/**
* Get the width of this line .
* @ return The width of the line .
*/
int CoreTextParagraphLayout : : CoreTextLine : : GetWidth ( ) const
{
2023-10-20 20:09:58 +02:00
if ( this - > empty ( ) ) return 0 ;
2018-10-28 23:30:49 +01:00
int total_width = 0 ;
2019-04-02 21:31:10 +02:00
for ( const auto & run : * this ) {
total_width + = run . GetAdvance ( ) ;
2018-10-28 23:30:49 +01:00
}
return total_width ;
}
/** Delete CoreText font reference for a specific font size. */
void MacOSResetScriptCache ( FontSize size )
{
2019-09-01 15:27:39 +02:00
_font_cache [ size ] . reset ( ) ;
2018-10-28 23:30:49 +01:00
}
2018-09-23 19:26:09 +02:00
2021-01-02 23:28:45 +01:00
/** Register an external font file with the CoreText system. */
void MacOSRegisterExternalFont ( const char * file_path )
{
if ( ! MacOSVersionIsAtLeast ( 10 , 6 , 0 ) ) return ;
CFAutoRelease < CFStringRef > path ( CFStringCreateWithCString ( kCFAllocatorDefault , file_path , kCFStringEncodingUTF8 ) ) ;
CFAutoRelease < CFURLRef > url ( CFURLCreateWithFileSystemPath ( kCFAllocatorDefault , path . get ( ) , kCFURLPOSIXPathStyle , false ) ) ;
CTFontManagerRegisterFontsForURL ( url . get ( ) , kCTFontManagerScopeProcess , nullptr ) ;
}
2021-02-13 22:51:18 +01:00
/** Store current language locale as a CoreFoundation locale. */
2018-09-23 19:26:09 +02:00
void MacOSSetCurrentLocaleName ( const char * iso_code )
{
if ( ! MacOSVersionIsAtLeast ( 10 , 5 , 0 ) ) return ;
2019-09-01 15:27:39 +02:00
CFAutoRelease < CFStringRef > iso ( CFStringCreateWithCString ( kCFAllocatorDefault , iso_code , kCFStringEncodingUTF8 ) ) ;
_osx_locale . reset ( CFLocaleCreate ( kCFAllocatorDefault , iso . get ( ) ) ) ;
2018-09-23 19:26:09 +02:00
}
/**
* Compares two strings using case insensitive natural sort .
*
* @ param s1 First string to compare .
* @ param s2 Second string to compare .
* @ return 1 if s1 < s2 , 2 if s1 = = s2 , 3 if s1 > s2 , or 0 if not supported by the OS .
*/
2023-04-27 15:39:10 +02:00
int MacOSStringCompare ( std : : string_view s1 , std : : string_view s2 )
2018-09-23 19:26:09 +02:00
{
static bool supported = MacOSVersionIsAtLeast ( 10 , 5 , 0 ) ;
if ( ! supported ) return 0 ;
CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering ;
2023-04-27 15:39:10 +02:00
CFAutoRelease < CFStringRef > cf1 ( CFStringCreateWithBytes ( kCFAllocatorDefault , ( const UInt8 * ) s1 . data ( ) , s1 . size ( ) , kCFStringEncodingUTF8 , false ) ) ;
CFAutoRelease < CFStringRef > cf2 ( CFStringCreateWithBytes ( kCFAllocatorDefault , ( const UInt8 * ) s2 . data ( ) , s2 . size ( ) , kCFStringEncodingUTF8 , false ) ) ;
2018-09-23 19:26:09 +02:00
2019-08-26 00:01:33 +02:00
/* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
2019-09-01 15:27:39 +02:00
if ( cf1 = = nullptr | | cf2 = = nullptr ) return 0 ;
2018-09-23 19:26:09 +02:00
2019-09-01 15:27:39 +02:00
return ( int ) CFStringCompareWithOptionsAndLocale ( cf1 . get ( ) , cf2 . get ( ) , CFRangeMake ( 0 , CFStringGetLength ( cf1 . get ( ) ) ) , flags , _osx_locale . get ( ) ) + 2 ;
2018-09-23 19:26:09 +02:00
}
2023-11-03 20:43:18 +01:00
/**
* Search if a string is contained in another string using the current locale .
*
* @ param str String to search in .
* @ param value String to search for .
* @ param case_insensitive Search case - insensitive .
* @ return 1 if value was found , 0 if it was not found , or - 1 if not supported by the OS .
*/
int MacOSStringContains ( const std : : string_view str , const std : : string_view value , bool case_insensitive )
{
static bool supported = MacOSVersionIsAtLeast ( 10 , 5 , 0 ) ;
if ( ! supported ) return - 1 ;
CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive ;
if ( case_insensitive ) flags | = kCFCompareCaseInsensitive ;
CFAutoRelease < CFStringRef > cf_str ( CFStringCreateWithBytes ( kCFAllocatorDefault , ( const UInt8 * ) str . data ( ) , str . size ( ) , kCFStringEncodingUTF8 , false ) ) ;
CFAutoRelease < CFStringRef > cf_value ( CFStringCreateWithBytes ( kCFAllocatorDefault , ( const UInt8 * ) value . data ( ) , value . size ( ) , kCFStringEncodingUTF8 , false ) ) ;
/* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
if ( cf_str = = nullptr | | cf_value = = nullptr ) return - 1 ;
return CFStringFindWithOptionsAndLocale ( cf_str . get ( ) , cf_value . get ( ) , CFRangeMake ( 0 , CFStringGetLength ( cf_str . get ( ) ) ) , flags , _osx_locale . get ( ) , nullptr ) ? 1 : 0 ;
}
2018-10-21 18:40:39 +02:00
/* virtual */ void OSXStringIterator : : SetString ( const char * s )
{
const char * string_base = s ;
this - > utf16_to_utf8 . clear ( ) ;
this - > str_info . clear ( ) ;
this - > cur_pos = 0 ;
/* CoreText operates on UTF-16, thus we have to convert the input string.
* To be able to return proper offsets , we have to create a mapping at the same time . */
std : : vector < UniChar > utf16_str ; ///< UTF-16 copy of the string.
while ( * s ! = ' \0 ' ) {
size_t idx = s - string_base ;
2023-05-08 19:01:06 +02:00
char32_t c = Utf8Consume ( & s ) ;
2018-10-21 18:40:39 +02:00
if ( c < 0x10000 ) {
utf16_str . push_back ( ( UniChar ) c ) ;
} else {
/* Make a surrogate pair. */
utf16_str . push_back ( ( UniChar ) ( 0xD800 + ( ( c - 0x10000 ) > > 10 ) ) ) ;
utf16_str . push_back ( ( UniChar ) ( 0xDC00 + ( ( c - 0x10000 ) & 0x3FF ) ) ) ;
this - > utf16_to_utf8 . push_back ( idx ) ;
}
this - > utf16_to_utf8 . push_back ( idx ) ;
}
this - > utf16_to_utf8 . push_back ( s - string_base ) ;
/* Query CoreText for word and cluster break information. */
this - > str_info . resize ( utf16_to_utf8 . size ( ) ) ;
2023-10-20 20:18:31 +02:00
if ( ! utf16_str . empty ( ) ) {
2019-09-01 15:27:39 +02:00
CFAutoRelease < CFStringRef > str ( CFStringCreateWithCharactersNoCopy ( kCFAllocatorDefault , & utf16_str [ 0 ] , utf16_str . size ( ) , kCFAllocatorNull ) ) ;
2018-10-21 18:40:39 +02:00
/* Get cluster breaks. */
2019-09-01 15:27:39 +02:00
for ( CFIndex i = 0 ; i < CFStringGetLength ( str . get ( ) ) ; ) {
CFRange r = CFStringGetRangeOfComposedCharactersAtIndex ( str . get ( ) , i ) ;
2018-10-21 18:40:39 +02:00
this - > str_info [ r . location ] . char_stop = true ;
i + = r . length ;
}
/* Get word breaks. */
2019-09-01 15:27:39 +02:00
CFAutoRelease < CFStringTokenizerRef > tokenizer ( CFStringTokenizerCreate ( kCFAllocatorDefault , str . get ( ) , CFRangeMake ( 0 , CFStringGetLength ( str . get ( ) ) ) , kCFStringTokenizerUnitWordBoundary , _osx_locale . get ( ) ) ) ;
2018-10-21 18:40:39 +02:00
CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone ;
2019-09-01 15:27:39 +02:00
while ( ( tokenType = CFStringTokenizerAdvanceToNextToken ( tokenizer . get ( ) ) ) ! = kCFStringTokenizerTokenNone ) {
2018-10-21 18:40:39 +02:00
/* Skip tokens that are white-space or punctuation tokens. */
if ( ( tokenType & kCFStringTokenizerTokenHasNonLettersMask ) ! = kCFStringTokenizerTokenHasNonLettersMask ) {
2019-09-01 15:27:39 +02:00
CFRange r = CFStringTokenizerGetCurrentTokenRange ( tokenizer . get ( ) ) ;
2018-10-21 18:40:39 +02:00
this - > str_info [ r . location ] . word_stop = true ;
}
}
}
/* End-of-string is always a valid stopping point. */
this - > str_info . back ( ) . char_stop = true ;
this - > str_info . back ( ) . word_stop = true ;
}
/* virtual */ size_t OSXStringIterator : : SetCurPosition ( size_t pos )
{
/* Convert incoming position to an UTF-16 string index. */
size_t utf16_pos = 0 ;
for ( size_t i = 0 ; i < this - > utf16_to_utf8 . size ( ) ; i + + ) {
if ( this - > utf16_to_utf8 [ i ] = = pos ) {
utf16_pos = i ;
break ;
}
}
/* Sanitize in case we get a position inside a grapheme cluster. */
while ( utf16_pos > 0 & & ! this - > str_info [ utf16_pos ] . char_stop ) utf16_pos - - ;
this - > cur_pos = utf16_pos ;
return this - > utf16_to_utf8 [ this - > cur_pos ] ;
}
/* virtual */ size_t OSXStringIterator : : Next ( IterType what )
{
assert ( this - > cur_pos < = this - > utf16_to_utf8 . size ( ) ) ;
assert ( what = = StringIterator : : ITER_CHARACTER | | what = = StringIterator : : ITER_WORD ) ;
if ( this - > cur_pos = = this - > utf16_to_utf8 . size ( ) ) return END ;
do {
this - > cur_pos + + ;
} while ( this - > cur_pos < this - > utf16_to_utf8 . size ( ) & & ( what = = ITER_WORD ? ! this - > str_info [ this - > cur_pos ] . word_stop : ! this - > str_info [ this - > cur_pos ] . char_stop ) ) ;
return this - > cur_pos = = this - > utf16_to_utf8 . size ( ) ? END : this - > utf16_to_utf8 [ this - > cur_pos ] ;
}
/* virtual */ size_t OSXStringIterator : : Prev ( IterType what )
{
assert ( this - > cur_pos < = this - > utf16_to_utf8 . size ( ) ) ;
assert ( what = = StringIterator : : ITER_CHARACTER | | what = = StringIterator : : ITER_WORD ) ;
if ( this - > cur_pos = = 0 ) return END ;
do {
this - > cur_pos - - ;
} while ( this - > cur_pos > 0 & & ( what = = ITER_WORD ? ! this - > str_info [ this - > cur_pos ] . word_stop : ! this - > str_info [ this - > cur_pos ] . char_stop ) ) ;
return this - > utf16_to_utf8 [ this - > cur_pos ] ;
}
2023-01-13 17:55:43 +01:00
/* static */ std : : unique_ptr < StringIterator > OSXStringIterator : : Create ( )
2018-10-21 18:40:39 +02:00
{
2019-04-10 23:07:06 +02:00
if ( ! MacOSVersionIsAtLeast ( 10 , 5 , 0 ) ) return nullptr ;
2018-10-21 18:40:39 +02:00
2023-01-13 17:55:43 +01:00
return std : : make_unique < OSXStringIterator > ( ) ;
2018-10-21 18:40:39 +02:00
}