diff --git a/opennurbs_annotationbase.h b/opennurbs_annotationbase.h
index 09a47ef2..c560b646 100644
--- a/opennurbs_annotationbase.h
+++ b/opennurbs_annotationbase.h
@@ -960,6 +960,12 @@ public:
//---------------------------
// ON_Object overrides
+ // virtual ON_Object::SizeOf override
+ unsigned int SizeOf() const override;
+
+ // virtual ON_Object::DataCRC override
+ ON__UINT32 DataCRC(ON__UINT32 current_remainder) const override;
+
bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
/*
diff --git a/opennurbs_dimension.cpp b/opennurbs_dimension.cpp
index 484fb035..a8bf0539 100644
--- a/opennurbs_dimension.cpp
+++ b/opennurbs_dimension.cpp
@@ -196,7 +196,13 @@ ON_TextContent* ON_Dimension::RebuildDimensionText(
}
else
{
- displaytext = displaytext + UserText();
+ // October 2, 2025 - Tim
+ // As far as I can tell RebuildDimensionText only gets called with
+ // expandanglebrackets set to false from the acad export plugin otherwise
+ // I would be jumpy about making these changes.
+ // These changes fix https://mcneel.myjetbrains.com/youtrack/issue/RH-89323
+
+ displaytext = PlainText();
if (dimstyle->Prefix().IsNotEmpty() || dimstyle->Suffix().IsNotEmpty())
{
int ci = displaytext.Find(L"<>");
@@ -206,7 +212,7 @@ ON_TextContent* ON_Dimension::RebuildDimensionText(
if (displaytext.Length() > ci + 2)
right = displaytext.Right(displaytext.Length() - ci - 2);
displaytext = displaytext.Left(ci);
- displaytext = displaytext + dimstyle->Prefix();
+ displaytext = dimstyle->Prefix() + displaytext;
displaytext = displaytext + L"<>";
displaytext = displaytext + dimstyle->Suffix();
displaytext = displaytext + right;
diff --git a/opennurbs_font.cpp b/opennurbs_font.cpp
index e4944e7e..d255ac03 100644
--- a/opennurbs_font.cpp
+++ b/opennurbs_font.cpp
@@ -5554,7 +5554,7 @@ const ON_ClassArray< ON_FontFaceQuartet >& ON_FontList::QuartetList() const
&& ssw_dex.j >= 0 && ssw_dex.j < 2
&& ssw_dex.k >= 1 && ssw_dex.k < max_weight_dex
)
- ? fonts_by_ssw[ssw_dex.i][ssw_dex.k][ssw_dex.k]
+ ? fonts_by_ssw[ssw_dex.i][ssw_dex.j][ssw_dex.k]
: nullptr;
if (nullptr != cleanf)
{
@@ -7638,7 +7638,7 @@ const ON_wString ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames(
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Heavy", L"Avenir Heavy", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-HeavyOblique", L"Avenir Heavy", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-Black", L"Avenir Black", ON_FontFaceQuartet::Member::Regular),
- Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-BlackOblique", L"Avenir-Black", ON_FontFaceQuartet::Member::Italic),
+ Internal_FakeWindowsLogfontName(L"Avenir", L"Avenir-BlackOblique", L"Avenir Black", ON_FontFaceQuartet::Member::Italic),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-UltraLight", L"Avenir Next Ultralight", ON_FontFaceQuartet::Member::Regular),
Internal_FakeWindowsLogfontName(L"Avenir Next", L"AvenirNext-UltraLightItalic", L"Avenir Next Ultralight", ON_FontFaceQuartet::Member::Italic),
diff --git a/opennurbs_instance.cpp b/opennurbs_instance.cpp
index 22666c51..deb6965f 100644
--- a/opennurbs_instance.cpp
+++ b/opennurbs_instance.cpp
@@ -1761,6 +1761,21 @@ bool ON_InstanceRef::IsValid( ON_TextLog* text_log ) const
return true;
}
+unsigned int ON_InstanceRef::SizeOf() const
+{
+ unsigned int sz = ON_Geometry::SizeOf();
+ sz += (sizeof(*this) - sizeof(ON_Geometry));
+ return sz;
+}
+
+ON__UINT32 ON_InstanceRef::DataCRC(ON__UINT32 current_remainder) const
+{
+ current_remainder = ON_CRC32(current_remainder, sizeof(m_instance_definition_uuid), &m_instance_definition_uuid);
+ current_remainder = ON_CRC32(current_remainder, sizeof(m_xform), &m_xform);
+ current_remainder = ON_CRC32(current_remainder, sizeof(m_bbox), &m_bbox);
+ return current_remainder;
+}
+
bool ON_InstanceRef::Write(
ON_BinaryArchive& binary_archive
) const
diff --git a/opennurbs_instance.h b/opennurbs_instance.h
index f4271587..c1ef7ed0 100644
--- a/opennurbs_instance.h
+++ b/opennurbs_instance.h
@@ -657,14 +657,22 @@ public:
// virtual ON_Object overrides
//
bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
+
bool Write(
ON_BinaryArchive& binary_archive
) const override;
+
bool Read(
ON_BinaryArchive& binary_archive
) override;
ON::object_type ObjectType() const override;
+ // virtual ON_Object::SizeOf override
+ unsigned int SizeOf() const override;
+
+ // virtual ON_Object::DataCRC override
+ ON__UINT32 DataCRC(ON__UINT32 current_remainder) const override;
+
/////////////////////////////////////////////////////////////
//
// virtual ON_Geometry overrides
diff --git a/opennurbs_internal_V2_annotation.cpp b/opennurbs_internal_V2_annotation.cpp
index 29b03037..b2ff33d4 100644
--- a/opennurbs_internal_V2_annotation.cpp
+++ b/opennurbs_internal_V2_annotation.cpp
@@ -5645,6 +5645,27 @@ void ON_TextDot::EmergencyDestroy()
m_display_bits = 0;
}
+unsigned int ON_TextDot::SizeOf() const
+{
+ unsigned int sz = ON_Geometry::SizeOf();
+ sz += (sizeof(*this) - sizeof(ON_Geometry));
+ sz += m_primary_text.SizeOf();
+ sz += m_secondary_text.SizeOf();
+ sz += m_font_face.SizeOf();
+ return sz;
+}
+
+ON__UINT32 ON_TextDot::DataCRC(ON__UINT32 current_remainder) const
+{
+ current_remainder = m_center_point.DataCRC(current_remainder);
+ current_remainder = m_primary_text.DataCRC(current_remainder);
+ current_remainder = m_secondary_text.DataCRC(current_remainder);
+ current_remainder = m_font_face.DataCRC(current_remainder);
+ current_remainder = ON_CRC32(current_remainder, sizeof(m_display_bits), &m_display_bits);
+ current_remainder = ON_CRC32(current_remainder, sizeof(m_height_in_points), &m_height_in_points);
+ return current_remainder;
+}
+
bool ON_TextDot::IsValid(
ON_TextLog* text_log
) const
diff --git a/opennurbs_mesh.cpp b/opennurbs_mesh.cpp
index 16f3147c..484a14b3 100644
--- a/opennurbs_mesh.cpp
+++ b/opennurbs_mesh.cpp
@@ -470,6 +470,7 @@ unsigned int ON_Mesh::SizeOf() const
{
unsigned int sz = ON_Geometry::SizeOf();
sz += m_V.SizeOfArray();
+ sz += m_dV.SizeOfArray();
sz += m_F.SizeOfArray();
sz += m_N.SizeOfArray();
sz += m_FN.SizeOfArray();
diff --git a/opennurbs_mesh_ngon.cpp b/opennurbs_mesh_ngon.cpp
index 9207b215..e2f0ac6c 100644
--- a/opennurbs_mesh_ngon.cpp
+++ b/opennurbs_mesh_ngon.cpp
@@ -5241,10 +5241,10 @@ ON_Plane ON_Plane::FromPointList(
//
ON_3dVector N(ON_3dVector::ZeroVector);
ON_3dVector X(ON_3dVector::UnsetVector);
+ const unsigned int index_0123[4] = { 0,1,2,3 };
if ( point_index_count <= 4 )
{
// use "standard" face normal for quads and triangles.
- const unsigned int index_0123[4] = {0,1,2,3};
if ( 0 == point_index_list )
point_index_list = index_0123;
Pi0 = point_list[point_index_list[point_stride*(point_count-1)]];
diff --git a/opennurbs_pointcloud.cpp b/opennurbs_pointcloud.cpp
index ab3b33c8..681df0c4 100644
--- a/opennurbs_pointcloud.cpp
+++ b/opennurbs_pointcloud.cpp
@@ -132,6 +132,7 @@ void ON_PointCloud::Dump( ON_TextLog& dump ) const
const bool bHasNormals = HasPointNormals();
const bool bHasColors = HasPointColors();
const bool bHasHiddenPoints = (HiddenPointCount() > 0);
+ const bool bHasPointValues = HasPointValues();
const int point_count = m_P.Count();
dump.Print("ON_PointCloud: %d points\n",point_count);
dump.PushIndent();
@@ -156,6 +157,11 @@ void ON_PointCloud::Dump( ON_TextLog& dump ) const
dump.Print(", color = ");
dump.PrintRGB(m_C[i]);
}
+ if (bHasPointValues) // 20-Jan-2026 show point value
+ {
+ dump.Print(", value = ");
+ dump.Print(m_V[i]);
+ }
if (bHasHiddenPoints && m_H[i])
{
dump.Print(" (hidden)");
diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h
index 9d937477..7322ef81 100644
--- a/opennurbs_public_version.h
+++ b/opennurbs_public_version.h
@@ -6,17 +6,17 @@
// To update version numbers, edit ..\build\build_dates.msbuild
#define RMA_VERSION_MAJOR 8
-#define RMA_VERSION_MINOR 26
+#define RMA_VERSION_MINOR 29
////////////////////////////////////////////////////////////////
//
// These are set automatically by the build system as the
// first step in each build.
//
-#define RMA_VERSION_YEAR 2025
-#define RMA_VERSION_MONTH 12
-#define RMA_VERSION_DATE 15
-#define RMA_VERSION_HOUR 19
+#define RMA_VERSION_YEAR 2026
+#define RMA_VERSION_MONTH 3
+#define RMA_VERSION_DATE 4
+#define RMA_VERSION_HOUR 11
#define RMA_VERSION_MINUTE 0
////////////////////////////////////////////////////////////////
@@ -35,20 +35,20 @@
// 3 = build system release build
#define RMA_VERSION_BRANCH 0
-#define VERSION_WITH_COMMAS 8,26,25349,19000
-#define VERSION_WITH_PERIODS 8.26.25349.19000
-#define COPYRIGHT "Copyright (C) 1993-2025, Robert McNeel & Associates. All Rights Reserved."
+#define VERSION_WITH_COMMAS 8,29,26063,11000
+#define VERSION_WITH_PERIODS 8.29.26063.11000
+#define COPYRIGHT "Copyright (C) 1993-2026, Robert McNeel & Associates. All Rights Reserved."
#define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library."
#define RMA_VERSION_NUMBER_MAJOR_STRING "8"
#define RMA_VERSION_NUMBER_MAJOR_WSTRING L"8"
#define RMA_PREVIOUS_VERSION_NUMBER_MAJOR_WSTRING L"7"
-#define RMA_VERSION_NUMBER_SR_STRING "SR26"
-#define RMA_VERSION_NUMBER_SR_WSTRING L"SR26"
+#define RMA_VERSION_NUMBER_SR_STRING "SR29"
+#define RMA_VERSION_NUMBER_SR_WSTRING L"SR29"
-#define RMA_VERSION_WITH_PERIODS_STRING "8.26.25349.19000"
-#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.26.25349.19000"
+#define RMA_VERSION_WITH_PERIODS_STRING "8.29.26063.11000"
+#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.29.26063.11000"
diff --git a/opennurbs_quaternion.cpp b/opennurbs_quaternion.cpp
index 776d200f..96356bd8 100644
--- a/opennurbs_quaternion.cpp
+++ b/opennurbs_quaternion.cpp
@@ -621,7 +621,7 @@ bool ON_Quaternion::GetYawPitchRoll(double& yaw, double& pitch, double& roll) co
ON_Xform X;
bool rc = GetRotation(X);
if (rc)
- rc = GetYawPitchRoll(yaw, pitch, roll);
+ rc = X.GetYawPitchRoll(yaw, pitch, roll);
return rc;
}
@@ -630,7 +630,7 @@ bool ON_Quaternion::GetEulerZYZ(double& alpha, double& beta, double& gamma) con
ON_Xform X;
bool rc = GetRotation(X);
if (rc)
- rc = GetEulerZYZ(alpha, beta, gamma);
+ rc = X.GetEulerZYZ(alpha, beta, gamma);
return rc;
}
diff --git a/opennurbs_sectionstyle.cpp b/opennurbs_sectionstyle.cpp
index d82685ad..d1ca77a4 100644
--- a/opennurbs_sectionstyle.cpp
+++ b/opennurbs_sectionstyle.cpp
@@ -111,6 +111,7 @@ ON_SectionStyle& ON_SectionStyle::operator=(const ON_SectionStyle& other)
}
return *this;
}
+
bool ON_SectionStyle::IsValid( ON_TextLog* text_log ) const
{
if (false == ON_ModelComponent::IsValid(text_log))
@@ -134,6 +135,43 @@ bool ON_SectionStyle::IsValid( ON_TextLog* text_log ) const
return true;
}
+const ON_SectionStyle* ON_SectionStyle::FromModelComponentRef(
+ const class ON_ModelComponentReference& model_component_reference,
+ const ON_SectionStyle* none_return_value
+)
+{
+ const ON_SectionStyle* p = ON_SectionStyle::Cast(model_component_reference.ModelComponent());
+ return (nullptr != p) ? p : none_return_value;
+}
+
+bool ON_SectionStyle::UpdateReferencedComponents(
+ const class ON_ComponentManifest& source_manifest,
+ const class ON_ComponentManifest& destination_manifest,
+ const class ON_ManifestMap& manifest_map
+)
+{
+ bool rc = true;
+ // Update hatch pattern index
+ int hatch_index = HatchIndex();
+ if (hatch_index >= 0)
+ {
+ int destination_hatch_index = -1;
+ if (manifest_map.GetAndValidateDestinationIndex(ON_ModelComponent::Type::HatchPattern, hatch_index, destination_manifest, &destination_hatch_index))
+ {
+ hatch_index = destination_hatch_index;
+ }
+ else
+ {
+ ON_ERROR("Unable to update hatch pattern reference.");
+ rc = false;
+ hatch_index = DefaultSectionStylePrivate.m_hatch_index;
+ }
+ SetHatchIndex(hatch_index);
+ }
+ return rc;
+}
+
+
// 12 Aug 2021 S. Baer
// When adding new fields written to 3dm files, always add information to this
// Dump function. Dump is used by the opennurbs file testing framework to
diff --git a/opennurbs_sectionstyle.h b/opennurbs_sectionstyle.h
index 87f4b5f0..aa5eb6d5 100644
--- a/opennurbs_sectionstyle.h
+++ b/opennurbs_sectionstyle.h
@@ -31,32 +31,52 @@ public:
static const ON_SectionStyle Unset; // index = ON_UNSET_INT_INDEX, id = nil
/*
- Description:
- Tests that name is set and there is at least one non-zero length segment
+ Parameters:
+ model_component_reference - [in]
+ none_return_value - [in]
+ value to return if ON_SectionStyle::Cast(model_component_ref.ModelComponent())
+ is nullptr
+ Returns:
+ If ON_SectionStyle::Cast(model_component_ref.ModelComponent()) is not nullptr,
+ that pointer is returned. Otherwise, none_return_value is returned.
+ */
+ static const ON_SectionStyle* FromModelComponentRef(
+ const class ON_ModelComponentReference& model_component_reference,
+ const ON_SectionStyle* none_return_value
+ );
+
+ bool UpdateReferencedComponents(
+ const class ON_ComponentManifest& source_manifest,
+ const class ON_ComponentManifest& destination_manifest,
+ const class ON_ManifestMap& manifest_map
+ ) override;
+
+ /*
+ Description:
+ Tests that name is set and there is at least one non-zero length segment
*/
bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
void Dump( ON_TextLog& ) const override; // for debugging
/*
- Description:
- Write to file (serialize definition to binary archive)
+ Description:
+ Write to file (serialize definition to binary archive)
*/
bool Write(ON_BinaryArchive&) const override;
/*
- Description:
- Read from file (restore definition from binary archive)
+ Description:
+ Read from file (restore definition from binary archive)
*/
bool Read(ON_BinaryArchive&) override;
-
/*
- Description:
- Test only the section style attributes below for equality. Does not
- perform any testing of the ON_ModelComponent fields
- Parameters:
- other: other section style to compare against this
+ Description:
+ Test only the section style attributes below for equality. Does not
+ perform any testing of the ON_ModelComponent fields
+ Parameters:
+ other: other section style to compare against this
*/
bool SectionAttributesEqual(const ON_SectionStyle& other) const;
@@ -89,7 +109,6 @@ public:
SectionBackgroundFillMode BackgroundFillMode() const;
void SetBackgroundFillMode(SectionBackgroundFillMode mode);
-
// Custom background fill color. If unset (default), the object's color or
// material is used for a fill
ON_Color BackgroundFillColor(bool print) const;
diff --git a/opennurbs_subd.h b/opennurbs_subd.h
index f329e63d..6fd02df2 100644
--- a/opennurbs_subd.h
+++ b/opennurbs_subd.h
@@ -46,6 +46,7 @@ enum class ON_SubDGetControlNetMeshPriority : unsigned char
};
+#pragma region RH_C_SHARED_ENUM [ON_SubDTextureCoordinateType] [Rhino.Geometry.SubDTextureCoordinateType] [byte]
///
/// ON_SubDTextureCoordinateType identifies the way ON_SubDMeshFragment texture coordinates are set from an ON_SubDFace.
///
@@ -91,6 +92,7 @@ enum class ON_SubDTextureCoordinateType : unsigned char
///
FromMapping = 7,
};
+#pragma endregion
#pragma region RH_C_SHARED_ENUM [ON_SubDVertexTag] [Rhino.Geometry.SubDVertexTag] [byte]
///
diff --git a/opennurbs_text.cpp b/opennurbs_text.cpp
index dcb1af29..05d75d15 100644
--- a/opennurbs_text.cpp
+++ b/opennurbs_text.cpp
@@ -537,6 +537,14 @@ const ON_wString ON_TextContent::Internal_GetPlainText(bool evaluate_fields, boo
map.j = plaintext.Length();
map.k = (int)wcslen(str);
}
+
+ // December 3, 2025 - Tim
+ // Fix for https://mcneel.myjetbrains.com/youtrack/issue/RH-90830
+ // Add a space before the fraction if is not the first
+ // chunk of text.
+ if (0 < ri && nullptr != wcschr(str, '/'))
+ plaintext += " ";
+
plaintext += str;
}
}
@@ -2524,9 +2532,22 @@ bool ON_TextContent::FormatLength(
wchar_t decimal_char,
ON_wString & output)
{
+ // Record length before appending so we only replace periods in the newly added portion
+ const int length_before = output.Length();
bool rc = ON_NumberFormatter::FormatLength( distance, output_lengthdisplay, round_off, resolution, zero_suppress, bracket_fractions, output);
if (rc && ON_wString::DecimalAsPeriod != decimal_char)
- output.Replace(ON_wString::DecimalAsPeriod, decimal_char);
+ {
+ // Only replace periods in the newly appended portion, not in any pre-existing text RH-83506
+ wchar_t* str = output.Array();
+ if (nullptr != str)
+ {
+ for (int i = length_before; i < output.Length(); i++)
+ {
+ if (str[i] == ON_wString::DecimalAsPeriod)
+ str[i] = decimal_char;
+ }
+ }
+ }
return rc;
}
@@ -2544,9 +2565,22 @@ bool ON_TextContent::FormatAngleStringDMS(
int resolution,
ON_wString& formatted_string)
{
+ // Record length before appending so we only replace periods in the newly added portion
+ const int length_before = formatted_string.Length();
bool rc = ON_NumberFormatter::FormatAngleStringDMS(angle_degrees, resolution, formatted_string);
if (rc && ON_wString::DecimalAsPeriod != decimal_char)
- formatted_string.Replace(ON_wString::DecimalAsPeriod, decimal_char);
+ {
+ // Only replace periods in the newly appended portion, not in any pre-existing text RH-83506
+ wchar_t* str = formatted_string.Array();
+ if (nullptr != str)
+ {
+ for (int i = length_before; i < formatted_string.Length(); i++)
+ {
+ if (str[i] == ON_wString::DecimalAsPeriod)
+ str[i] = decimal_char;
+ }
+ }
+ }
return rc;
}
@@ -2558,9 +2592,22 @@ bool ON_TextContent::FormatAngleStringDecimal(
wchar_t decimal_char,
ON_wString& formatted_string)
{
+ // Record length before appending so we only replace periods in the newly added portion
+ const int length_before = formatted_string.Length();
bool rc = ON_NumberFormatter::FormatAngleStringDecimal(angle_radians, resolution, roundoff, zero_suppression, formatted_string);
if (rc && ON_wString::DecimalAsPeriod != decimal_char)
- formatted_string.Replace(ON_wString::DecimalAsPeriod, decimal_char);
+ {
+ // Only replace periods in the newly appended portion, not in any pre-existing text RH-83506
+ wchar_t* str = formatted_string.Array();
+ if (nullptr != str)
+ {
+ for (int i = length_before; i < formatted_string.Length(); i++)
+ {
+ if (str[i] == ON_wString::DecimalAsPeriod)
+ str[i] = decimal_char;
+ }
+ }
+ }
return rc;
}
@@ -2623,6 +2670,8 @@ bool ON_TextContent::FormatAreaOrVolume(
ON_DimStyle::OBSOLETE_length_format output_format = ON_DimStyle::OBSOLETE_length_format::Decimal;
+ // Record length before appending so we only replace periods in the newly added portion
+ const int length_before = formatted_string.Length();
bool rc = ON_NumberFormatter::FormatNumber(
value,
output_format,
@@ -2633,7 +2682,18 @@ bool ON_TextContent::FormatAreaOrVolume(
formatted_string);
if (rc && ON_wString::DecimalAsPeriod != decimal_char)
- formatted_string.Replace(ON_wString::DecimalAsPeriod, decimal_char);
+ {
+ // Only replace periods in the newly appended portion, not in any pre-existing text RH-83506
+ wchar_t* str = formatted_string.Array();
+ if (nullptr != str)
+ {
+ for (int i = length_before; i < formatted_string.Length(); i++)
+ {
+ if (str[i] == ON_wString::DecimalAsPeriod)
+ str[i] = decimal_char;
+ }
+ }
+ }
return rc;
}
diff --git a/opennurbs_textrun.cpp b/opennurbs_textrun.cpp
index 581893e2..86141c20 100644
--- a/opennurbs_textrun.cpp
+++ b/opennurbs_textrun.cpp
@@ -1048,6 +1048,250 @@ void ON_TextRun::SetUnicodeString(ON__UINT32*& dest, size_t count, const ON__UIN
}
}
+// 2026-02-10 - kike@mcneel.com : See RH-91129
+// ON_TextRun::WrapTextRun should be iterative instead of recursive.
+// But by now and to fix the issue the recursion level is now 128
+// And the recursive function ON_TextWrapper::WrapTextRun is lighter on stack than before.
+// Also the glyph extraction is done just once, so should be faster than before.
+class ON_TextWrapper
+{
+ const int max_recursion_level = 128;
+
+ const ON_TextRun& text_run;
+ ON_TextRunArray& newruns;
+ const wchar_t* display_string;
+ const size_t wcscount;
+
+ const ON_Font* font;
+ double height_scale;
+ ON_SimpleArray glyph_list;
+ ON_TextBox text_box;
+
+public:
+ ON_TextWrapper(const ON_TextRun& run, ON_TextRunArray& runs) :
+ text_run(run),
+ newruns(runs),
+ display_string(run.DisplayString()),
+ wcscount(nullptr != display_string ? wcslen(display_string) : 0)
+ { }
+
+ int WrapText(
+ int call_count, // recursion depth
+ int start_char_offset, // char offset in cp array
+ double wrapwidth, // max linewidth
+ double& y_offset, // y offset from input run from previously added soft returns
+ double& linewidth // linewidth so far
+ )
+ {
+ font = text_run.Font();
+ if (nullptr == font)
+ return 0;
+
+ height_scale = text_run.HeightScale(font); // Font units to world units
+ ON_FontGlyph::GetGlyphList(display_string, font, ON_NextLine, glyph_list, text_box);
+ {
+ const ON_FontGlyph* Aglyph = font->CodePointGlyph((ON__UINT32)L'A');
+ if (nullptr == Aglyph)
+ return 0;
+
+ const ON_TextBox& Aglyph_box = Aglyph->GlyphBox();
+ double Awidth = Aglyph_box.m_advance.i * height_scale;
+ // height scale is ~1e-3 so floor() will pretty much always make this value 0
+ // when text height is much less than 1, so this is a test for a NAN result
+ if (floor(Awidth) < 0.0)
+ {
+ ON_ERROR("Font height scale * width of 'A' is less than 0\n");
+ return 0;
+ }
+ if (!(Awidth > 0.0 && wrapwidth >= Awidth))
+ return 0;
+ }
+
+ return WrapTextRun(call_count, start_char_offset, wrapwidth, y_offset, linewidth);
+ }
+
+private:
+ int WrapTextRun(
+ int call_count, // recursion depth
+ int start_char_offset, // char offset in cp array
+ double wrapwidth, // max linewidth
+ double& y_offset, // y offset from input run from previously added soft returns
+ double& linewidth // linewidth so far
+ )
+ {
+ int new_count = 0;
+
+ if (max_recursion_level < call_count)
+ {
+ ON_ERROR("WrapTextRun: Recursion too deep.");
+ return 0;
+ }
+ if (0 > start_char_offset)
+ {
+ ON_ERROR("WrapTextRun: String start offset < 0.");
+ start_char_offset = 0;
+ }
+ if (0.0 > linewidth)
+ {
+ ON_ERROR("WrapTextRun: Linewidtht < 0.");
+ linewidth = 0.0;
+ }
+
+ wchar_t* temp_display_str = (wchar_t*)onmalloc((wcscount + 1) * sizeof(wchar_t));
+ double runwidth = 0.0; // run width without trailing spaces
+ {
+#pragma region Run Width
+ //double runwidth0 = 0.0; // run width including trailing spaces
+ if (0 == start_char_offset) // using the whole run
+ {
+ //runwidth = Advance().x;
+ runwidth = text_box.m_advance.i * height_scale;
+ }
+ else // Part of the run has already been picked off and added to the previous line
+ {
+ // Find width of remaining characters
+ for (int ci = start_char_offset; ci < wcscount && ci < glyph_list.Count(); ci++)
+ {
+ const ON_FontGlyph* gi = glyph_list[ci];
+
+ if (nullptr != gi)
+ {
+ const ON_TextBox& glyph_box = gi->GlyphBox();
+ double charwidth = glyph_box.m_advance.i * height_scale;
+ runwidth += charwidth;
+ }
+ }
+ }
+
+ if (0.0 > runwidth)
+ runwidth = 0.0;
+#pragma endregion Run Width
+
+#pragma region Whole Run
+ if (runwidth + linewidth <= wrapwidth || 2 > wcscount)
+ {
+ // Adding this entire run won't go past wrap width
+ // or the run has only 0 or 1 character and can't be wrapped
+ ON_TextRun* newrun = ON_TextRun::GetManagedTextRun();
+ if (nullptr != newrun)
+ {
+ *newrun = text_run;
+ if (text_run.Type() == ON_TextRun::RunType::kNewline ||
+ text_run.Type() == ON_TextRun::RunType::kParagraph ||
+ text_run.Type() == ON_TextRun::RunType::kSoftreturn)
+ linewidth = 0.0;
+ else
+ {
+ if (0 != start_char_offset)
+ {
+ wcsncpy(temp_display_str, display_string + start_char_offset, wcscount - start_char_offset);
+ temp_display_str[wcscount - start_char_offset] = 0;
+ newrun->SetDisplayString(temp_display_str);
+ }
+ linewidth += runwidth;
+ }
+ newruns.AppendRun(newrun);
+ onfree(temp_display_str);
+ return 1; // 1 new run was added
+ }
+ }
+#pragma endregion Whole Run
+ }
+
+ // Find what part of the run will fit
+ bool found_space = false;
+ int last_space = -1;
+ int run_length = 0;
+ double curwidth = 0.0;
+ double linefeedheight = font->FontMetrics().LineSpace() * height_scale;
+
+ int sp_count = 0;
+ for (int ci = start_char_offset; ci < (int)wcscount; ci++)
+ {
+ if ((ci + 1) < wcscount &&
+ display_string[ci] >= 0xD800 && display_string[ci] < 0xDC00 &&
+ display_string[ci + 1] >= 0xDC00 && display_string[ci + 1] < 0xE000)
+ {
+ sp_count++;
+ continue;
+ }
+
+ int gi = ci - sp_count;
+ if (gi >= glyph_list.Count())
+ break;
+
+ const ON_FontGlyph* glyph = glyph_list[gi];
+ if (nullptr != glyph)
+ {
+ const ON_TextBox& glyph_box = glyph->GlyphBox();
+ curwidth += glyph_box.m_advance.i * height_scale;
+ run_length++;
+
+ if (linewidth + curwidth > wrapwidth) // reached wrapping width
+ {
+ if (found_space) // store run up to last space
+ run_length = last_space - start_char_offset + 1;
+ else if (0.0 < linewidth) // A line is already started
+ run_length = 0;
+ else // no space yet - store run up to this char position
+ run_length = ci - start_char_offset;
+
+ if (0 < run_length)
+ {
+ ON_TextRun* newrun = ON_TextRun::GetManagedTextRun(); // make a new run
+ if (nullptr != newrun)
+ {
+ *newrun = text_run;
+ wcsncpy(temp_display_str, display_string + start_char_offset, run_length);
+ temp_display_str[run_length] = 0;
+ newrun->SetDisplayString(temp_display_str);
+ newrun->SetOffset(ON_2dVector(0.0, y_offset + text_run.Offset().y));
+ newruns.AppendRun(newrun);
+ }
+ }
+ // add a soft return
+ ON_TextRun* lfrun = ON_TextRun::GetManagedTextRun();
+ if (nullptr != lfrun)
+ {
+ lfrun->SetFont(font);
+ lfrun->SetType(ON_TextRun::RunType::kSoftreturn);
+ lfrun->SetTextHeight(text_run.TextHeight());
+ newruns.AppendRun(lfrun);
+
+ // Starting a new line now
+ linewidth = 0.0;
+ curwidth = 0.0;
+ y_offset -= linefeedheight;
+ }
+
+ int wrapcount = WrapTextRun(call_count + 1, run_length + start_char_offset, wrapwidth, y_offset, linewidth);
+
+ onfree(temp_display_str);
+ return new_count + wrapcount;
+ }
+ if (iswspace(display_string[ci]))
+ {
+ found_space = true;
+ last_space = ci;
+ }
+ }
+ }
+
+ ON_TextRun* newrun = ON_TextRun::GetManagedTextRun(); // make a new run
+ if (nullptr != newrun)
+ {
+ *newrun = text_run;
+ wcsncpy(temp_display_str, display_string + start_char_offset, run_length);
+ temp_display_str[run_length] = 0;
+ newrun->SetOffset(ON_2dVector(0.0, y_offset + text_run.Offset().y));
+ newruns.AppendRun(newrun);
+ new_count += 1;
+ }
+ onfree(temp_display_str);
+ return new_count;
+ }
+};
+
int ON_TextRun::WrapTextRun(
int call_count, // recursion depth
int start_char_offset, // char offset in cp array
@@ -1057,206 +1301,8 @@ int ON_TextRun::WrapTextRun(
ON_TextRunArray& newruns// new runs made by wrapping
) const
{
- int new_count = 0;
-
- if (500 < call_count)
- {
- ON_ERROR("WrapTextRun: Recursion too deep.");
- return 0;
- }
- if (0 > start_char_offset)
- {
- ON_ERROR("WrapTextRun: String start offset < 0.");
- start_char_offset = 0;
- }
-
- if (0.0 > linewidth)
- {
- ON_ERROR("WrapTextRun: Linewidtht < 0.");
- linewidth = 0.0;
- }
-
- const wchar_t* display_string = DisplayString();
-
- size_t wcscount = 0;
- if (nullptr != display_string)
- wcscount = wcslen(display_string);
-
- const ON_Font* font = Font();
- if (nullptr == font)
- return 0;
- double height_scale = HeightScale(font); // Font units to world units
-
- ON_SimpleArray< const ON_FontGlyph*> glyph_list;
- ON_TextBox text_box;
-
- ON_FontGlyph::GetGlyphList(display_string, font, ON_NextLine, glyph_list, text_box);
- int glyph_count = glyph_list.Count();
-
- const ON_FontGlyph* Aglyph = font->CodePointGlyph((ON__UINT32)L'A');
- if (nullptr == Aglyph)
- return 0;
- const ON_TextBox Aglyph_box = Aglyph->GlyphBox();
- double Awidth = Aglyph_box.m_advance.i * height_scale;
- // height scale is ~1e-3 so floor() will pretty much always make this value 0
- // when text height is much less than 1, so this is a test for a NAN result
- if (floor(Awidth) < 0.0)
- {
- ON_ERROR("Font height scale * width of 'A' is less than 0\n");
- return 0;
- }
- if (!(Awidth > 0.0 && wrapwidth >= Awidth))
- return 0;
-
-#pragma region Run Width
- double runwidth = 0.0; // run width without trailing spaces
- //double runwidth0 = 0.0; // run width including trailing spaces
- if (0 == start_char_offset) // using the whole run
- {
- //runwidth = Advance().x;
- runwidth = text_box.m_advance.i * height_scale;
- }
- else // Part of the run has already been picked off and added to the previous line
- {
- // Find width of remaining characters
- for (int ci = start_char_offset; ci < wcscount && ci < glyph_count; ci++)
- {
- const ON_FontGlyph* gi = glyph_list[ci];
-
- if (nullptr != gi)
- {
- const ON_TextBox glyph_box = gi->GlyphBox();
- double charwidth = glyph_box.m_advance.i * height_scale;
- runwidth += charwidth;
- }
- }
- }
-
- if (0.0 > runwidth)
- runwidth = 0.0;
-#pragma endregion Run Width
-
-#pragma region Whole Run
- wchar_t* temp_display_str = (wchar_t*)onmalloc((wcscount + 1) * sizeof(wchar_t));
- if (runwidth + linewidth <= wrapwidth || 2 > wcscount)
- {
- // Adding this entire run won't go past wrap width
- // or the run has only 0 or 1 character and can't be wrapped
- ON_TextRun* newrun = ON_TextRun::GetManagedTextRun();
- if (nullptr != newrun)
- {
- *newrun = *this;
- if (Type() == ON_TextRun::RunType::kNewline ||
- Type() == ON_TextRun::RunType::kParagraph ||
- Type() == ON_TextRun::RunType::kSoftreturn)
- linewidth = 0.0;
- else
- {
- if (0 != start_char_offset)
- {
- wcsncpy(temp_display_str, display_string + start_char_offset, wcscount - start_char_offset);
- temp_display_str[wcscount - start_char_offset] = 0;
- newrun->SetDisplayString(temp_display_str);
- }
- linewidth += runwidth;
- }
- newruns.AppendRun(newrun);
- onfree(temp_display_str);
- return 1; // 1 new run was added
- }
- }
-#pragma endregion Whole Run
-
- // Find what part of the run will fit
- bool found_space = false;
- int last_space = -1;
- int run_length = 0;
- double curwidth = 0.0;
- double linefeedheight = font->FontMetrics().LineSpace() * height_scale;
-
- int sp_count = 0;
- for (int ci = start_char_offset; ci < (int)wcscount; ci++)
- {
- if ((ci + 1) < wcscount &&
- display_string[ci] >= 0xD800 && display_string[ci] < 0xDC00 &&
- display_string[ci + 1] >= 0xDC00 && display_string[ci + 1] < 0xE000)
- {
- sp_count++;
- continue;
- }
-
- int gi = ci - sp_count;
- if (gi >= glyph_count)
- break;
-
- const ON_FontGlyph* glyph = glyph_list[gi];
- if (nullptr != glyph)
- {
- const ON_TextBox glyph_box = glyph->GlyphBox();
- curwidth += glyph_box.m_advance.i * height_scale;
- run_length++;
-
- if (linewidth + curwidth > wrapwidth) // reached wrapping width
- {
- if (found_space) // store run up to last space
- run_length = last_space - start_char_offset + 1;
- else if (0.0 < linewidth) // A line is already started
- run_length = 0;
- else // no space yet - store run up to this char position
- run_length = ci - start_char_offset;
-
- if (0 < run_length)
- {
- ON_TextRun* newrun = ON_TextRun::GetManagedTextRun(); // make a new run
- if (nullptr != newrun)
- {
- *newrun = *this;
- wcsncpy(temp_display_str, display_string + start_char_offset, run_length);
- temp_display_str[run_length] = 0;
- newrun->SetDisplayString(temp_display_str);
- newrun->SetOffset(ON_2dVector(0.0, y_offset + Offset().y));
- newruns.AppendRun(newrun);
- }
- }
- // add a soft return
- ON_TextRun* lfrun = ON_TextRun::GetManagedTextRun();
- if (nullptr != lfrun)
- {
- lfrun->SetFont(Font());
- lfrun->SetType(ON_TextRun::RunType::kSoftreturn);
- lfrun->SetTextHeight(this->TextHeight());
- newruns.AppendRun(lfrun);
-
- // Starting a new line now
- linewidth = 0.0;
- curwidth = 0.0;
- y_offset -= linefeedheight;
- }
-
- int wrapcount = WrapTextRun(call_count + 1, run_length + start_char_offset, wrapwidth, y_offset, linewidth, newruns);
- onfree(temp_display_str);
- return new_count + wrapcount;
- }
- if (iswspace(display_string[ci]))
- {
- found_space = true;
- last_space = ci;
- }
- }
- }
-
- ON_TextRun* newrun = ON_TextRun::GetManagedTextRun(); // make a new run
- if (nullptr != newrun)
- {
- *newrun = *this;
- wcsncpy(temp_display_str, display_string + start_char_offset, run_length);
- temp_display_str[run_length] = 0;
- newrun->SetOffset(ON_2dVector(0.0, y_offset + Offset().y));
- newruns.AppendRun(newrun);
- new_count += 1;
- }
- onfree(temp_display_str);
- return new_count;
+ ON_TextWrapper wrapper(*this, newruns);
+ return wrapper.WrapText(call_count, start_char_offset, wrapwidth, y_offset, linewidth);
}
ON_StackedText::StackStyle ON_StackedText::StackStyleFromUnsigned(