Changeset View
Changeset View
Standalone View
Standalone View
extern/opennurbs/opennurbs_string_format.cpp
- This file was added.
| /* $NoKeywords: $ */ | |||||
| /* | |||||
| // | |||||
| // Copyright (c) 1993-2014 Robert McNeel & Associates. All rights reserved. | |||||
| // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert | |||||
| // McNeel & Associates. | |||||
| // | |||||
| // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. | |||||
| // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF | |||||
| // MERCHANTABILITY ARE HEREBY DISCLAIMED. | |||||
| // | |||||
| // For complete openNURBS copyright information see <http://www.opennurbs.org>. | |||||
| // | |||||
| //////////////////////////////////////////////////////////////// | |||||
| */ | |||||
| #include "opennurbs.h" | |||||
| #if !defined(ON_COMPILING_OPENNURBS) | |||||
| // This check is included in all opennurbs source .c and .cpp files to insure | |||||
| // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. | |||||
| // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined | |||||
| // and the opennurbs .h files alter what is declared and how it is declared. | |||||
| #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs | |||||
| #endif | |||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
| // | |||||
| // ON_StringBuffer formatting | |||||
| // | |||||
| ON_StringBuffer::ON_StringBuffer() | |||||
| : m_buffer(nullptr) | |||||
| , m_buffer_capacity(0) | |||||
| , m_heap_buffer(nullptr) | |||||
| , m_heap_buffer_capacity(0) | |||||
| {} | |||||
| ON_StringBuffer::ON_StringBuffer( | |||||
| char* stack_buffer, | |||||
| size_t stack_buffer_capacity | |||||
| ) | |||||
| : m_buffer(stack_buffer_capacity > 0 ? stack_buffer : nullptr) | |||||
| , m_buffer_capacity(nullptr == m_buffer ? 0 : stack_buffer_capacity) | |||||
| , m_heap_buffer(nullptr) | |||||
| , m_heap_buffer_capacity(0) | |||||
| {} | |||||
| ON_StringBuffer::~ON_StringBuffer() | |||||
| { | |||||
| m_buffer = nullptr; | |||||
| m_buffer_capacity = 0; | |||||
| if (nullptr != m_heap_buffer) | |||||
| { | |||||
| delete[] m_heap_buffer; | |||||
| m_heap_buffer = nullptr; | |||||
| } | |||||
| m_heap_buffer_capacity = 0; | |||||
| } | |||||
| bool ON_StringBuffer::GrowBuffer( | |||||
| size_t buffer_capacity | |||||
| ) | |||||
| { | |||||
| if (buffer_capacity <= m_buffer_capacity && (nullptr != m_buffer || 0 == m_buffer_capacity)) | |||||
| return true; | |||||
| if (buffer_capacity > m_heap_buffer_capacity || nullptr == m_heap_buffer) | |||||
| { | |||||
| if (nullptr != m_heap_buffer) | |||||
| delete[] m_heap_buffer; | |||||
| m_heap_buffer = new(std::nothrow) char[buffer_capacity]; | |||||
| m_heap_buffer_capacity = (nullptr != m_heap_buffer) ? buffer_capacity : 0; | |||||
| } | |||||
| m_buffer = m_heap_buffer; | |||||
| m_buffer_capacity = m_heap_buffer_capacity; | |||||
| return (buffer_capacity <= m_buffer_capacity); | |||||
| } | |||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
| // | |||||
| // ON_wStringBuffer formatting | |||||
| // | |||||
| ON_wStringBuffer::ON_wStringBuffer() | |||||
| : m_buffer(nullptr) | |||||
| , m_buffer_capacity(0) | |||||
| , m_heap_buffer(nullptr) | |||||
| , m_heap_buffer_capacity(0) | |||||
| {} | |||||
| ON_wStringBuffer::ON_wStringBuffer( | |||||
| wchar_t* stack_buffer, | |||||
| size_t stack_buffer_capacity | |||||
| ) | |||||
| : m_buffer(stack_buffer_capacity > 0 ? stack_buffer : nullptr) | |||||
| , m_buffer_capacity(nullptr == m_buffer ? 0 : stack_buffer_capacity) | |||||
| , m_heap_buffer(nullptr) | |||||
| , m_heap_buffer_capacity(0) | |||||
| {} | |||||
| ON_wStringBuffer::~ON_wStringBuffer() | |||||
| { | |||||
| m_buffer = nullptr; | |||||
| m_buffer_capacity = 0; | |||||
| if (nullptr != m_heap_buffer) | |||||
| { | |||||
| delete[] m_heap_buffer; | |||||
| m_heap_buffer = nullptr; | |||||
| } | |||||
| m_heap_buffer_capacity = 0; | |||||
| } | |||||
| bool ON_wStringBuffer::GrowBuffer( | |||||
| size_t buffer_capacity | |||||
| ) | |||||
| { | |||||
| if (buffer_capacity <= m_buffer_capacity && (nullptr != m_buffer || 0 == m_buffer_capacity)) | |||||
| return true; | |||||
| if (buffer_capacity > m_heap_buffer_capacity || nullptr == m_heap_buffer) | |||||
| { | |||||
| if (nullptr != m_heap_buffer) | |||||
| delete[] m_heap_buffer; | |||||
| m_heap_buffer = new(std::nothrow) wchar_t[buffer_capacity]; | |||||
| m_heap_buffer_capacity = (nullptr != m_heap_buffer) ? buffer_capacity : 0; | |||||
| } | |||||
| m_buffer = m_heap_buffer; | |||||
| m_buffer_capacity = m_heap_buffer_capacity; | |||||
| return (buffer_capacity <= m_buffer_capacity); | |||||
| } | |||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
| // | |||||
| // ON_String::FromNumber number formatting | |||||
| // | |||||
| #define BUFFER_DECL(ctype) ctype _buffer[64]; unsigned int _buffer_index = 63; const ctype _zero('0'); | |||||
| #define UNSIGNED_TO_BUFFER(ctype,n) \ | |||||
| ON__UINT64 _u64 = (ON__UINT64)(n); \ | |||||
| _buffer[_buffer_index] = 0; \ | |||||
| if (0==_u64) {\ | |||||
| _buffer[--_buffer_index] = _zero; \ | |||||
| } else { \ | |||||
| while (_u64 > 0 && _buffer_index > 0) {ctype d = (ctype)(_u64%10); _u64 /= 10; _buffer[--_buffer_index] = _zero + d;} \ | |||||
| } | |||||
| #define SIGNED_TO_BUFFER(ctype,n) \ | |||||
| ON__INT64 _i64 = (ON__INT64)(n); UNSIGNED_TO_BUFFER(ctype,_i64<0?(-_i64):_i64) if (_i64 < 0 && _buffer_index > 0) {_buffer[--_buffer_index] = '-';} | |||||
| #define UNSIGNED_TO_STRING(ctype,stype,n) BUFFER_DECL(ctype) UNSIGNED_TO_BUFFER(ctype,n) return stype(&_buffer[_buffer_index]); | |||||
| #define SIGNED_TO_STRING(ctype,stype,n) BUFFER_DECL(ctype) SIGNED_TO_BUFFER(ctype,n) return stype(&_buffer[_buffer_index]); | |||||
| const ON_String ON_String::FromNumber( | |||||
| char n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| short n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| int n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| ON__INT64 n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| unsigned char n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| unsigned short n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| unsigned int n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| ON__UINT64 n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(char, ON_String, n) | |||||
| } | |||||
| const ON_String ON_String::FromNumber( | |||||
| double d | |||||
| ) | |||||
| { | |||||
| char buffer[64]; | |||||
| if (ON_String::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), "%g", d) > 0) | |||||
| return ON_String(buffer); | |||||
| return ON_String::EmptyString; | |||||
| } | |||||
| const ON_String ON_String::ApproximateFromNumber( | |||||
| double d | |||||
| ) | |||||
| { | |||||
| char buffer[64]; | |||||
| if (0.0 == d || (d == d && fabs(d) >= 1.0e-16 && fabs(d) <= 1.0e16)) | |||||
| { | |||||
| // Do not use %f without making sure the number is in a range | |||||
| // reasonable for %f. Otherwise we end up with number strings | |||||
| // thate are 300 digits long when a 1e300 comes by. | |||||
| if (ON_String::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), "%f", d) > 0) | |||||
| return ON_String(buffer); | |||||
| // It may be that 64 elements were not enought for %f format if the "reasonable range" | |||||
| // test is not good enough. We try again with "%g". | |||||
| } | |||||
| if (ON_String::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), "%g", d) > 0) | |||||
| return ON_String(buffer); | |||||
| return ON_String::EmptyString; | |||||
| } | |||||
| const ON_String ON_String::PreciseFromNumber( | |||||
| double d | |||||
| ) | |||||
| { | |||||
| char buffer[64]; | |||||
| if (ON_String::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), "%.17g", d) > 0) | |||||
| return ON_String(buffer); | |||||
| return ON_String::EmptyString; | |||||
| } | |||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
| // | |||||
| // ON_wString::FromNumber number formatting | |||||
| // | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| char n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| short n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| int n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| ON__INT64 n | |||||
| ) | |||||
| { | |||||
| SIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| unsigned char n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| unsigned short n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| unsigned int n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| ON__UINT64 n | |||||
| ) | |||||
| { | |||||
| UNSIGNED_TO_STRING(wchar_t, ON_wString, n) | |||||
| } | |||||
| const ON_wString ON_wString::FromNumber( | |||||
| double d | |||||
| ) | |||||
| { | |||||
| wchar_t buffer[64]; | |||||
| if (ON_wString::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%g", d) > 0) | |||||
| return ON_wString(buffer); | |||||
| return ON_wString::EmptyString; | |||||
| } | |||||
| const ON_wString ON_wString::ApproximateFromNumber( | |||||
| double d | |||||
| ) | |||||
| { | |||||
| wchar_t buffer[64]; | |||||
| if (0.0 == d || (d == d && fabs(d) >= 1.0e-16 && fabs(d) <= 1.0e16)) | |||||
| { | |||||
| // Do not use %f without making sure the number is in a range | |||||
| // reasonable for %f. Otherwise we end up with number strings | |||||
| // thate are 300 digits long when a 1e300 comes by. | |||||
| if (ON_wString::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%f", d) > 0) | |||||
| return ON_String(buffer); | |||||
| // It may be that 64 elements were not enought for %f format if the "reasonable range" | |||||
| // test is not good enough. We try again with "%g". | |||||
| } | |||||
| if (ON_wString::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%g", d) > 0) | |||||
| return ON_String(buffer); | |||||
| return ON_String::EmptyString; | |||||
| } | |||||
| const ON_wString ON_wString::PreciseFromNumber( | |||||
| double d | |||||
| ) | |||||
| { | |||||
| wchar_t buffer[64]; | |||||
| if (ON_wString::FormatIntoBuffer(buffer, sizeof(buffer) / sizeof(buffer[0]), L"%.17g", d) > 0) | |||||
| return ON_wString(buffer); | |||||
| return ON_wString::EmptyString; | |||||
| } | |||||
| const ON_wString ON_wString::FromCurrentCoordinatedUniversalTime( | |||||
| ON_DateFormat date_format, | |||||
| ON_TimeFormat time_format, | |||||
| wchar_t date_separator, | |||||
| wchar_t date_time_separator, | |||||
| wchar_t time_separator | |||||
| ) | |||||
| { | |||||
| struct tm current_time; | |||||
| memset(¤t_time,0,sizeof(current_time)); | |||||
| time_t gmt = time(0); | |||||
| const struct tm* t = gmtime(&gmt); | |||||
| if ( t ) | |||||
| current_time = *t; | |||||
| return ON_wString::FromTime( | |||||
| current_time, | |||||
| date_format, | |||||
| time_format, | |||||
| date_separator, | |||||
| date_time_separator, | |||||
| time_separator | |||||
| ); | |||||
| } | |||||
| const ON_wString ON_wString::FromSecondsSinceJanuaryFirst1970( | |||||
| ON__UINT64 seconds_since_jan_first_1970, | |||||
| ON_DateFormat date_format, | |||||
| ON_TimeFormat time_format, | |||||
| wchar_t date_separator, | |||||
| wchar_t date_time_separator, | |||||
| wchar_t time_separator | |||||
| ) | |||||
| { | |||||
| struct tm current_time; | |||||
| memset(¤t_time, 0, sizeof(current_time)); | |||||
| time_t uct = (time_t)seconds_since_jan_first_1970; | |||||
| const struct tm* t = gmtime(&uct); | |||||
| if (t) | |||||
| { | |||||
| current_time = *t; | |||||
| } | |||||
| return FromTime(current_time, date_format, time_format, date_separator, date_time_separator, time_separator); | |||||
| } | |||||
| const ON_wString ON_wString::FromTime( | |||||
| const struct tm& t, | |||||
| ON_DateFormat date_format, | |||||
| ON_TimeFormat time_format, | |||||
| wchar_t date_separator, | |||||
| wchar_t date_time_separator, | |||||
| wchar_t time_separator | |||||
| ) | |||||
| { | |||||
| int mday = t.tm_mday; | |||||
| if (mday < 1 || mday > 31) | |||||
| mday = 0; | |||||
| int yday = t.tm_yday; | |||||
| if (yday < 0 || yday > 365) | |||||
| yday = 0; | |||||
| else | |||||
| yday++; | |||||
| int mon = t.tm_mon; | |||||
| if (mon < 0 || mon > 11) | |||||
| mon = 0; | |||||
| else | |||||
| mon++; | |||||
| int year = t.tm_year; | |||||
| if (year < 0) | |||||
| year = 0; | |||||
| else | |||||
| year += 1900; | |||||
| return (mon > 0 && mday > 0) | |||||
| ? ON_wString::FromYearMonthDayHourMinuteSecond( | |||||
| year, mon, mday, | |||||
| (int)t.tm_hour, (int)t.tm_min, (int)t.tm_sec, | |||||
| date_format, | |||||
| time_format, | |||||
| date_separator, | |||||
| date_time_separator, | |||||
| time_separator | |||||
| ) | |||||
| : ON_wString::FromYearDayHourMinuteSecond( | |||||
| year, yday, | |||||
| (int)t.tm_hour, (int)t.tm_min, (int)t.tm_sec, | |||||
| date_format, | |||||
| time_format, | |||||
| date_separator, | |||||
| date_time_separator, | |||||
| time_separator | |||||
| ); | |||||
| } | |||||
| const ON_wString ON_wString::FromYearDayHourMinuteSecond( | |||||
| int year, | |||||
| int day_of_year, | |||||
| int hour, | |||||
| int minute, | |||||
| int second, | |||||
| ON_DateFormat date_format, | |||||
| ON_TimeFormat time_format, | |||||
| wchar_t date_separator, | |||||
| wchar_t date_time_separator, | |||||
| wchar_t time_separator | |||||
| ) | |||||
| { | |||||
| unsigned int month = 0; | |||||
| unsigned int mday = 0; | |||||
| if ( ON_DateFormat::Unset != date_format && ON_DateFormat::Omit != date_format && year > 0 && day_of_year > 0 && day_of_year <= 366 ) | |||||
| { | |||||
| ON_GetGregorianMonthAndDayOfMonth( | |||||
| (unsigned int)year, | |||||
| (unsigned int)day_of_year, | |||||
| &month, | |||||
| &mday | |||||
| ); | |||||
| } | |||||
| return ON_wString::FromYearMonthDayHourMinuteSecond( | |||||
| year, | |||||
| (int)month, | |||||
| (int)mday, | |||||
| hour, | |||||
| minute, | |||||
| second, | |||||
| date_format, | |||||
| time_format, | |||||
| date_separator, | |||||
| date_time_separator, | |||||
| time_separator | |||||
| ); | |||||
| } | |||||
| const ON_wString ON_wString::FromYearMonthDayHourMinuteSecond( | |||||
| int year, | |||||
| int month, | |||||
| int mday, | |||||
| int hour, | |||||
| int minute, | |||||
| int second, | |||||
| ON_DateFormat date_format, | |||||
| ON_TimeFormat time_format, | |||||
| wchar_t date_separator, | |||||
| wchar_t date_time_separator, | |||||
| wchar_t time_separator | |||||
| ) | |||||
| { | |||||
| if (year < 1582) | |||||
| year = 0; | |||||
| if (mday < 1 || mday > 31) | |||||
| mday = 0; | |||||
| if (month < 1 || month > 12) | |||||
| month = 0; | |||||
| const int yday | |||||
| = (ON_DateFormat::YearDayOfYear == date_format) | |||||
| ? ON_DayOfGregorianYear(year, month, mday) | |||||
| : 0; | |||||
| if (0 == date_separator) | |||||
| date_separator = ON_wString::HyphenMinus; | |||||
| if (0 == date_time_separator) | |||||
| date_time_separator = ON_wString::Space; | |||||
| if (0 == time_separator) | |||||
| time_separator = ':'; | |||||
| bool bValidDate | |||||
| = ON_DateFormat::YearDayOfYear == date_format | |||||
| ? (yday > 0) | |||||
| : (month > 0 && mday > 0); | |||||
| bool bValidHMS = (hour >= 0 && minute >= 0 && second >= 0 && hour < 24 && minute <= 59 && second <= 59); | |||||
| const wchar_t ds[2] = { date_separator, 0 }; | |||||
| const wchar_t ts[2] = { time_separator, 0 }; | |||||
| const wchar_t* ampm = (hour >= 12) ? L"PM" : L"AM"; | |||||
| ON_wString date; | |||||
| switch (date_format) | |||||
| { | |||||
| case ON_DateFormat::Unset: | |||||
| bValidDate = true; | |||||
| break; | |||||
| case ON_DateFormat::Omit: | |||||
| bValidDate = true; | |||||
| break; | |||||
| case ON_DateFormat::YearMonthDay: | |||||
| date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, month, ds, mday); | |||||
| break; | |||||
| case ON_DateFormat::YearDayMonth: | |||||
| date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, mday, ds, month); | |||||
| break; | |||||
| case ON_DateFormat::MonthDayYear: | |||||
| date = ON_wString::FormatToString(L"%d%ls%d%ls%d", month, ds, mday, ds, year); | |||||
| break; | |||||
| case ON_DateFormat::DayMonthYear: | |||||
| date = ON_wString::FormatToString(L"%d%ls%d%ls%d", mday, ds, month, ds, year); | |||||
| break; | |||||
| case ON_DateFormat::YearDayOfYear: | |||||
| date = ON_wString::FormatToString(L"%d%ls%d", year, ds, yday); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| if (false == bValidDate) | |||||
| return ON_wString::EmptyString; | |||||
| ON_wString hms; | |||||
| switch (time_format) | |||||
| { | |||||
| case ON_TimeFormat::Unset: | |||||
| bValidHMS = true; | |||||
| break; | |||||
| case ON_TimeFormat::Omit: | |||||
| bValidHMS = true; | |||||
| break; | |||||
| case ON_TimeFormat::HourMinute12: | |||||
| hms = ON_wString::FormatToString(L"%02d%ls%02d%ls", hour%12, ts, minute, ampm); | |||||
| break; | |||||
| case ON_TimeFormat::HourMinuteSecond12: | |||||
| hms = ON_wString::FormatToString(L"%02d%ls%02d%ls%02d%ls", hour%12, ts, minute, ts, second, ampm); | |||||
| break; | |||||
| case ON_TimeFormat::HourMinute24: | |||||
| hms = ON_wString::FormatToString(L"%02d%ls%02d", hour, ts, minute); | |||||
| break; | |||||
| case ON_TimeFormat::HourMinuteSecond24: | |||||
| hms = ON_wString::FormatToString(L"%02d%ls%02d%ls%02d", hour, ts, minute, ts, second); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| ON_wString result = date; | |||||
| if (result.IsNotEmpty() && hms.IsNotEmpty()) | |||||
| { | |||||
| const wchar_t dts[] = { date_time_separator,0 }; | |||||
| result += dts; | |||||
| } | |||||
| result += hms; | |||||
| return result; | |||||
| } | |||||
| static bool ON_BytesToHexadecimalString( | |||||
| const ON__UINT8* bytes, | |||||
| size_t byte_count, | |||||
| bool bCapitalDigits, | |||||
| bool bReverse, | |||||
| char* str, | |||||
| size_t str_capacity | |||||
| ) | |||||
| { | |||||
| if (nullptr == str || str_capacity < 2*byte_count || byte_count <= 0 || nullptr == bytes ) | |||||
| { | |||||
| if (nullptr != str && str_capacity > 0) | |||||
| str[0] = 0; | |||||
| return false; | |||||
| } | |||||
| const int A_minus_10 = (bCapitalDigits ? 'A' : 'a') - 10; | |||||
| size_t j = 0; | |||||
| int c; | |||||
| if ( bReverse ) | |||||
| bytes += 19; | |||||
| const int delta_byte = bReverse ? -1 : 1; | |||||
| for (int i = 0; i < 20; i++) | |||||
| { | |||||
| ON__UINT8 b = *bytes; | |||||
| bytes += delta_byte; | |||||
| c = (int)(b / 16); | |||||
| if (c < 10) | |||||
| c += '0'; | |||||
| else | |||||
| c += A_minus_10; | |||||
| if (j < str_capacity) | |||||
| str[j++] = (char)c; | |||||
| c = (int)(b % 16); | |||||
| if (c < 10) | |||||
| c += '0'; | |||||
| else | |||||
| c += A_minus_10; | |||||
| if (j < str_capacity) | |||||
| str[j++] = (char)c; | |||||
| } | |||||
| if ((size_t)j < str_capacity) | |||||
| str[j] = 0; | |||||
| return true; | |||||
| } | |||||
| const ON_String ON_String::HexadecimalFromBytes( | |||||
| const ON__UINT8* bytes, | |||||
| size_t byte_count, | |||||
| bool bCapitalDigits, | |||||
| bool bReverse | |||||
| ) | |||||
| { | |||||
| if (nullptr == bytes || byte_count <= 0) | |||||
| return ON_String::EmptyString; | |||||
| const size_t s_length = 2 * byte_count; | |||||
| ON_String s; | |||||
| s.ReserveArray(s_length); | |||||
| s.SetLength(s_length); | |||||
| if ( false == ON_BytesToHexadecimalString(bytes,byte_count,bCapitalDigits,bReverse,s.Array(),s_length) ) | |||||
| return ON_String::EmptyString; | |||||
| return s; | |||||
| } | |||||
| const ON_wString ON_wString::HexadecimalFromBytes( | |||||
| const ON__UINT8* bytes, | |||||
| size_t byte_count, | |||||
| bool bCapitalDigits, | |||||
| bool bReverse | |||||
| ) | |||||
| { | |||||
| if (nullptr == bytes || byte_count <= 0) | |||||
| return ON_String::EmptyString; | |||||
| size_t s_length = 2 * byte_count; | |||||
| ON_wString s; | |||||
| s.ReserveArray(s_length); | |||||
| s.SetLength(s_length); | |||||
| wchar_t* wstr = s.Array(); | |||||
| char* str = (char*)wstr; | |||||
| if ( false == ON_BytesToHexadecimalString(bytes,byte_count,bCapitalDigits,bReverse,str,s_length) ) | |||||
| return ON_String::EmptyString; | |||||
| wchar_t* wstr0 = wstr; | |||||
| wstr += s_length; | |||||
| str += s_length; | |||||
| while (wstr > wstr0) | |||||
| { | |||||
| wstr--; | |||||
| str--; | |||||
| *wstr = (wchar_t)*str; // all str[] elements are single byte UTF-8 encodings | |||||
| } | |||||
| return s; | |||||
| } | |||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
| // | |||||
| // ON_String formatting | |||||
| // | |||||
| bool ON_VARGS_FUNC_CDECL ON_String::Format(const char* format, ...) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| bool rc = FormatVargs(format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| bool ON_VARGS_FUNC_CDECL ON_String::Format(const unsigned char* format, ...) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| bool rc = FormatVargs(format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| const ON_String ON_VARGS_FUNC_CDECL ON_String::FormatToString( | |||||
| const char* format, | |||||
| ... | |||||
| ) | |||||
| { | |||||
| ON_StringBuffer buffer; | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| ON_String::FormatVargsIntoBuffer(buffer, format, args); | |||||
| va_end(args); | |||||
| return ON_String(buffer.m_buffer); | |||||
| } | |||||
| bool ON_String::FormatVargs(const char* format, va_list args) | |||||
| { | |||||
| const int len_count = ON_String::FormatVargsOutputCount(format, args); | |||||
| if (len_count > 0) | |||||
| { | |||||
| // temp is used because sometimes "this" is an argument as in: | |||||
| // ON_String str = L"filename.ext"; | |||||
| // str.Format(L"C:\\%s",str); | |||||
| ON_String temp; | |||||
| temp.SetLength(len_count); | |||||
| const int len_string = ON_String::FormatVargsIntoBuffer(temp.Array(), len_count + 1, format, args); | |||||
| if (len_string == len_count) | |||||
| { | |||||
| *this = temp; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| Destroy(); | |||||
| Create(); | |||||
| return (0 == len_count); | |||||
| } | |||||
| bool ON_String::FormatVargs(const unsigned char* format, va_list args) | |||||
| { | |||||
| return FormatVargs((const char*)format, args); | |||||
| } | |||||
| int ON_VARGS_FUNC_CDECL ON_String::FormatIntoBuffer( | |||||
| char* buffer, size_t buffer_capacity, | |||||
| const char* format, | |||||
| ... | |||||
| ) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| int rc = ON_String::FormatVargsIntoBuffer(buffer, buffer_capacity, format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| int ON_VARGS_FUNC_CDECL ON_String::FormatIntoBuffer( | |||||
| ON_StringBuffer& buffer, | |||||
| const char* format, | |||||
| ... | |||||
| ) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| int rc = ON_String::FormatVargsIntoBuffer(buffer, format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| int ON_String::FormatVargsIntoBuffer( | |||||
| char* buffer, | |||||
| size_t buffer_capacity, | |||||
| const char* format, | |||||
| va_list args | |||||
| ) | |||||
| { | |||||
| if (0 == buffer || buffer_capacity <= 0) | |||||
| return -1; | |||||
| buffer[0] = 0; | |||||
| #if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU) | |||||
| // CLang modifies args so a copy is required | |||||
| va_list args_copy; | |||||
| va_copy (args_copy, args); | |||||
| #if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) | |||||
| int len = vsnprintf(buffer, buffer_capacity, format, args_copy); | |||||
| #else | |||||
| int len = vsnprintf_l(buffer, buffer_capacity, ON_Locale::Ordinal.NumericLocalePtr(), format, args_copy); | |||||
| #endif | |||||
| va_end(args_copy); | |||||
| #else | |||||
| int len = _vsprintf_p_l(buffer, buffer_capacity, format, ON_Locale::Ordinal.NumericLocalePtr(), args); | |||||
| #endif | |||||
| if (((size_t)len) >= buffer_capacity) | |||||
| len = -1; | |||||
| buffer[(len <= 0) ? 0 : len] = 0; | |||||
| buffer[buffer_capacity - 1] = 0; | |||||
| return len; | |||||
| } | |||||
| int ON_String::FormatVargsIntoBuffer( | |||||
| ON_StringBuffer& buffer, | |||||
| const char* format, | |||||
| va_list args | |||||
| ) | |||||
| { | |||||
| va_list args_copy; | |||||
| va_copy(args_copy, args); | |||||
| int rc = ON_String::FormatVargsOutputCount(format, args_copy); | |||||
| va_end(args_copy); | |||||
| size_t buffer_capacity = (rc <= 0) ? 1 : (rc + 1); | |||||
| if (false == buffer.GrowBuffer(buffer_capacity) || nullptr == buffer.m_buffer || buffer.m_buffer_capacity <= 0) | |||||
| return (rc < 0 ? rc : -1); | |||||
| buffer.m_buffer[0] = 0; | |||||
| buffer.m_buffer[buffer.m_buffer_capacity - 1] = 0; | |||||
| if (rc > 0) | |||||
| { | |||||
| rc = ON_String::FormatVargsIntoBuffer(buffer.m_buffer, buffer.m_buffer_capacity, format, args); | |||||
| } | |||||
| return rc; | |||||
| } | |||||
| int ON_String::FormatVargsOutputCount( | |||||
| const char* format, | |||||
| va_list args | |||||
| ) | |||||
| { | |||||
| if ( nullptr == format || 0 == format[0] ) | |||||
| return 0; | |||||
| #if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU) | |||||
| // CLang modifies args so a copy is required | |||||
| va_list args_copy; | |||||
| va_copy (args_copy, args); | |||||
| #if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) | |||||
| int len = vsnprintf(nullptr, 0, format, args_copy); | |||||
| #else | |||||
| int len = vsnprintf_l(nullptr, 0, ON_Locale::Ordinal.NumericLocalePtr(), format, args_copy); | |||||
| #endif | |||||
| va_end(args_copy); | |||||
| return len; | |||||
| #else | |||||
| return _vscprintf_p_l(format, ON_Locale::Ordinal.NumericLocalePtr(), args); | |||||
| #endif | |||||
| } | |||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
| // | |||||
| // ON_wString formatting | |||||
| // | |||||
| bool ON_VARGS_FUNC_CDECL ON_wString::Format(const wchar_t* format, ...) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| bool rc = FormatVargs(format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| bool ON_wString::FormatVargs(const wchar_t* format, va_list args) | |||||
| { | |||||
| const int len_count = ON_wString::FormatVargsOutputCount(format, args); | |||||
| if (len_count > 0) | |||||
| { | |||||
| // temp is used because sometimes "this" is an argument as in: | |||||
| // ON_String str = L"filename.ext"; | |||||
| // str.Format(L"C:\\%s",str); | |||||
| ON_wString temp; | |||||
| temp.SetLength(len_count); | |||||
| const int len_string = ON_wString::FormatVargsIntoBuffer(temp.Array(), len_count + 1, format, args); | |||||
| if (len_string == len_count) | |||||
| { | |||||
| *this = temp; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| Destroy(); | |||||
| Create(); | |||||
| return (0 == len_count); | |||||
| } | |||||
| const ON_wString ON_VARGS_FUNC_CDECL ON_wString::FormatToString( | |||||
| const wchar_t* format, | |||||
| ... | |||||
| ) | |||||
| { | |||||
| ON_wStringBuffer buffer; | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| ON_wString::FormatVargsIntoBuffer(buffer, format, args); | |||||
| va_end(args); | |||||
| return ON_wString(buffer.m_buffer); | |||||
| } | |||||
| int ON_VARGS_FUNC_CDECL ON_wString::FormatIntoBuffer( | |||||
| wchar_t* buffer, size_t buffer_capacity, | |||||
| const wchar_t* format, | |||||
| ... | |||||
| ) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| int rc = ON_wString::FormatVargsIntoBuffer(buffer, buffer_capacity, format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| int ON_VARGS_FUNC_CDECL ON_wString::FormatIntoBuffer( | |||||
| ON_wStringBuffer& buffer, | |||||
| const wchar_t* format, | |||||
| ... | |||||
| ) | |||||
| { | |||||
| va_list args; | |||||
| va_start(args, format); | |||||
| int rc = ON_wString::FormatVargsIntoBuffer(buffer, format, args); | |||||
| va_end(args); | |||||
| return rc; | |||||
| } | |||||
| #if defined(ON_COMPILER_CLANG) | |||||
| static const wchar_t* ConvertToCLangFormat( | |||||
| const wchar_t* format, | |||||
| ON_wStringBuffer& clang_format_buffer | |||||
| ) | |||||
| { | |||||
| size_t format_capacity = 0; | |||||
| const wchar_t* s; | |||||
| wchar_t c; | |||||
| s = format; | |||||
| for(;;) | |||||
| { | |||||
| if ('%' == *s) | |||||
| { | |||||
| s++; | |||||
| c = *s; | |||||
| if ('s' == c ) | |||||
| { | |||||
| // convert %s to %ls | |||||
| format_capacity++; | |||||
| s++; | |||||
| } | |||||
| else if (c >= '1' && c <= '9') | |||||
| { | |||||
| // look for %N$s with N >= 1 ... | |||||
| s++; | |||||
| while (*s >= '0' && *s <= '9') | |||||
| s++; | |||||
| if ('$' == *s && 's' == s[1] ) | |||||
| { | |||||
| // convert %N$s to %N$ls | |||||
| format_capacity++; | |||||
| s++; | |||||
| s++; | |||||
| } | |||||
| } | |||||
| } | |||||
| if ( 0 == *s++ ) | |||||
| break; | |||||
| } | |||||
| if (0 == format_capacity) | |||||
| return format; | |||||
| format_capacity += (s - format) + 1; // +1 for null terminator | |||||
| if ( !clang_format_buffer.GrowBuffer(format_capacity) ) | |||||
| return format; | |||||
| wchar_t* ls = clang_format_buffer.m_buffer; | |||||
| if ( nullptr == ls ) | |||||
| return format; | |||||
| s = format; | |||||
| format = ls; | |||||
| for(;;) | |||||
| { | |||||
| if ('%' == *s) | |||||
| { | |||||
| *ls++ = *s++; | |||||
| c = *s; | |||||
| if ('s' == c ) | |||||
| { | |||||
| // convert %s to %ls | |||||
| *ls++ = 'l'; | |||||
| *ls++ = *s++; | |||||
| } | |||||
| else if (c >= '1' && c <= '9') | |||||
| { | |||||
| // look for %N$s with N >= 1 ... | |||||
| *ls++ = *s++; | |||||
| while (*s >= '0' && *s <= '9') | |||||
| *ls++ = *s++; | |||||
| if ('$' == *s && 's' == s[1] ) | |||||
| { | |||||
| // convert %N$s to %N$ls | |||||
| *ls++ = *s++; | |||||
| *ls++ = 'l'; | |||||
| *ls++ = *s++; | |||||
| } | |||||
| } | |||||
| } | |||||
| if ( 0 == (*ls++ = *s++) ) | |||||
| break; | |||||
| } | |||||
| return format; | |||||
| } | |||||
| #endif | |||||
| int ON_wString::FormatVargsIntoBuffer( | |||||
| wchar_t* buffer, | |||||
| size_t buffer_capacity, | |||||
| const wchar_t* format, | |||||
| va_list args | |||||
| ) | |||||
| { | |||||
| if (0 == buffer || buffer_capacity <= 0) | |||||
| return -1; | |||||
| buffer[0] = 0; | |||||
| if ( nullptr == format || 0 == format[0] ) | |||||
| return 0; | |||||
| #if defined(ON_COMPILER_CLANG) | |||||
| // CLang requires %ls to properly format a const wchar_t* parameter | |||||
| wchar_t clang_format_stack_buffer[128]; | |||||
| ON_wStringBuffer clang_format_buffer(clang_format_stack_buffer, sizeof(clang_format_stack_buffer) / sizeof(clang_format_stack_buffer[0])); | |||||
| format = ConvertToCLangFormat( | |||||
| format, | |||||
| clang_format_buffer | |||||
| ); | |||||
| va_list args_copy; | |||||
| va_copy (args_copy, args); | |||||
| // Cannot use Apple's vswprintf_l() because it's buggy. | |||||
| // This means we cannot be certain that a period will be used for a decimal point in formatted printing. | |||||
| // For details, see comments below in ON_wString::FormatVargsOutputCount(). | |||||
| //int len = vswprintf_l(buffer, buffer_capacity, ON_Locale::Ordinal.NumericLocalePtr(), format, args_copy); | |||||
| int len = vswprintf(buffer, buffer_capacity, format, args_copy); | |||||
| va_end(args_copy); | |||||
| #else | |||||
| #if defined(ON_COMPILER_GNU) | |||||
| va_list args_copy; | |||||
| va_copy (args_copy, args); | |||||
| int len = vswprintf(buffer, buffer_capacity, format, args_copy); | |||||
| va_end(args_copy); | |||||
| #else | |||||
| // Using ON_Locale::Ordinal.NumericLocalePtr() insures that a period | |||||
| // will be use for the decimal point in formatted printing. | |||||
| int len = _vswprintf_p_l(buffer, buffer_capacity, format, ON_Locale::Ordinal.NumericLocalePtr(), args); | |||||
| #endif | |||||
| #endif | |||||
| if (((size_t)len) >= buffer_capacity) | |||||
| len = -1; | |||||
| buffer[(len <= 0) ? 0 : len] = 0; | |||||
| buffer[buffer_capacity - 1] = 0; | |||||
| return len; | |||||
| } | |||||
| int ON_wString::FormatVargsIntoBuffer( | |||||
| ON_wStringBuffer& buffer, | |||||
| const wchar_t* format, | |||||
| va_list args | |||||
| ) | |||||
| { | |||||
| va_list args_copy; | |||||
| va_copy(args_copy, args); | |||||
| int rc = ON_wString::FormatVargsOutputCount(format, args_copy); | |||||
| va_end(args_copy); | |||||
| size_t buffer_capacity = (rc <= 0) ? 1 : (rc + 1); | |||||
| if (false == buffer.GrowBuffer(buffer_capacity) || nullptr == buffer.m_buffer || buffer.m_buffer_capacity <= 0) | |||||
| return (rc < 0 ? rc : -1); | |||||
| buffer.m_buffer[0] = 0; | |||||
| buffer.m_buffer[buffer.m_buffer_capacity - 1] = 0; | |||||
| if (rc > 0) | |||||
| { | |||||
| rc = ON_wString::FormatVargsIntoBuffer(buffer.m_buffer, buffer.m_buffer_capacity, format, args); | |||||
| } | |||||
| return rc; | |||||
| } | |||||
| int ON_wString::FormatVargsOutputCount( | |||||
| const wchar_t* format, | |||||
| va_list args | |||||
| ) | |||||
| { | |||||
| if ( nullptr == format || 0 == format[0] ) | |||||
| return 0; | |||||
| #if defined(ON_COMPILER_CLANG) | |||||
| // Unlike _vscwprintf_p_l(), CLang's vswprintf() does not tell you how many characters would have | |||||
| // been written if there was space enough in the buffer. | |||||
| // It reports an error when there is not enough space. | |||||
| // Assume a moderately large machine so kilobytes of wchar_t on the stack is not a problem. | |||||
| // CLang requires %ls to properly format a const wchar_t* parameter | |||||
| wchar_t clang_format_stack_buffer[128]; | |||||
| ON_wStringBuffer clang_format_buffer(clang_format_stack_buffer, sizeof(clang_format_stack_buffer) / sizeof(clang_format_stack_buffer[0])); | |||||
| format = ConvertToCLangFormat( | |||||
| format, | |||||
| clang_format_buffer | |||||
| ); | |||||
| // Attempting to directly get the count fails in OS X 10.4 June 2015 (always returns fmt_size = -1) | |||||
| // | |||||
| ////va_list args_copy; | |||||
| ////va_copy(args_copy, args); | |||||
| ////const int formatted_string_count = vswprintf_l(nullptr, 0, ON_Locale::Ordinal.NumericLocalePtr(), format, args_copy); | |||||
| ////va_end(args_copy); | |||||
| ////return (formatted_string_count >= 0) ? formatted_string_count : -1; | |||||
| wchar_t stack_buffer[1024]; | |||||
| ON_wStringBuffer buffer(stack_buffer, sizeof(stack_buffer) / sizeof(stack_buffer[0])); | |||||
| size_t buffer_capacity = buffer.m_buffer_capacity; | |||||
| for(;;) | |||||
| { | |||||
| va_list args_copy; | |||||
| va_copy(args_copy, args); | |||||
| // NOTE: | |||||
| // Cannot use Apple's vswprintf_l() because it's buggy. | |||||
| // This means we cannot be certain that a period will be used for a decimal point in formatted printing. | |||||
| // | |||||
| // Apple's OS X 10.4 (July 2015) formatted printing functions are buggy when locale is specified. | |||||
| // Here's what happens: | |||||
| //// int ON_VARGS_FUNC_CDECL VargsFormatFuncA(const wchar_t* format, ...) | |||||
| //// { | |||||
| //// va_list args; | |||||
| //// va_start(args, format); | |||||
| //// wchar_t buffer[128]; | |||||
| //// int count = vswprintf(buffer, 128, format, args); | |||||
| //// va_end(args); | |||||
| //// return count; | |||||
| //// } | |||||
| //// | |||||
| //// int ON_VARGS_FUNC_CDECL VargsFormatFuncB(const wchar_t* format, ...) | |||||
| //// { | |||||
| //// va_list args; | |||||
| //// va_start(args, format); | |||||
| //// wchar_t buffer[128]; | |||||
| //// #if defined(ON_COMPILER_CLANG) | |||||
| //// int count = vswprintf_l(buffer, 128, ON_Locale::Ordinal.NumericLocalePtr(), format, args_copy); | |||||
| //// #else | |||||
| //// int count = _vswprintf_p_l(buffer, 128, format, ON_Locale::Ordinal.NumericLocalePtr(), args); | |||||
| //// #endif | |||||
| //// va_end(args); | |||||
| //// return count; | |||||
| //// } //// | |||||
| //// | |||||
| //// Tests() | |||||
| //// { | |||||
| //// const wchar_t str1[5] = {'T', 'e', 's', 't', 0}; | |||||
| //// const wchar_t str2[5] = {'T', 0xFFFD,'s', 't', 0}; | |||||
| //// | |||||
| //// int Acount1 = VargsFormatFuncA(L"%ls",str1); | |||||
| //// int Acount2 = VargsFormatFuncA(L"%ls",str2); | |||||
| //// | |||||
| //// RhinoApp().Print("Acount1 = %d Acount2 = %d\n",Acount1,Acount2); | |||||
| //// | |||||
| //// int Bcount1 = VargsFormatFuncB(L"%ls",str1); | |||||
| //// int Bcount2 = VargsFormatFuncB(L"%ls",str2); | |||||
| //// | |||||
| //// // Windows Results: Acount1 = 4, Acount2 = 4, Bcount1 = 4, Bcount2 = 4 | |||||
| //// // Apple Results: Acount1 = 4, Acount2 = 4, Bcount1 = 4, Bcount2 = -1 | |||||
| //// } | |||||
| //// | |||||
| //const int formatted_string_count = vswprintf_l(buffer.m_buffer, buffer.m_buffer_capacity, ON_Locale::Ordinal.NumericLocalePtr(), format, args_copy); | |||||
| const int formatted_string_count = vswprintf(buffer.m_buffer, buffer.m_buffer_capacity, format, args_copy); | |||||
| va_end(args_copy); | |||||
| if (formatted_string_count >= 0) | |||||
| { | |||||
| // formatted_string_count = number of wchar_t elements not including null terminator | |||||
| return formatted_string_count; | |||||
| } | |||||
| if ( buffer_capacity >= 1024*16*16*16 ) | |||||
| break; | |||||
| buffer_capacity *= 16; | |||||
| if (false == buffer.GrowBuffer(buffer_capacity)) | |||||
| break; | |||||
| if (nullptr == buffer.m_buffer) | |||||
| break; | |||||
| if (buffer_capacity < buffer.m_buffer_capacity) | |||||
| break; | |||||
| } | |||||
| return -1; | |||||
| #else | |||||
| #if defined(ON_COMPILER_GNU) | |||||
| // 31 May 2019 S. Baer (RH-52038) | |||||
| // TODO: The following code needs to be tested. This was added by request from a user that needed | |||||
| // a GCC compile. This is obviously a cut and paste of the above clang code | |||||
| wchar_t stack_buffer[1024]; | |||||
| ON_wStringBuffer buffer(stack_buffer, sizeof(stack_buffer) / sizeof(stack_buffer[0])); | |||||
| size_t buffer_capacity = buffer.m_buffer_capacity; | |||||
| for(;;) | |||||
| { | |||||
| va_list args_copy; | |||||
| va_copy(args_copy, args); | |||||
| const int formatted_string_count = vswprintf(buffer.m_buffer, buffer.m_buffer_capacity, format, args_copy); | |||||
| va_end(args_copy); | |||||
| if (formatted_string_count >= 0) | |||||
| { | |||||
| // formatted_string_count = number of wchar_t elements not including null terminator | |||||
| return formatted_string_count; | |||||
| } | |||||
| if ( buffer_capacity >= 1024*16*16*16 ) | |||||
| break; | |||||
| buffer_capacity *= 16; | |||||
| if (false == buffer.GrowBuffer(buffer_capacity)) | |||||
| break; | |||||
| if (nullptr == buffer.m_buffer) | |||||
| break; | |||||
| if (buffer_capacity < buffer.m_buffer_capacity) | |||||
| break; | |||||
| } | |||||
| return -1; | |||||
| #else | |||||
| // Using ON_Locale::Ordinal.NumericLocalePtr() insures that a period | |||||
| // will be use for the decimal point in formatted printing. | |||||
| return _vscwprintf_p_l(format, ON_Locale::Ordinal.NumericLocalePtr(), args); | |||||
| #endif | |||||
| #endif | |||||
| } | |||||