Files
opennurbs/opennurbs_string_values.cpp
2024-02-15 08:00:36 -08:00

1842 lines
52 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
#include "opennurbs_parse.h"
ON_LengthValue::StringFormat ON_LengthValue::LengthStringFormatFromUnsigned(
unsigned int string_format_as_unsigned
)
{
switch (string_format_as_unsigned)
{
ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::ExactDecimal);
ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::ExactProperFraction);
ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::ExactImproperFraction);
ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::CleanDecimal);
ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::CleanProperFraction);
ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::CleanImproperFraction);
}
ON_ERROR("Invalid string_format_as_unsigned value.");
return (ON_LengthValue::Unset.m_string_format);
}
ON_AngleValue::StringFormat ON_AngleValue::AngleStringFormatFromUnsigned(
unsigned int string_format_as_unsigned
)
{
switch (string_format_as_unsigned)
{
ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::ExactDecimal);
ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::ExactFraction);
ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::CleanDecimal);
ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::CleanFraction);
}
ON_ERROR("Invalid string_format_as_unsigned value.");
return (ON_AngleValue::Unset.m_string_format);
}
const ON_LengthValue ON_LengthValue::RemoveUnitSystem() const
{
if (
ON::LengthUnitSystem::Unset == this->LengthUnitSystem().UnitSystem()
|| ON::LengthUnitSystem::None == this->LengthUnitSystem().UnitSystem()
)
{
return *this;
}
ON_LengthValue rc(*this);
rc.m_length_unit_system = ON_UnitSystem::None;
const ON_ParseSettings parse_settings = rc.LengthStringParseSettings();
const wchar_t* str = static_cast<const wchar_t*>(rc.m_length_as_string);
const int str_count = rc.m_length_as_string.Length();
ON_ParseSettings parse_results;
double x = ON_DBL_QNAN;
int str_index = ON_ParseNumberExpression(str, str_count, parse_settings, &parse_results, &x);
if (str_index > 0 && str_index <= str_count && x == m_length)
{
rc.m_length_as_string.SetLength(str_index);
rc.m_length_as_string.TrimLeftAndRight();
}
else
{
rc = ON_LengthValue::Create(m_length, ON_LengthUnitName::None, m_string_format);
if (!(rc.m_length == m_length))
{
ON_ERROR("Unable to remove unit system");
return *this;
}
}
return rc;
}
const ON_LengthValue ON_LengthValue::ChangeLength(
double length_value
) const
{
ON_LengthValue rc(*this);
if (!ON_IsValid(length_value))
{
rc.m_length_as_string = ON_wString::EmptyString;
rc.m_length = ON_LengthValue::Unset.m_length;
}
else
{
rc = ON_LengthValue::Create(length_value, m_length_unit_system, m_context_locale_id, m_string_format);
}
return rc;
}
ON_LengthValue ON_LengthValue::CreateFromString(
ON_ParseSettings parse_settings,
const wchar_t* string
)
{
if ( nullptr == string || 0 == string[0] )
return ON_LengthValue::Unset;
const wchar_t* string_end = nullptr;
const ON_LengthValue length_value = ON_LengthValue::CreateFromSubString(parse_settings, string, -1, &string_end);
if (nullptr == string_end || !(string < string_end))
{
// Calling ON_ERROR here causes .NET trauma when ON_LengthValue::CreateFromString() is used for
// input validation.
//ON_ERROR("Invalid input parameters.");
return ON_LengthValue::Unset;
}
return length_value;
}
ON_LengthValue ON_LengthValue::CreateFromSubString(
ON_ParseSettings parse_settings,
const wchar_t* string,
int string_count,
const wchar_t** string_end
)
{
// All calls to some version of ON_LengthValue::CreateFrom*String(...) end up here.
if (nullptr != string_end && &string != string_end)
*string_end = string;
if (string_count < -1)
{
ON_ERROR("Invalid string_count parameter.");
return ON_LengthValue::Unset;
}
if (nullptr == string || 0 == string_count || 0 == string[0] )
{
// empty string fails silently.
return ON_LengthValue::Unset;
}
double length_value = ON_DBL_QNAN;
ON_ParseSettings parse_results;
ON::LengthUnitSystem string_length_unit_system = ON::LengthUnitSystem::Unset;
const int parse_count = ON_ParseLengthExpression(
string,
string_count,
parse_settings,
&length_value,
&parse_results,
&string_length_unit_system
);
if ( parse_count <= 0
|| (parse_count > string_count && string_count >= 0)
|| false == (length_value == length_value)
)
{
// Calling ON_ERROR HERE .NET wrapper trauma when CreateFromSubString() is used for string validation.
// ON_ERROR("Input string parameter is not valid.");
return ON_LengthValue::Unset;
}
ON_LengthValue rc;
rc.m_length = length_value;
if (ON::LengthUnitSystem::Unset != string_length_unit_system && ON::LengthUnitSystem::None != string_length_unit_system)
rc.m_length_unit_system = ON_UnitSystem(string_length_unit_system);
else
rc.m_length_unit_system = ON_UnitSystem(parse_settings.ContextLengthUnitSystem());
rc.m_context_angle_unit_system = parse_settings.ContextAngleUnitSystem();
rc.m_context_locale_id = parse_settings.ContextLocaleId();
rc.m_length_as_string = ON_wString(string, parse_count);
rc.m_length_as_string.TrimLeftAndRight();
if (nullptr != string_end)
*string_end = string + parse_count;
return rc;
}
static
double ON_CleanValueTolerance(
double value,
double clean_value_tolerance
)
{
if (clean_value_tolerance > 1.0 / 512.0)
clean_value_tolerance = 1.0 / 512.0;
const double a = fabs(value);
// The relative tolerance 256.0*ON_EPSILON*a is reqired in order for all (1+/- ON_EPSILON)*value where
// value is one of the rational numbers with denominators
// 2,3,4,8,10,16,32,64,128 and numerators from 1 to 2*denominator to be "cleaned up}
// to numerator/denominator.
const double relative_tolerance = 256.0*ON_EPSILON*a;
return (clean_value_tolerance > relative_tolerance) ? clean_value_tolerance : relative_tolerance;
}
static
double ON_CleanNumber(
double value,
double clean_value_tolerance
)
{
if (!ON_IsValid(value))
return value;
const double tol = ON_CleanValueTolerance(value, clean_value_tolerance);
if (fabs(value) <= tol)
return 0.0;
const double s = (value < 0.0) ? -1.0 : 1.0;
const double a = fabs(value);
double i = 0.0;
const double f = modf(a, &i);
if (f <= tol)
return s*i;
if ((1.0-f) <= tol)
return s*(i+1.0);
// terminate these lists with 0.0.
const double d1[] = { 2.0, 3.0, 4.0, 8.0, 10.0, 16.0, 32.0, 64.0, 128.0, 0.0 };
const double d2[] = { 5.0, 6.0, 7.0, 9.0, 12.0, 0.0 };
const double* d[2] = { d1, d2 };
for (int pass = 0; pass < 2; pass++)
{
for (size_t k = 0; d[pass][k] > 0.0; k++)
{
const double y = d[pass][k] * f;
double x = floor(y);
if (y - x > 0.5)
x += 1.0;
if (fabs(x - y) <= tol)
{
// (i*d[pass][k] + x) / d[pass][k] is more precise (repeatable)
// than i + x/d[pass][k]
return s*(i*d[pass][k] + x) / d[pass][k];
}
}
}
return value;
}
const ON_wString& ON_NTimesPowerOf10AsStringFail(const ON_wString& g_format, bool bLogError)
{
// The number being formatted might be bogus.
// This can occur when values are not properly initialized.
if (bLogError) // <- good place for debugging breakpoint
{
ON_ERROR("Unexpected result.");
}
// g_format is a valid string representation of the input value, so
// the user is seeing something that is correct, but may have
// scientific notation or lots of digits.
return g_format;
}
static
const ON_wString ON_NTimesPowerOf10AsString(
double value,
double tol,
const ON_wString& g_format,
ON__UINT64 n,
int e,
double* clean_value
)
{
if (nullptr != clean_value)
*clean_value = value;
if ( false == ON_IsValid(value) )
return ON_NTimesPowerOf10AsStringFail(g_format,false); // value is a nan or UNSET value.
// ON__UINT64 range is 0 to 18,446,744,073,709,551,615 = 1.8...e19
const double max_pretty_value = 1.0e18; // must be <= 18,446,744,073,709,551,615 ( 20 decimal digits )
const double min_pretty_value = 1.0 / max_pretty_value;
if (fabs(value) >= max_pretty_value || fabs(value) <= min_pretty_value)
return ON_NTimesPowerOf10AsStringFail(g_format,false); // value is too big or too small for a "pretty" format.
// returns n*(10^e) as a "pretty" string - no exponential notation that disturbs users.
ON__UINT64 q = 1;
ON__UINT64 i = 0;
ON__UINT64 f = 0;
if (e >= 0)
{
for (int ie = 0; ie < e; ie++)
q *= 10;
if (fabs((double)n)*fabs((double)q) >= max_pretty_value)
return ON_NTimesPowerOf10AsStringFail(g_format,false);
i = n*q;
f = 0;
}
else
{
// e is negative
for (int ie = 0; ie > e; ie--)
q *= 10;
if (fabs((double)n) <= min_pretty_value*fabs((double)q))
return ON_NTimesPowerOf10AsStringFail(g_format,false);
i = n / q;
f = n % q;
}
const double x = ((double)i) + ((double)f) / ((double)q);
if (fabs(x - value) <= tol)
{
if (0 == f)
{
if (nullptr != clean_value)
*clean_value = x;
return ON_wString::FormatToString(L"%llu", i);
}
wchar_t sf[32] = { 0 }; // 32 is more than enough to hold the maximum of 20 digits
size_t sf_capacity = sizeof(sf) / sizeof(sf[i]);
size_t sfi = 0;
for (ON__UINT64 r = q / 10; r > 0 && sfi < sf_capacity; r /= 10)
{
sf[sfi++] = (wchar_t)(int((f / r)%10) + '0');
}
const ON_wString value_as_string = ON_wString::FormatToString(L"%llu.%ls", i, sf);
double y = ON_DBL_QNAN;
const int scan_count = value_as_string.Scan( L"%lf", &y );
if (1 == scan_count && fabs(y - value) <= tol)
{
if (nullptr != clean_value)
*clean_value = y;
return value_as_string;
}
}
return ON_NTimesPowerOf10AsStringFail(g_format,false);
}
static
const ON_wString ON_CleanNumberToString(
double value,
double clean_value_tolerance,
double* clean_value
)
{
if (nullptr != clean_value)
*clean_value = value;
if (!ON_IsValid(value))
return ON_wString::EmptyString;
// replace any -0.0 value with 0.0
if (0.0 == value)
{
value = 0.0;
if (nullptr != clean_value)
*clean_value = value;
}
const double tol = ON_CleanValueTolerance(value, clean_value_tolerance);
if (fabs(value) <= tol)
{
if (nullptr != clean_value)
*clean_value = 0.0;
return ON_wString(L"0");
}
const ON_wString g_format = ON_wString::FormatToString(L"%.17g", value);
const ON_wString e_format = ON_wString::FormatToString(L"%.17e", value);
const wchar_t* s0 = static_cast<const wchar_t*>(e_format);
if ('-' == s0[0] || '+' == s0[0])
s0++;
if ( s0[0] < '0' || s0[0] > '9')
{
ON_ERROR("Unexpected double string format.");
return g_format;
}
if ('.' != s0[1])
{
return g_format;
}
const wchar_t* decimal = s0;
while (0 != *decimal && '.' != *decimal)
decimal++;
if ( '.' != decimal[0] || decimal[1] < '0' || decimal[1] > '9')
{
return g_format;
}
const wchar_t* exponent = s0 + 2;
while (0 != *exponent && 'e' != *exponent && 'E' != *exponent)
exponent++;
if (0 == exponent[0])
return g_format;
int e = 0;
if (0 != exponent[0])
{
for (int i = ('+' == exponent[1] || '-' == exponent[1]) ? 2 : 1; 0 != exponent[i]; i++)
{
if (exponent[i] < '0' || exponent[i] > '9')
{
ON_ERROR("Unexpected double string format.");
return g_format;
}
e = 10 * e + ((int)(exponent[i] - '0'));
}
if (e > 0 && '-' == exponent[1])
e = -e;
}
const unsigned int max_count = 3;
unsigned int zero_count = 0;
unsigned int nine_count = 0;
const wchar_t* first_nine = nullptr;
const wchar_t* first_zero = nullptr;
const ON__UINT64 ten = 10;
ON__UINT64 n = 0;
e++; // e is unconditionally decremented when n is initialized.
for ( const wchar_t* s = s0; s < exponent; s++)
{
if ('.' == *s)
continue;
if ('0' == *s)
{
if (nullptr != first_nine)
{
while (first_nine < s)
{
n = ten*n + ((ON__UINT64)(*first_nine++ - '0'));
e--;
}
first_nine = nullptr;
nine_count = 0;
}
if (nullptr == first_zero)
first_zero = s;
zero_count++;
if (max_count == zero_count)
{
double x = ((double)n)*pow(10.0, e);
if (fabs(x - value) <= tol)
{
return ON_NTimesPowerOf10AsString(value, tol, g_format, n, e, clean_value);
}
}
}
else if ('9' == *s)
{
if (nullptr != first_zero)
{
while (first_zero < s)
{
n = ten*n + ((ON__UINT64)(*first_zero++ - '0'));
e--;
}
first_zero = nullptr;
zero_count = 0;
}
if (nullptr == first_nine)
first_nine = s;
nine_count++;
if (max_count == nine_count)
{
double x = ((double)(n+1))*pow(10.0, e);
if (fabs(x - value) <= tol)
{
return ON_NTimesPowerOf10AsString(value, tol, g_format, n+1, e, clean_value);
}
nine_count--;
n = ten*n + ((ON__UINT64)(*first_nine++ - '0'));
e--;
}
}
else
{
n = ten*n + ((ON__UINT64)(*s - '0'));
e--;
}
}
return g_format;
}
/*
Parameters:
value - [in]
value to test
bImproperFraction - [in]
True if the returned value can be an improper fraction
(1.25 will be 5/4, proper = 0, numerator = 5, denominator = 4).
False if the returned value should be a proper fraction
(1.25 will be 1-1/4, proper = 1, numerator = 1, denominator = 4).
sign - [out]
+1.0, -1.0 or 0.0.
Returns:
True:
value is a rational number with a denominator commonly used in annotation.
value = sign*(proper + numerator/denominator);
False:
value is not a rational number with a denominator commonly used in annotation.
*/
static
bool ON_IsAnnotationFractionNumber(
double value,
bool bImproperFraction,
double* sign,
double* proper,
double* numerator,
double* denominator
)
{
if (nullptr != sign)
*sign = (value < 0.0) ? -1.0 : ((value > 0.0) ? 1.0 : 0.0);
if (nullptr != proper)
*proper = 0.0;
if (nullptr != numerator)
*numerator = value;
if (nullptr != denominator)
*denominator = 1.0;
if (!ON_IsValid(value))
return false;
const double a = fabs(value);
double i = 0.0;
const double f = modf(a, &i);
const double tol = 4.0*ON_EPSILON*a;
if ( !(f > tol) )
return false;
// terminate this list with 0.0.
const double d[] = { 2.0, 3.0, 4.0, 8.0, 10.0, 16.0, 32.0, 64.0, 128.0, 0.0 };
for (size_t k = 0; d[k] > 0.0; k++)
{
const double y = d[k] * f;
double x = floor(y);
if (y - x > 0.5)
x += 1.0;
if (!(fabs(x - y) <= tol))
continue;
if (false == bImproperFraction && i >= 1.0)
{
if (nullptr != proper)
*proper = i;
i = 0.0;
}
if (nullptr != numerator)
*numerator = i*d[k] + x;
if (nullptr != denominator)
*denominator = d[k];
return true;
}
return false;
}
ON_LengthValue ON_LengthValue::Create(
double length_value,
const class ON_UnitSystem& length_unit_system,
unsigned int locale_id,
ON_LengthValue::StringFormat string_format
//, double clean_format_tolerance
)
{
const double clean_format_tolerance = 0.0; // == ON_ZERO_TOLERANCE
ON_LengthValue rc;
rc.m_string_format = string_format;
for (;;)
{
bool bFraction = false;
bool bImproperFraction = false;
bool bClean = false;
switch (string_format)
{
case ON_LengthValue::StringFormat::ExactDecimal:
break;
case ON_LengthValue::StringFormat::ExactProperFraction:
bFraction = true;
break;
case ON_LengthValue::StringFormat::ExactImproperFraction:
bFraction = true;
bImproperFraction = true;
break;
case ON_LengthValue::StringFormat::CleanDecimal:
bClean = true;
break;
case ON_LengthValue::StringFormat::CleanProperFraction:
bClean = true;
bFraction = true;
break;
case ON_LengthValue::StringFormat::CleanImproperFraction:
bClean = true;
bFraction = true;
bImproperFraction = true;
break;
default:
break;
}
if (false == (length_value == length_value))
break;
if (ON::LengthUnitSystem::Unset == length_unit_system.UnitSystem())
break;
rc.m_length
= bClean
? ON_CleanNumber(length_value,clean_format_tolerance)
: length_value;
rc.m_length_unit_system = length_unit_system;
const ON_LengthUnitName name = ON_LengthUnitName::Create(locale_id, length_unit_system.UnitSystem(), length_value > 1.0);
// DO NOT USE A "PRETTY" FORMAT!
// It is critical that this string scan back to rc.m_length exactly.
double sign = ON_DBL_QNAN;
double proper = ON_DBL_QNAN;
double numerator = ON_DBL_QNAN;
double denominator = ON_DBL_QNAN;
if (
bFraction
&& ON_IsAnnotationFractionNumber(rc.m_length, bImproperFraction, &sign, &proper, &numerator, &denominator)
)
{
// (proper*denominator + numerator)/denominator is more precise (repeatable)
// than proper + numerator/denominator
rc.m_length = sign*(proper*denominator + numerator)/denominator;
if (proper != 0.0)
{
if (name.LengthUnitNameIsNotEmpty())
rc.m_length_as_string.Format(L"%0.17g-%0.17g/%0.17g %ls", sign*proper, numerator, denominator, name.LengthUnitName());
else
rc.m_length_as_string.Format(L"%0.17g-%0.17g/%0.17g", sign*proper, numerator, denominator);
}
else
{
if (name.LengthUnitNameIsNotEmpty())
rc.m_length_as_string.Format(L"%0.17g/%0.17g %ls", numerator, denominator, name.LengthUnitName());
else
rc.m_length_as_string.Format(L"%0.17g/%0.17g", numerator, denominator);
}
}
else
{
double clean_length = ON_DBL_QNAN;
const ON_wString clean_length_as_string = ON_CleanNumberToString(rc.m_length, 0.0, &clean_length);
if (bClean || clean_length == rc.m_length)
{
rc.m_length = clean_length;
if (name.LengthUnitNameIsNotEmpty())
rc.m_length_as_string.Format(L"%ls %ls", static_cast<const wchar_t*>(clean_length_as_string), name.LengthUnitName());
else
rc.m_length_as_string = clean_length_as_string;
}
else if (name.LengthUnitNameIsNotEmpty())
rc.m_length_as_string.Format(L"%0.17g %ls", rc.m_length, name.LengthUnitName());
else
rc.m_length_as_string.Format(L"%0.17g", rc.m_length);
}
rc.m_context_angle_unit_system = ON::AngleUnitSystem::Radians;
rc.m_context_locale_id = name.LocaleId();
return rc;
}
return ON_LengthValue::Unset;
}
ON_LengthValue ON_LengthValue::Create(
double length_value,
const ON::LengthUnitSystem length_unit_system,
unsigned int locale_id,
ON_LengthValue::StringFormat string_format
//, double clean_format_tolerance
)
{
return ON_LengthValue::Create(
length_value,
ON_UnitSystem(length_unit_system),
locale_id,
string_format
//, clean_format_tolerance
);
}
ON_LengthValue ON_LengthValue::Create(
double length_value,
const ON_LengthUnitName& length_unit_system,
ON_LengthValue::StringFormat string_format
//, double clean_format_tolerance
)
{
return ON_LengthValue::Create(
length_value,
length_unit_system.LengthUnit(),
length_unit_system.LocaleId(),
string_format
);
}
double ON_LengthValue::Length(
const ON_UnitSystem& context_unit_system
) const
{
if ( ON::LengthUnitSystem::None == context_unit_system.UnitSystem())
return m_length;
if (
m_length_unit_system.MetersPerUnit(ON_DBL_QNAN) == context_unit_system.MetersPerUnit(ON_DBL_QNAN)
&& ON::LengthUnitSystem::Unset != context_unit_system.UnitSystem()
)
return m_length;
return m_length*ON::UnitScale(m_length_unit_system, context_unit_system);
}
double ON_LengthValue::Length(
ON::LengthUnitSystem context_unit_system
) const
{
if ( ON::LengthUnitSystem::None == context_unit_system)
return m_length;
if ( m_length_unit_system.UnitSystem() == context_unit_system && ON::LengthUnitSystem::Unset != context_unit_system)
return m_length;
return m_length*ON::UnitScale(m_length_unit_system, context_unit_system);
}
const ON_UnitSystem& ON_LengthValue::LengthUnitSystem() const
{
return m_length_unit_system;
}
const ON_wString& ON_LengthValue::LengthAsString() const
{
m_length_as_string.IsValid(true);
return m_length_as_string;
}
const wchar_t* ON_LengthValue::LengthAsStringPointer() const
{
m_length_as_string.IsValid(true);
return static_cast<const wchar_t*>(m_length_as_string);
}
bool ON_LengthValue::IsUnset() const
{
return IsSet() ? false : true;
}
bool ON_LengthValue::IsSet() const
{
return (
m_length_unit_system.UnitSystem() != ON::LengthUnitSystem::Unset
&& (ON_UNSET_VALUE < m_length && m_length < ON_UNSET_POSITIVE_VALUE)
&& m_length_as_string.IsNotEmpty()
);
}
bool ON_LengthValue::Write(
class ON_BinaryArchive& archive
) const
{
const int content_version = 1;
if (!archive.BeginWrite3dmAnonymousChunk(content_version))
return false;
bool rc = false;
for (;;)
{
// content version = 0
if (!archive.WriteDouble(m_length))
break;
if (!m_length_unit_system.Write(archive))
break;
if (!archive.WriteInt(static_cast<unsigned int>(m_context_angle_unit_system)))
break;
if (!archive.WriteInt(m_context_locale_id))
break;
m_length_as_string.IsValid(true);
if (!archive.WriteString(m_length_as_string))
break;
// content version 1 added m_string_format
const unsigned int u = static_cast<unsigned char>(m_string_format);
if (!archive.WriteInt(u))
break;
rc = true;
break;
}
if (!archive.EndWrite3dmChunk())
rc = false;
return rc;
}
bool ON_LengthValue::Read(
class ON_BinaryArchive& archive
)
{
*this = ON_LengthValue::Unset;
int content_version = 0;
if (!archive.BeginRead3dmAnonymousChunk(&content_version))
return false;
bool rc = false;
for (;;)
{
if (!archive.ReadDouble(&m_length))
break;
if (!m_length_unit_system.Read(archive))
break;
unsigned int context_angle_unit_system_as_unsigned = static_cast<unsigned int>(m_context_angle_unit_system);
if (!archive.ReadInt(&context_angle_unit_system_as_unsigned))
break;
m_context_angle_unit_system = ON::AngleUnitSystemFromUnsigned(context_angle_unit_system_as_unsigned);
if (ON::AngleUnitSystem::None == m_context_angle_unit_system || ON::AngleUnitSystem::Unset == m_context_angle_unit_system)
m_context_angle_unit_system = ON::AngleUnitSystem::Radians;
if (!archive.ReadInt(&m_context_locale_id))
break;
if (!archive.ReadString(m_length_as_string))
break;
if (content_version >= 1)
{
// content version 1 added m_string_format
unsigned int u = static_cast<unsigned char>(m_string_format);
if (!archive.ReadInt(&u))
break;
m_string_format = ON_LengthValue::LengthStringFormatFromUnsigned(u);
}
rc = true;
break;
}
if (!archive.EndRead3dmChunk())
rc = false;
return rc;
}
ON_AngleValue ON_AngleValue::CreateFromString(
ON_ParseSettings parse_settings,
const wchar_t* string
)
{
if ( nullptr == string || 0 == string[0] )
return ON_AngleValue::Unset;
const wchar_t* string_end = nullptr;
const ON_AngleValue angle_value = ON_AngleValue::CreateFromSubString(parse_settings, string, -1, &string_end);
if (nullptr == string_end || !(string < string_end))
{
ON_ERROR("Invalid input parameters.");
return ON_AngleValue::Unset;
}
return angle_value;
}
ON_AngleValue ON_AngleValue::CreateFromSubString(
ON_ParseSettings parse_settings,
const wchar_t* string,
int string_count,
const wchar_t** string_end
)
{
// All calls to some version of ON_AngleValue::CreateFrom*String(...) end up here.
if (nullptr != string_end && &string != string_end)
*string_end = string;
if (string_count < -1)
{
ON_ERROR("Invalid string_count parameter.");
return ON_AngleValue::Unset;
}
if (nullptr == string || 0 == string_count || 0 == string[0] )
{
// empty string fails silently.
return ON_AngleValue::Unset;
}
double angle_value = ON_DBL_QNAN;
ON_ParseSettings parse_results;
ON::AngleUnitSystem string_angle_unit_system = ON::AngleUnitSystem::Unset;
const int parse_count = ON_ParseAngleExpression(
string,
string_count,
parse_settings,
&angle_value,
&parse_results,
&string_angle_unit_system
);
if ( parse_count <= 0
|| (parse_count > string_count && string_count >= 0)
|| false == (angle_value == angle_value)
)
{
// non-empty string failure generates debugger error.
ON_ERROR("Input string parameter is not valid.");
return ON_AngleValue::Unset;
}
ON_AngleValue rc;
rc.m_angle = angle_value;
if (ON::AngleUnitSystem::Unset != string_angle_unit_system && ON::AngleUnitSystem::None != string_angle_unit_system)
rc.m_angle_unit_system = string_angle_unit_system;
else
rc.m_angle_unit_system = parse_settings.DefaultAngleUnitSystem();
rc.m_context_length_unit_system = parse_settings.ContextLengthUnitSystem();
rc.m_context_locale_id = parse_settings.ContextLocaleId();
rc.m_angle_as_string = ON_wString(string, parse_count);
rc.m_angle_as_string.TrimLeftAndRight();
if (nullptr != string_end)
*string_end = string + parse_count;
return rc;
}
ON_AngleValue ON_AngleValue::Create(
double angle_value,
const class ON_AngleUnitName& angle_unit_system,
ON_AngleValue::StringFormat string_format
)
{
return ON_AngleValue::Create(
angle_value,
angle_unit_system.AngleUnit(),
angle_unit_system.LocaleId(),
string_format
);
}
ON_AngleValue ON_AngleValue::Create(
double angle_value,
ON::AngleUnitSystem angle_unit_system,
unsigned int locale_id,
ON_AngleValue::StringFormat string_format
)
{
ON_AngleValue rc;
for (;;)
{
bool bFraction = false;
bool bClean = false;
switch (string_format)
{
case ON_AngleValue::StringFormat::ExactDecimal:
break;
case ON_AngleValue::StringFormat::ExactFraction:
bFraction = true;
break;
case ON_AngleValue::StringFormat::CleanDecimal:
bClean = true;
break;
case ON_AngleValue::StringFormat::CleanFraction:
bClean = true;
bFraction = true;
break;
default:
string_format = ON_AngleValue::StringFormat::ExactDecimal;
break;
}
if (false == (angle_value == angle_value))
break;
if (ON::AngleUnitSystem::Unset == angle_unit_system)
break;
rc.m_angle = angle_value;
rc.m_angle_unit_system = angle_unit_system;
const ON_AngleUnitName name = ON_AngleUnitName::Create(locale_id, angle_unit_system, angle_value > 1.0);
// DO NOT USE A "PRETTY" FORMAT!
// It is critical that this string scan back to rc.m_angle exactly.
double sign = ON_DBL_QNAN;
double proper = ON_DBL_QNAN;
double numerator = ON_DBL_QNAN;
double denominator = ON_DBL_QNAN;
if (
bFraction
&& ON_IsAnnotationFractionNumber(rc.m_angle, true, &sign, &proper, &numerator, &denominator)
)
{
rc.m_angle = sign*numerator/denominator;
if (name.AngleUnitNameIsNotEmpty() )
rc.m_angle_as_string.Format(L"%0.17g/%0.17g %ls", numerator, denominator, name.AngleUnitName());
else
rc.m_angle_as_string.Format(L"%0.17g/%0.17g", numerator, denominator);
}
else
{
if (name.AngleUnitNameIsNotEmpty())
rc.m_angle_as_string.Format(L"%0.17g %ls", rc.m_angle, name.AngleUnitName());
else
rc.m_angle_as_string.Format(L"%0.17g", rc.m_angle);
}
rc.m_context_length_unit_system = ON::LengthUnitSystem::None;
rc.m_context_locale_id = name.LocaleId();
return rc;
}
return ON_AngleValue::Unset;
}
double ON_AngleValue::Angle(
ON::AngleUnitSystem context_unit_system
) const
{
if ( ON::AngleUnitSystem::None == context_unit_system)
return m_angle;
if ( m_angle_unit_system == context_unit_system )
return m_angle;
return m_angle*ON::AngleUnitScale(m_angle_unit_system, context_unit_system);
}
ON::AngleUnitSystem ON_AngleValue::AngleUnitSystem() const
{
return m_angle_unit_system;
}
const ON_wString& ON_AngleValue::AngleAsString() const
{
return m_angle_as_string;
}
const wchar_t* ON_AngleValue::AngleAsStringPointer() const
{
return static_cast<const wchar_t*>(m_angle_as_string);
}
bool ON_AngleValue::IsUnset() const
{
return IsSet() ? false : true;
}
bool ON_AngleValue::IsSet() const
{
return (
m_angle_unit_system != ON::AngleUnitSystem::Unset
&& (ON_UNSET_VALUE < m_angle && m_angle < ON_UNSET_POSITIVE_VALUE)
&& m_angle_as_string.IsNotEmpty()
);
}
bool ON_AngleValue::Write(
class ON_BinaryArchive& archive
) const
{
const int content_version = 1;
if (!archive.BeginWrite3dmAnonymousChunk(content_version))
return false;
bool rc = false;
for (;;)
{
if (!archive.WriteDouble(m_angle))
break;
if (!archive.WriteInt(static_cast<unsigned int>(m_angle_unit_system)))
break;
if (!archive.WriteInt(static_cast<unsigned int>(m_context_length_unit_system)))
break;
if (!archive.WriteInt(m_context_locale_id))
break;
if (!archive.WriteString(m_angle_as_string))
break;
// content version 1 added m_string_format
const unsigned int u = static_cast<unsigned char>(m_string_format);
if (!archive.WriteInt(u))
break;
rc = true;
break;
}
if (!archive.EndWrite3dmChunk())
rc = false;
return rc;
}
bool ON_AngleValue::Read(
class ON_BinaryArchive& archive
)
{
*this = ON_AngleValue::Unset;
int content_version = 0;
if (!archive.BeginRead3dmAnonymousChunk(&content_version))
return false;
bool rc = false;
for (;;)
{
if (!archive.ReadDouble(&m_angle))
break;
unsigned int angle_unit_system_as_unsigned = static_cast<unsigned int>(m_angle_unit_system);
if (!archive.ReadInt(&angle_unit_system_as_unsigned))
break;
m_angle_unit_system = ON::AngleUnitSystemFromUnsigned(angle_unit_system_as_unsigned);
unsigned int context_length_unit_system_as_unsigned = static_cast<unsigned int>(m_context_length_unit_system);
if (!archive.ReadInt(&context_length_unit_system_as_unsigned))
break;
m_context_length_unit_system = ON::LengthUnitSystemFromUnsigned(context_length_unit_system_as_unsigned);
if (ON::LengthUnitSystem::Unset == m_context_length_unit_system)
m_context_length_unit_system = ON::LengthUnitSystem::None;
if (!archive.ReadInt(&m_context_locale_id))
break;
if (!archive.ReadString(m_angle_as_string))
break;
if (content_version >= 1)
{
// content version 1 added m_string_format
unsigned int u = static_cast<unsigned char>(m_string_format);
if (!archive.ReadInt(&u))
break;
m_string_format = ON_AngleValue::AngleStringFormatFromUnsigned(u);
}
rc = true;
break;
}
if (!archive.EndRead3dmChunk())
rc = false;
return rc;
}
ON_ScaleValue ON_ScaleValue::CreateFromString(
ON_ParseSettings parse_settings,
const wchar_t* string
)
{
if ( nullptr == string || 0 == string[0] )
return ON_ScaleValue::Unset;
const wchar_t* string_end = nullptr;
const ON_ScaleValue scale_value = ON_ScaleValue::CreateFromSubString(parse_settings, string, -1, &string_end);
if (nullptr == string_end || !(string < string_end))
{
ON_ERROR("Invalid input parameters.");
return ON_ScaleValue::Unset;
}
return scale_value;
}
ON_ScaleValue ON_ScaleValue::CreateFromSubString(
ON_ParseSettings parse_settings,
const wchar_t* string,
int string_count,
const wchar_t** string_end
)
{
// All calls to some version of ON_AngleValue::CreateFrom*String(...) end up here.
if (nullptr != string_end && &string != string_end)
*string_end = string;
if (string_count < -1)
{
ON_ERROR("Invalid string_count parameter.");
return ON_ScaleValue::Unset;
}
if (nullptr == string || 0 == string_count || 0 == string[0] )
{
// empty string fails silently.
return ON_ScaleValue::Unset;
}
const wchar_t* left_side_string_end = nullptr;
ON_ParseSettings left_side_parse_settings = parse_settings;
const ON_LengthValue left_side = ON_LengthValue::CreateFromSubString( left_side_parse_settings, string, string_count, &left_side_string_end);
if (left_side.IsUnset() || nullptr == left_side_string_end || !(string < left_side_string_end))
return ON_ScaleValue::Unset;
const size_t left_side_count = left_side_string_end - string;
if ( -1 != string_count )
{
string_count -= (int)left_side_count;
// 0 = string_count may be ok - conditions checked later.
if (string_count < 0)
{
ON_ERROR("Invalid input parameters.");
return ON_ScaleValue::Unset;
}
}
const double left_side_length = left_side.Length(ON::LengthUnitSystem::None);
const ON::LengthUnitSystem left_side_unit_system = left_side.LengthUnitSystem().UnitSystem();
// look for equal, colon or fraction;
ON_ScaleValue::ScaleStringFormat format_preference = ON_ScaleValue::ScaleStringFormat::Unset;
const wchar_t* separator_string = left_side_string_end;
const wchar_t* separator_string_end = separator_string;
for (int i = 0; -1 == string_count || i < string_count; i++)
{
if (parse_settings.IsInteriorWhiteSpace(separator_string[i]))
continue;
switch (separator_string[i])
{
case ':':
format_preference = ON_ScaleValue::ScaleStringFormat::RatioFormat;
break;
case '=':
format_preference = ON_ScaleValue::ScaleStringFormat::EquationFormat;
break;
case '/':
format_preference = ON_ScaleValue::ScaleStringFormat::FractionFormat;
break;
}
if (ON_ScaleValue::ScaleStringFormat::Unset != format_preference)
{
for (i++; -1 == string_count || i < string_count; i++)
{
if (false == parse_settings.IsInteriorWhiteSpace(separator_string[i]))
break;
}
}
separator_string_end = separator_string + i;
break;
}
const size_t separator_count = separator_string_end - separator_string;
if ( -1 != string_count )
{
string_count -= (int)separator_count;
if (string_count <= 0)
{
ON_ERROR("Invalid input parameters.");
return ON_ScaleValue::Unset;
}
}
const wchar_t* right_side_string = separator_string_end;
const wchar_t* right_side_string_end = right_side_string;
ON_LengthValue right_side;
if (ON_ScaleValue::ScaleStringFormat::Unset == format_preference)
{
// A single value scanned.
if (ON::LengthUnitSystem::None != left_side_unit_system)
return ON_ScaleValue::Unset;
right_side = ON_LengthValue::Create(1.0, ON::LengthUnitSystem::None, 0, ON_LengthValue::StringFormat::ExactDecimal);
format_preference
= (left_side_length == floor(left_side_length) && ON::LengthUnitSystem::None == left_side_unit_system)
? ON_ScaleValue::ScaleStringFormat::RatioFormat
: ON_ScaleValue::ScaleStringFormat::EquationFormat;
}
else
{
// parse right side
ON_ParseSettings right_side_parse_settings = parse_settings;
right_side_parse_settings.SetParseLeadingWhiteSpace(false);
right_side = ON_LengthValue::CreateFromSubString( right_side_parse_settings, right_side_string, string_count, &right_side_string_end );
}
if (right_side.IsUnset())
{
ON_ERROR("Invalid input parameters.");
return ON_ScaleValue::Unset;
}
ON_ScaleValue scale_value = ON_ScaleValue::Create(
left_side,
right_side,
format_preference
);
if (scale_value.IsUnset() || !(right_side_string_end > string))
{
ON_ERROR("Invalid input parameters.");
return ON_ScaleValue::Unset;
}
scale_value.m_scale_as_string = ON_wString(string, (int)(right_side_string_end - string));
scale_value.m_scale_as_string.TrimLeftAndRight();
if (nullptr != string_end)
*string_end = right_side_string_end;
return scale_value;
}
static double ON_InternalDefuzz( double rel_tol, double x)
{
if (!(rel_tol >= 4.0*ON_EPSILON))
rel_tol = 4.0*ON_EPSILON;
const double s = 256.0;
const double a = s*fabs(x);
if (!(a > 255.0))
return x;
double ia = floor(a);
double fa = a - ia;
if (fa > 0.5)
ia += 1.0;
const double y
= (fabs(a - ia) <= a*rel_tol)
? (((x < 0.0) ? -ia : ia)/s)
: x;
return y;
}
static double ON_InternalQuotient(
double rel_tol,
double numerator,
double denominator
)
{
if (numerator == numerator && denominator != 0.0)
{
const double r = ON_InternalDefuzz(rel_tol, numerator / denominator);
const double s = (numerator != 0.0) ? ON_InternalDefuzz(rel_tol, denominator / numerator ) : 0.0;
return
(s >= 2.0 && s == floor(s))
? (1.0 / s)
: r;
}
ON_ERROR("Invalid input.");
return ON_DBL_QNAN;
}
static bool ON_Internal_RemoveCommonFactors(const double rel_zero_tol, const double factor, double& x, double& y)
{
if (false == (x > 0.0 && y > 0.0 && factor > 0.0 && rel_zero_tol >= 0.0 && rel_zero_tol < 0.01))
{
ON_ERROR("Invalid input parameters.");
return false;
}
if (1.0 == factor)
return true; // nothing to do
if (!(x < 1.0 / ON_EPSILON && y < 1.0 / ON_EPSILON))
{
// x or y is too big for this code to work with IEEE double arithmetic.
return false;
}
const double fat_eps = 4.0*ON_EPSILON;
const double factor_tol = (rel_zero_tol > fat_eps) ? rel_zero_tol : fat_eps;
if (!(factor > x*factor_tol && factor > y*factor_tol))
{
// factor is too small for the ON_ScaleValue context where this
// function is designed to be used.
return false;
}
bool rc = false;
for (;;)
{
const double s = ON_InternalQuotient(factor_tol,x,factor);
if (!(s == floor(s)))
break;
const double t = ON_InternalQuotient(factor_tol,y,factor);
if (!(t == floor(t)))
break;
if (factor >= 1.0)
{
if (!(s < x && t < y))
break;
}
rc = true;
x = s;
y = t;
if ( !(factor >= 2.0 && x >= factor*(1.0-fat_eps) && y >= factor*(1.0-fat_eps)) )
break;
}
return rc;
}
static bool ON_Internal_SimplifyRatio(double& x, double& y)
{
// returns true if the output values of x and y are both integers.
const double rel_zero_tol = 1.0e-14;
if (!(x > 0.0 && y > 0.0))
return false;
if (fabs(x / y - 1.0) <= rel_zero_tol || fabs(y / x - 1.0) <= rel_zero_tol)
{
x = 1.0;
y = 1.0;
return true;
}
if ( x < y )
ON_Internal_RemoveCommonFactors(rel_zero_tol, x, x, y);
else if ( y < x )
ON_Internal_RemoveCommonFactors(rel_zero_tol, y, y, x);
const double factors[] = { 2.0, 3.0, 5.0 };
for ( size_t i = 0; i < sizeof(factors)/sizeof(factors[0]); i++ )
ON_Internal_RemoveCommonFactors(rel_zero_tol, factors[i], y, x);
return (x == floor(x) && y == floor(y));
}
ON_ScaleValue ON_ScaleValue::Create(
const class ON_LengthValue& left_side_length,
const class ON_LengthValue& right_side_length,
ON_ScaleValue::ScaleStringFormat string_format_preference
)
{
ON_ScaleValue scale_value = ON_ScaleValue::Unset;
scale_value.m_left_length = left_side_length;
scale_value.m_right_length = right_side_length;
scale_value.m_string_format_preference = string_format_preference;
if (scale_value.m_left_length.IsUnset() || scale_value.m_right_length.IsUnset())
return scale_value;
ON::LengthUnitSystem left_length_unit_system = scale_value.m_left_length.LengthUnitSystem().UnitSystem();
ON::LengthUnitSystem right_length_unit_system = scale_value.m_right_length.LengthUnitSystem().UnitSystem();
const double left_length = scale_value.m_left_length.Length(left_length_unit_system);
const double right_length = scale_value.m_right_length.Length(right_length_unit_system);
if (false == (left_length > 0.0 && right_length > 0.0))
{
// one or both of left_length and right_length is a NAN or negative
ON_ERROR("Invalid input");
return scale_value;
}
// Dale Lear http://mcneel.myjetbrains.com/youtrack/issue/RH-34709
// Turns out that stripping unit system information when one side is None
// was a bad idea. After using ON_ScaleValue in the ON_DimStyle
// class, preserving the unit systems works better when one
// is None.
////if (ON::LengthUnitSystem::None == left_length_unit_system
//// && ON::LengthUnitSystem::None != right_length_unit_system
//// )
////{
//// // remove units from right side
//// scale_value.m_right_length = scale_value.m_right_length.RemoveUnitSystem();
//// right_length_unit_system = ON::LengthUnitSystem::None;
////}
////if (ON::LengthUnitSystem::None != left_length_unit_system
//// && ON::LengthUnitSystem::None == right_length_unit_system
//// )
////{
//// // remove units from left side
//// scale_value.m_left_length = scale_value.m_left_length.RemoveUnitSystem();
//// left_length_unit_system = ON::LengthUnitSystem::None;
////}
const double left_to_right_scale = ON::UnitScale(scale_value.m_left_length.LengthUnitSystem(), scale_value.m_right_length.LengthUnitSystem());
const double right_to_left_scale = ON::UnitScale(scale_value.m_right_length.LengthUnitSystem(), scale_value.m_left_length.LengthUnitSystem());
if (false == (left_to_right_scale > 0.0 && right_to_left_scale > 0.0 && 1.0 == ON_InternalDefuzz(1.0e-14,left_to_right_scale*right_to_left_scale)) )
{
// one or both of left_to_right_scale and right_to_left_scale is a NAN or not set correctly
ON_ERROR("Invalid input");
ON_InternalDefuzz(1.0e-14, left_to_right_scale*right_to_left_scale);
return scale_value;
}
double x = left_length;
double y = right_length;
if (left_to_right_scale > right_to_left_scale)
x *= left_to_right_scale;
else if ( right_to_left_scale > left_to_right_scale)
y *= right_to_left_scale;
if (false == (x > 0.0 && y > 0.0))
{
// one or both of x and y is a NAN or not set correctly
ON_ERROR("Invalid input");
return scale_value;
}
if (!ON_Internal_SimplifyRatio(x, y))
string_format_preference = ON_ScaleValue::ScaleStringFormat::EquationFormat;
scale_value.m_left_to_right_scale = ON_InternalQuotient(0.0,x,y);
scale_value.m_right_to_left_scale = ON_InternalQuotient(0.0,y,x);
if (scale_value.m_left_to_right_scale >= 2.0 && floor(scale_value.m_left_to_right_scale) == scale_value.m_left_to_right_scale)
{
scale_value.m_right_to_left_scale = 1.0 / scale_value.m_left_to_right_scale;
}
else if (scale_value.m_right_to_left_scale >= 2.0 && floor(scale_value.m_right_to_left_scale) == scale_value.m_right_to_left_scale)
{
scale_value.m_left_to_right_scale = 1.0 / scale_value.m_right_to_left_scale;
}
switch (string_format_preference)
{
case ON_ScaleValue::ScaleStringFormat::RatioFormat:
// Do NOT dumb down the %.17g in the format string.
// If you don't like the format, then make your own string and call
// ON_ScaleValue::CreateFromString(). It is critical that
// the values in the formatted string exactly match the double values.
scale_value.m_scale_as_string.Format(L"%.17g:%.17g", x, y);
break;
case ON_ScaleValue::ScaleStringFormat::FractionFormat:
// Do NOT dumb down the %.17g in the format string.
// If you don't like the format, then make your own string and call
// ON_ScaleValue::CreateFromString(). It is critical that
// the values in the formatted string exactly match the double values.
scale_value.m_scale_as_string.Format(L"%.17g/%.17g", x, y);
break;
case ON_ScaleValue::ScaleStringFormat::EquationFormat:
// no break here
default:
scale_value.m_scale_as_string.Format(L"%ls = %ls",
scale_value.m_left_length.LengthAsStringPointer(),
scale_value.m_right_length.LengthAsStringPointer()
);
break;
}
scale_value.m_string_format_preference = string_format_preference;
return scale_value;
}
double ON_ScaleValue::LeftToRightScale() const
{
return m_left_to_right_scale;
}
double ON_ScaleValue::RightToLeftScale() const
{
return m_right_to_left_scale;
}
const class ON_LengthValue& ON_ScaleValue::LeftLengthValue() const
{
return m_left_length;
}
const class ON_LengthValue& ON_ScaleValue::RightLengthValue() const
{
return m_right_length;
}
const ON_wString& ON_ScaleValue::ScaleAsString() const
{
m_scale_as_string.IsValid(true);
return m_scale_as_string;
}
const wchar_t* ON_ScaleValue::ScaleAsStringPointer() const
{
m_scale_as_string.IsValid(true);
return static_cast<const wchar_t*>(m_scale_as_string);
}
bool ON_ScaleValue::IsUnset() const
{
return IsSet() ? false : true;
}
bool ON_ScaleValue::IsSet() const
{
return (
m_left_length.IsSet()
&& m_right_length.IsSet()
&& (ON_UNSET_VALUE < m_left_to_right_scale && m_left_to_right_scale < ON_UNSET_POSITIVE_VALUE)
&& (ON_UNSET_VALUE < m_right_to_left_scale && m_right_to_left_scale < ON_UNSET_POSITIVE_VALUE)
&& m_scale_as_string.IsNotEmpty()
);
}
bool ON_ScaleValue::Write(
class ON_BinaryArchive& archive
) const
{
const int content_version = 1;
if (!archive.BeginWrite3dmAnonymousChunk(content_version))
return false;
bool rc = false;
for (;;)
{
// content_version = 0
if (!archive.WriteDouble(m_left_to_right_scale))
break;
if (!archive.WriteDouble(m_right_to_left_scale))
break;
if (!archive.WriteInt(m_context_locale_id))
break;
if (!archive.WriteInt(static_cast<unsigned int>(m_context_length_unit_system)))
break;
if (!archive.WriteInt(static_cast<unsigned int>(m_context_angle_unit_system)))
break;
m_scale_as_string.IsValid(true);
if (!archive.WriteString(m_scale_as_string))
break;
if (!m_left_length.Write(archive))
break;
if (!m_right_length.Write(archive))
break;
// content version 1 added m_string_format_preference
unsigned int u = static_cast<unsigned char>(m_string_format_preference);
if (!archive.WriteInt(u))
break;
rc = true;
break;
}
if (!archive.EndWrite3dmChunk())
rc = false;
return rc;
}
ON_ScaleValue::ScaleStringFormat ON_ScaleValue::ScaleStringFormatFromUnsigned(
unsigned int scale_string_format_as_unsigned
)
{
switch (scale_string_format_as_unsigned)
{
ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::None);
ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::RatioFormat);
ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::EquationFormat);
ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::FractionFormat);
ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::Unset);
}
ON_ERROR("Invalid scale_string_format_as_unsigned value.");
return (ON_ScaleValue::Unset.m_string_format_preference);
}
bool ON_ScaleValue::Read(
class ON_BinaryArchive& archive
)
{
*this = ON_ScaleValue::Unset;
int content_version = 0;
if (!archive.BeginRead3dmAnonymousChunk(&content_version))
return false;
bool rc = false;
for (;;)
{
if (!archive.ReadDouble(&m_left_to_right_scale))
break;
if (!archive.ReadDouble(&m_right_to_left_scale))
break;
if (!archive.ReadInt(&m_context_locale_id))
break;
unsigned int u;
u = static_cast<unsigned int>(m_context_length_unit_system);
if (!archive.ReadInt(&u))
break;
m_context_length_unit_system = ON::LengthUnitSystemFromUnsigned(u);
u = static_cast<unsigned int>(m_context_angle_unit_system);
if (!archive.ReadInt(&u))
break;
m_context_angle_unit_system = ON::AngleUnitSystemFromUnsigned(u);
if (!archive.ReadString(m_scale_as_string))
break;
if (!m_left_length.Read(archive))
break;
if (!m_right_length.Read(archive))
break;
if (content_version >= 1)
{
// content version 1 added m_string_format_preference
u = static_cast<unsigned char>(m_string_format_preference);
if (!archive.ReadInt(&u))
break;
m_string_format_preference = ON_ScaleValue::ScaleStringFormatFromUnsigned(u);
}
rc = true;
break;
}
if (!archive.EndRead3dmChunk())
rc = false;
return rc;
}
ON_LengthValue::StringFormat ON_LengthValue::LengthStringFormat() const
{
return m_string_format;
}
unsigned int ON_LengthValue::ContextLocaleId() const
{
return m_context_locale_id;
}
ON::AngleUnitSystem ON_LengthValue::ContextAngleUnitSystem() const
{
return m_context_angle_unit_system;
}
const ON_ParseSettings ON_LengthValue::LengthStringParseSettings() const
{
return ON_ParseSettings(m_length_unit_system.UnitSystem(), m_context_angle_unit_system, m_context_locale_id);
}
ON_ParseSettings const ON_AngleValue::AngleStringParseSettings() const
{
return ON_ParseSettings(m_context_length_unit_system, m_angle_unit_system, m_context_locale_id);
}
const ON_ParseSettings ON_ScaleValue::ScaleStringParseSettings() const
{
return ON_ParseSettings(m_context_length_unit_system, m_context_angle_unit_system, m_context_locale_id);
}
void ON_ScaleValue::SwapLeftAndRight()
{
double x = m_left_to_right_scale;
m_left_to_right_scale = m_right_to_left_scale;
m_right_to_left_scale = x;
ON_LengthValue t = m_left_length;
m_left_length = m_right_length;
m_right_length = t;
// TODO: properly reverse string
m_scale_as_string = ON_ScaleValue::Create(m_left_length,m_right_length,ON_ScaleValue::ScaleStringFormat::None).ScaleAsString();
}
const ON_SHA1_Hash ON_ScaleValue::ContentHash() const
{
ON_SHA1 sha1;
sha1.AccumulateDouble(m_left_to_right_scale);
sha1.AccumulateDouble(m_right_to_left_scale);
sha1.AccumulateUnsigned32(m_context_locale_id);
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_context_length_unit_system));
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_context_angle_unit_system));
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_string_format_preference));
m_scale_as_string.IsValid(true);
sha1.AccumulateString(m_scale_as_string);
ON_SHA1_Hash h = m_left_length.ContentHash();
sha1.AccumulateSubHash(h);
h = m_right_length.ContentHash();
sha1.AccumulateSubHash(h);
return sha1.Hash();
}
int ON_ScaleValue::Compare(
const ON_ScaleValue& lhs,
const ON_ScaleValue& rhs
)
{
double x = lhs.RightToLeftScale();
double y = rhs.RightToLeftScale();
if (x < y)
return -1;
if (x > y)
return 1;
return ON_SHA1_Hash::Compare(lhs.ContentHash(), rhs.ContentHash());
}
const ON_SHA1_Hash ON_LengthValue::ContentHash() const
{
ON_SHA1 sha1;
sha1.AccumulateUnsigned32(m_context_locale_id);
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_context_angle_unit_system));
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_string_format));
sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_length_unit_system.UnitSystem()));
m_length_as_string.IsValid(true);
sha1.AccumulateString(m_length_as_string);
return sha1.Hash();
}
int ON_LengthValue::Compare(
const ON_LengthValue& lhs,
const ON_LengthValue& rhs
)
{
if (lhs.m_length < rhs.m_length)
return -1;
if (lhs.m_length > rhs.m_length)
return 1;
return ON_SHA1_Hash::Compare(lhs.ContentHash(), rhs.ContentHash());
}