mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-01 11:36:09 +08:00
1260 lines
32 KiB
C++
1260 lines
32 KiB
C++
//
|
|
// Copyright (c) 1993-2022 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 enough 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 enough 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(), size_t(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) || defined(ON_RUNTIME_WASM)
|
|
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 : (size_t(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) || defined(ON_RUNTIME_WASM)
|
|
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(), size_t(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 : (size_t(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
|
|
}
|