Fix STR #2530 (Mac OS only). Implements a fast algorithm for fl_width() that memorizes the
width of all characters the first time they are seen and computes the width of a string as the sum of the widths of its characters. Char widths are memorized in 256 blocks of 256 widths; only blocks used in some text are allocated and computed. The width of characters beyond U+FFFF is computed anew each time. Strings are drawn using core text, after having deactivated character kerning, so their width is the sum of the widths of their characters. This is the same algorithm as used for WIN32. git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@8305 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
This commit is contained in:
parent
1b146a4837
commit
f9363c16d0
@ -63,11 +63,11 @@ public:
|
|||||||
ATSUTextLayout layout;
|
ATSUTextLayout layout;
|
||||||
# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
CTFontRef fontref;
|
CTFontRef fontref;
|
||||||
|
// the unicode span is divided in 256 blocks of 256 characters
|
||||||
|
float *width[256]; // array of arrays of character widths
|
||||||
# endif
|
# endif
|
||||||
ATSUStyle style;
|
ATSUStyle style;
|
||||||
short ascent, descent, q_width;
|
short ascent, descent, q_width;
|
||||||
// short width[256];
|
|
||||||
// bool knowWidths;
|
|
||||||
char *q_name;
|
char *q_name;
|
||||||
int size;
|
int size;
|
||||||
# elif USE_XFT
|
# elif USE_XFT
|
||||||
|
@ -34,6 +34,7 @@ extern unsigned fl_utf8toUtf16(const char* src, unsigned srclen, unsigned short*
|
|||||||
#define check_default_font() {if (!fl_fontsize) fl_font(0, 12);}
|
#define check_default_font() {if (!fl_fontsize) fl_font(0, 12);}
|
||||||
|
|
||||||
static CGAffineTransform font_mx = { 1, 0, 0, -1, 0, 0 };
|
static CGAffineTransform font_mx = { 1, 0, 0, -1, 0, 0 };
|
||||||
|
static CFMutableDictionaryRef attributes = NULL;
|
||||||
|
|
||||||
Fl_Font_Descriptor::Fl_Font_Descriptor(const char* name, Fl_Fontsize Size) {
|
Fl_Font_Descriptor::Fl_Font_Descriptor(const char* name, Fl_Fontsize Size) {
|
||||||
next = 0;
|
next = 0;
|
||||||
@ -59,7 +60,7 @@ if (fl_mac_os_version >= 0x1050) {//unfortunately, CTFontCreateWithName != NULL
|
|||||||
CTFontGetAdvancesForGlyphs(fontref, kCTFontHorizontalOrientation, glyph, advances, 2);
|
CTFontGetAdvancesForGlyphs(fontref, kCTFontHorizontalOrientation, glyph, advances, 2);
|
||||||
w = advances[0].width;
|
w = advances[0].width;
|
||||||
if ( abs(advances[0].width - advances[1].width) < 1E-2 ) {//this is a fixed-width font
|
if ( abs(advances[0].width - advances[1].width) < 1E-2 ) {//this is a fixed-width font
|
||||||
//slightly rescale fixed-width fonts so the character width has an integral value
|
// slightly rescale fixed-width fonts so the character width has an integral value
|
||||||
CFRelease(fontref);
|
CFRelease(fontref);
|
||||||
CGFloat fsize = size / ( w/floor(w + 0.5) );
|
CGFloat fsize = size / ( w/floor(w + 0.5) );
|
||||||
fontref = CTFontCreateWithName(str, fsize, NULL);
|
fontref = CTFontCreateWithName(str, fsize, NULL);
|
||||||
@ -69,7 +70,20 @@ if (fl_mac_os_version >= 0x1050) {//unfortunately, CTFontCreateWithName != NULL
|
|||||||
ascent = (short)(CTFontGetAscent(fontref) + 0.5);
|
ascent = (short)(CTFontGetAscent(fontref) + 0.5);
|
||||||
descent = (short)(CTFontGetDescent(fontref) + 0.5);
|
descent = (short)(CTFontGetDescent(fontref) + 0.5);
|
||||||
q_width = w + 0.5;
|
q_width = w + 0.5;
|
||||||
|
for (int i = 0; i < 256; i++) width[i] = NULL;
|
||||||
|
if (!attributes) {
|
||||||
|
static CFNumberRef zero_ref;
|
||||||
|
float zero = 0.;
|
||||||
|
zero_ref = CFNumberCreate(NULL, kCFNumberFloat32Type, &zero);
|
||||||
|
// deactivate kerning for all fonts, so that string width = sum of character widths
|
||||||
|
// which allows fast fl_width() implementation.
|
||||||
|
attributes = CFDictionaryCreateMutable(kCFAllocatorDefault,
|
||||||
|
3,
|
||||||
|
&kCFTypeDictionaryKeyCallBacks,
|
||||||
|
&kCFTypeDictionaryValueCallBacks);
|
||||||
|
CFDictionarySetValue (attributes, kCTKernAttributeName, zero_ref);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
#endif
|
#endif
|
||||||
#if ! __LP64__
|
#if ! __LP64__
|
||||||
@ -165,10 +179,12 @@ Fl_Font_Descriptor::~Fl_Font_Descriptor() {
|
|||||||
*/
|
*/
|
||||||
if (this == fl_fontsize) fl_fontsize = 0;
|
if (this == fl_fontsize) fl_fontsize = 0;
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
if (fl_mac_os_version >= 0x1050) CFRelease(fontref);
|
if (fl_mac_os_version >= 0x1050) {
|
||||||
#else
|
CFRelease(fontref);
|
||||||
/* ATSUDisposeTextLayout(layout);
|
for (int i = 0; i < 256; i++) {
|
||||||
ATSUDisposeStyle(style); */
|
if (width[i]) free(width[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +276,24 @@ int fl_descent() {
|
|||||||
else return -1;
|
else return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
double fl_width(const UniChar* txt, int n) {
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
|
// returns width of a pair of UniChar's in the surrogate range
|
||||||
|
static CGFloat surrogate_width(const UniChar *txt)
|
||||||
|
{
|
||||||
|
CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, txt, 2, kCFAllocatorNull);
|
||||||
|
CTFontRef font2 = CTFontCreateForString(fl_fontsize->fontref, str, CFRangeMake(0,2));
|
||||||
|
CFRelease(str);
|
||||||
|
CGGlyph glyphs[2];
|
||||||
|
bool b = CTFontGetGlyphsForCharacters(font2, txt, glyphs, 2);
|
||||||
|
CGSize a;
|
||||||
|
if (b) CTFontGetAdvancesForGlyphs(font2, kCTFontHorizontalOrientation, glyphs, &a, 1);
|
||||||
|
else a.width = fl_fontsize->q_width;
|
||||||
|
CFRelease(font2);
|
||||||
|
return a.width;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static double fl_width(const UniChar* txt, int n) {
|
||||||
check_default_font();
|
check_default_font();
|
||||||
if (!fl_fontsize) {
|
if (!fl_fontsize) {
|
||||||
check_default_font(); // avoid a crash!
|
check_default_font(); // avoid a crash!
|
||||||
@ -269,38 +302,68 @@ double fl_width(const UniChar* txt, int n) {
|
|||||||
}
|
}
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
if (fl_mac_os_version >= 0x1050) {
|
if (fl_mac_os_version >= 0x1050) {
|
||||||
CTFontRef fontref = fl_fontsize->fontref;
|
double retval = 0;
|
||||||
CFStringRef str = CFStringCreateWithBytes(NULL, (const UInt8*)txt, n * sizeof(UniChar), kCFStringEncodingUTF16, false);
|
UniChar uni;
|
||||||
CFAttributedStringRef astr = CFAttributedStringCreate(NULL, str, NULL);
|
int i;
|
||||||
CFMutableAttributedStringRef mastr = CFAttributedStringCreateMutableCopy(NULL, 0, astr);
|
for (i = 0; i < n; i++) { // loop over txt
|
||||||
CFRelease(astr);
|
uni = txt[i];
|
||||||
CFAttributedStringSetAttribute(mastr, CFRangeMake(0, CFStringGetLength(str)), kCTFontAttributeName, fontref);
|
if (uni >= 0xD800 && uni <= 0xDBFF) { // handles the surrogate range
|
||||||
CFRelease(str);
|
retval += surrogate_width(txt + i);
|
||||||
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
|
i++; // because a pair of UniChar's represent a single character
|
||||||
CFRelease(mastr);
|
continue;
|
||||||
double retval = CTLineGetTypographicBounds(ctline, NULL, NULL, NULL);
|
}
|
||||||
CFRelease(ctline);
|
unsigned int r = uni >> 8; // index of the character block containing uni
|
||||||
return retval;
|
if (!fl_fontsize->width[r]) { // this character block has not been hit yet
|
||||||
|
//fprintf(stderr,"r=%d size=%d name=%s\n",r,fl_fontsize->size, fl_fontsize->q_name);
|
||||||
|
// allocate memory to hold width of each character in the block
|
||||||
|
fl_fontsize->width[r] = (float*) malloc(sizeof(float) * 0x100);
|
||||||
|
UniChar ii = r * 0x100;
|
||||||
|
CGGlyph glyph;
|
||||||
|
CGSize advance_size;
|
||||||
|
for (int j = 0; j < 0x100; j++) { // loop over the block
|
||||||
|
CTFontRef font2 = fl_fontsize->fontref;
|
||||||
|
bool must_release = false;
|
||||||
|
// ii spans all characters of this block
|
||||||
|
bool b = CTFontGetGlyphsForCharacters(font2, &ii, &glyph, 1);
|
||||||
|
if (!b) { // the current font doesn't contain this char
|
||||||
|
CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, &ii, 1, kCFAllocatorNull);
|
||||||
|
// find a font that contains it
|
||||||
|
font2 = CTFontCreateForString(font2, str, CFRangeMake(0,1));
|
||||||
|
must_release = true;
|
||||||
|
CFRelease(str);
|
||||||
|
b = CTFontGetGlyphsForCharacters(font2, &ii, &glyph, 1);
|
||||||
|
}
|
||||||
|
if (b) CTFontGetAdvancesForGlyphs(font2, kCTFontHorizontalOrientation, &glyph, &advance_size, 1);
|
||||||
|
else advance_size.width = 0.;
|
||||||
|
// the width of one character of this block of characters
|
||||||
|
fl_fontsize->width[r][j] = advance_size.width;
|
||||||
|
if (must_release) CFRelease(font2);
|
||||||
|
ii++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sum the widths of all characters of txt
|
||||||
|
retval += fl_fontsize->width[r][uni & 0xFF];
|
||||||
}
|
}
|
||||||
else {
|
return retval;
|
||||||
|
} else {
|
||||||
#endif
|
#endif
|
||||||
#if ! __LP64__
|
#if ! __LP64__
|
||||||
OSStatus err;
|
OSStatus err;
|
||||||
Fixed bBefore, bAfter, bAscent, bDescent;
|
Fixed bBefore, bAfter, bAscent, bDescent;
|
||||||
ATSUTextLayout layout;
|
ATSUTextLayout layout;
|
||||||
ByteCount iSize;
|
ByteCount iSize;
|
||||||
ATSUAttributeTag iTag;
|
ATSUAttributeTag iTag;
|
||||||
ATSUAttributeValuePtr iValuePtr;
|
ATSUAttributeValuePtr iValuePtr;
|
||||||
|
|
||||||
// Here's my ATSU text measuring attempt... This seems to do the Right Thing
|
// Here's my ATSU text measuring attempt... This seems to do the Right Thing
|
||||||
// now collect our ATSU resources and measure our text string
|
// now collect our ATSU resources and measure our text string
|
||||||
layout = fl_fontsize->layout;
|
layout = fl_fontsize->layout;
|
||||||
// activate the current GC
|
// activate the current GC
|
||||||
iSize = sizeof(CGContextRef);
|
iSize = sizeof(CGContextRef);
|
||||||
iTag = kATSUCGContextTag;
|
iTag = kATSUCGContextTag;
|
||||||
iValuePtr = &fl_gc;
|
iValuePtr = &fl_gc;
|
||||||
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
|
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
|
||||||
// now measure the bounding box
|
// now measure the bounding box
|
||||||
err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n);
|
err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n);
|
||||||
err = ATSUGetUnjustifiedBounds(layout, kATSUFromTextBeginning, n, &bBefore, &bAfter, &bAscent, &bDescent);
|
err = ATSUGetUnjustifiedBounds(layout, kATSUFromTextBeginning, n, &bBefore, &bAfter, &bAscent, &bDescent);
|
||||||
// If err is OK then return length, else return 0. Or something...
|
// If err is OK then return length, else return 0. Or something...
|
||||||
@ -310,7 +373,7 @@ else {
|
|||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return 0; // FIXME: I do not understand the shuffeling of the above ifdef's and why they are here!
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double fl_width(const char* txt, int n) {
|
double fl_width(const char* txt, int n) {
|
||||||
@ -319,17 +382,13 @@ double fl_width(const char* txt, int n) {
|
|||||||
return fl_width(uniStr, wc_len);
|
return fl_width(uniStr, wc_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*double fl_width(uchar c) {
|
|
||||||
return fl_width((const char*)(&c), 1);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
double fl_width(unsigned int wc) {
|
double fl_width(unsigned int wc) {
|
||||||
UniChar uc = wc;
|
const UniChar uc = wc;
|
||||||
return fl_width(&uc, 1);
|
return fl_width(&uc, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// text extent calculation
|
// text extent calculation
|
||||||
void fl_text_extents(const UniChar* txt, int n, int &dx, int &dy, int &w, int &h) {
|
void fl_text_extents(const char *str8, int n, int &dx, int &dy, int &w, int &h) {
|
||||||
if (!fl_fontsize) {
|
if (!fl_fontsize) {
|
||||||
check_default_font(); // avoid a crash!
|
check_default_font(); // avoid a crash!
|
||||||
if (!fl_fontsize)
|
if (!fl_fontsize)
|
||||||
@ -339,12 +398,9 @@ void fl_text_extents(const UniChar* txt, int n, int &dx, int &dy, int &w, int &h
|
|||||||
}
|
}
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
if (fl_mac_os_version >= 0x1050) {
|
if (fl_mac_os_version >= 0x1050) {
|
||||||
CTFontRef fontref = fl_fontsize->fontref;
|
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)str8, n, kCFStringEncodingUTF8, false);
|
||||||
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)txt, n *sizeof(UniChar), kCFStringEncodingUTF16, false);
|
CFDictionarySetValue (attributes, kCTFontAttributeName, fl_fontsize->fontref);
|
||||||
CFAttributedStringRef astr = CFAttributedStringCreate(NULL, str16, NULL);
|
CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes);
|
||||||
CFMutableAttributedStringRef mastr = CFAttributedStringCreateMutableCopy(NULL, 0, astr);
|
|
||||||
CFRelease(astr);
|
|
||||||
CFAttributedStringSetAttribute(mastr, CFRangeMake(0, CFStringGetLength(str16)), kCTFontAttributeName, fontref);
|
|
||||||
CFRelease(str16);
|
CFRelease(str16);
|
||||||
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
|
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
|
||||||
CFRelease(mastr);
|
CFRelease(mastr);
|
||||||
@ -376,6 +432,7 @@ else {
|
|||||||
iValuePtr = &fl_gc;
|
iValuePtr = &fl_gc;
|
||||||
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
|
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
|
||||||
// now measure the bounding box
|
// now measure the bounding box
|
||||||
|
UniChar *txt = mac_Utf8_to_Utf16(str8, n, &n);
|
||||||
err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n);
|
err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n);
|
||||||
Rect bbox;
|
Rect bbox;
|
||||||
err = ATSUMeasureTextImage(layout, kATSUFromTextBeginning, n, 0, 0, &bbox);
|
err = ATSUMeasureTextImage(layout, kATSUFromTextBeginning, n, 0, 0, &bbox);
|
||||||
@ -391,12 +448,6 @@ else {
|
|||||||
return;
|
return;
|
||||||
} // fl_text_extents
|
} // fl_text_extents
|
||||||
|
|
||||||
void fl_text_extents(const char *c, int n, int &dx, int &dy, int &w, int &h) {
|
|
||||||
int wc_len = n;
|
|
||||||
UniChar *uniStr = mac_Utf8_to_Utf16(c, n, &wc_len);
|
|
||||||
fl_text_extents(uniStr, wc_len, dx, dy, w, h);
|
|
||||||
} // fl_text_extents
|
|
||||||
|
|
||||||
|
|
||||||
void fl_draw(const char *str, int n, float x, float y);
|
void fl_draw(const char *str, int n, float x, float y);
|
||||||
|
|
||||||
@ -423,27 +474,14 @@ static CGColorRef flcolortocgcolor(Fl_Color i)
|
|||||||
void fl_draw(const char *str, int n, float x, float y) {
|
void fl_draw(const char *str, int n, float x, float y) {
|
||||||
// avoid a crash if no font has been selected by user yet !
|
// avoid a crash if no font has been selected by user yet !
|
||||||
check_default_font();
|
check_default_font();
|
||||||
// convert to UTF-16 first
|
|
||||||
UniChar *uniStr = mac_Utf8_to_Utf16(str, n, &n);
|
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
if (fl_mac_os_version >= 0x1050) {
|
if (fl_mac_os_version >= 0x1050) {
|
||||||
CFStringRef keys[2];
|
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)str, n, kCFStringEncodingUTF8, false);
|
||||||
CFTypeRef values[2];
|
|
||||||
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)uniStr, n * sizeof(UniChar), kCFStringEncodingUTF16, false);
|
|
||||||
CGColorRef color = flcolortocgcolor(fl_color());
|
CGColorRef color = flcolortocgcolor(fl_color());
|
||||||
keys[0] = kCTFontAttributeName;
|
CFDictionarySetValue (attributes, kCTFontAttributeName, fl_fontsize->fontref);
|
||||||
keys[1] = kCTForegroundColorAttributeName;
|
CFDictionarySetValue (attributes, kCTForegroundColorAttributeName, color);
|
||||||
values[0] = fl_fontsize->fontref;
|
|
||||||
values[1] = color;
|
|
||||||
CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault,
|
|
||||||
(const void**)&keys,
|
|
||||||
(const void**)&values,
|
|
||||||
2,
|
|
||||||
&kCFTypeDictionaryKeyCallBacks,
|
|
||||||
&kCFTypeDictionaryValueCallBacks);
|
|
||||||
CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes);
|
CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes);
|
||||||
CFRelease(str16);
|
CFRelease(str16);
|
||||||
CFRelease(attributes);
|
|
||||||
CFRelease(color);
|
CFRelease(color);
|
||||||
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
|
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
|
||||||
CFRelease(mastr);
|
CFRelease(mastr);
|
||||||
@ -453,8 +491,7 @@ void fl_draw(const char *str, int n, float x, float y) {
|
|||||||
CTLineDraw(ctline, fl_gc);
|
CTLineDraw(ctline, fl_gc);
|
||||||
CGContextSetShouldAntialias(fl_gc, false);
|
CGContextSetShouldAntialias(fl_gc, false);
|
||||||
CFRelease(ctline);
|
CFRelease(ctline);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
#endif
|
#endif
|
||||||
#if ! __LP64__
|
#if ! __LP64__
|
||||||
OSStatus err;
|
OSStatus err;
|
||||||
@ -466,6 +503,8 @@ void fl_draw(const char *str, int n, float x, float y) {
|
|||||||
ATSUAttributeValuePtr iValuePtr=&fl_gc;
|
ATSUAttributeValuePtr iValuePtr=&fl_gc;
|
||||||
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
|
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
|
||||||
|
|
||||||
|
// convert to UTF-16 first
|
||||||
|
UniChar *uniStr = mac_Utf8_to_Utf16(str, n, &n);
|
||||||
err = ATSUSetTextPointerLocation(layout, uniStr, kATSUFromTextBeginning, n, n);
|
err = ATSUSetTextPointerLocation(layout, uniStr, kATSUFromTextBeginning, n, n);
|
||||||
CGContextSetShouldAntialias(fl_gc, true);
|
CGContextSetShouldAntialias(fl_gc, true);
|
||||||
err = ATSUDrawText(layout, kATSUFromTextBeginning, n, FloatToFixed(x), FloatToFixed(y));
|
err = ATSUDrawText(layout, kATSUFromTextBeginning, n, FloatToFixed(x), FloatToFixed(y));
|
||||||
|
Loading…
Reference in New Issue
Block a user